1. 비지니스 요구사항
회원관리예제를 진행하기 앞서, 우리는 '비지니스 요구사항'을 정리해야 한다.
이때, 가정조건은 "개발의 DB(데이터베이스) 선정이 없는 시나리오" 입니다.
Q1. 왜 DB가 없어요?
스프링 생태계를 이해하는게 우선이며, 스프링 동작원리를 이해하는게 목적이기 때문에
프로젝트는 단순한 비지니스 예제로 익히기 위해서 입니다.
Q2. 일반적인 웹 애플리케이션 계층구조
일반적인 웹 애플리케이션에는 컨트롤러, 서비스, 리포지토리, 도메인, DB로 이루어져 있습니다.
다음과 같이 만들 겁니다.
- 서비스 : 회원조회(중복가입 방지)
- 도메인 : 회원, 주문, 할인쿠폰(주로 DB에 저장하고 관리되도록 한다.)
- 리포지토리 : DB 접근, 도메인 객체를 DB에 저장하고 관리한다.
Q3. 클래스 의존관계
그림과 같이, 회원저장을 interface 설계로 MemberRepository를 만들고, MemoryMemberRepository를 메모리 구현체로 만들겁니다.
Q4. 인터페이스(interface)설계로 하는 이유?
비지니스 가정조건대로 현재 "아직 데이터 저장소가 선정되지 않은 상태"입니다.
interface설계를 통해 가벼운 메모리모드(MemoryMemberRepository)로 단순히 저장하는 개발을 시도하면, 향후에 RDB, NoSQL 중에서 DB를 선정하여 개발할 수도 있습니다. interface는 나중에 변경가능하기 때문입니다.
2. 코드
1. hello.hellospring에 domain패키지를 생성한다.
2.hello.hellospring.domain > Member(class)생성
package hello.hellospring.domain;
public class Member {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Q. 코드 분석
private Long id;
'id'는 식별자로,
임의의 값으로 DB에 데이터 저장하든, sequence에 하든 상관없이 고객이 아닌 "시스템이 지정하는 ID"입니다.
Q.여기서 Getter&Setter는 왜 열어 두나요?
단순예제이므로 그냥 다 사용하도록 허용했습니다.
3.회원 리포지토리 생성
package hello.hellospring.repositoty;
import hello.hellospring.domain.Member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
Q. ripository(리포지토리)가 뭔가요?
쉽게 말해 '저장소'라고 생각하면 됩니다.
Q. Optional<>은 뭔가요?
자바8에 들어있는 기능으로 간단히 findById나 findByName이 가져온 과정에서 값이 null(값이 없음)일 수도 있으니까 null을 처리하기 위해 요즘에는 optional<>으로 감싸서 선언하는 것을 선호합니다.
Q. 코드설명
Member save(Member member); //save() :회원이 저장소에 저장된다.
Optional<Member> findById(Long id); //findById, findByName : Id,Name을 찾아온다.
Optional<Member> findByName(String name);
List<Member> findAll(); //지금까지 저장된 회원 리스트를 모두 반환해준다.
4.구현체 생성한다.
package hello.hellospring.repositoty;
import hello.hellospring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
Q. Implement methods는 어떻게 하는지?
Implement 코드 위에 마우스를 가져다 대면, 옵션이 나오는데 'All' 선택하면 됩니다.
Q. Hash Map<>은 뭔가요?
구현을 하기 위해, save(Member member)를 할 때 메모리를 저장해두어야 하는데,
이때 Hash Map<>을 사용해서 메모리를 저장하였습니다.
Q. Hash Map<>을 사용한 이유는?
실무에서는 동시성 문제가 있을 수 있어서 공유되는 변수일 경우에 ConcurrentHashMap을 사용해야 하지만
예제를 하는 수업이니 단순히 HashMap을 사용한 것뿐입니다.
Q. 0L은 뭔가요?
private static long sequence = 0L;
sequencesms 0,1,2....로 키값을 생성해주는데, 위 코드는 "0번 키값 생성"입니다.
//그리고 실무에서는 long타입보다는 동시성 문제를 고려한 타입을 써야 하지만 단순예제이니까 long을 선택했습니다.
Q. 코드가 너무 어려워요...
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
store에 넣기전에(=store.put), Member에 Id값을 세팅해주고(=member.setId), store에 저장해준다.
name은 고객이 입력하는 데이터이기 때문에 이미 저장되어 있는 상태이며,
Id는 시스템저장할 때 시스템이 정해주기 때문에 sequence로 저장하는겁니다.
Q. findById는 store에서 꺼내면 되는 것입니다.
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
store.get(id)해서 꺼내면 되는데
"store.get()에 결과가 없다면 null이 반환이 되는데
- 옛날 : return store.get(id); //그냥 null을 반환했다.
- 요즘: return Optional.ofNullable(store.get(id)); → null이여도 optional로 감싸서 클라이언트의 처리가 가능해졌다.
Q. findByName은 람다식(?)으로 반환
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
- '->' : 자바 8의 람다를 사용해서 루프를 생성하였다.
- member.getName()에서 파라미터로 넘어온 Name이 같은지 확인한다.
- .findAny() : 하나라도 찾는 기능으로, 루프를 돌면서 하나 찾아지면 Optional<Member>에 반환하고, 못 찾으면Otional<Member>에 null을 포함하여 반환한다.
Q. findAll은 왜 list로 처리했나요?
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
실무에서는 루프 돌기 편하게 하기 위해서 list로 반환합니다.
store.values()dp member를 반환하면 구현이 끝납니다.
2편, 스포
다음 편에서는 동작확인할 수 있는 테스트케이스 작성을 배울 겁니다.
'spring' 카테고리의 다른 글
5. DI_ 컴포넌트 스캔과 자동 의존관계 설정 (0) | 2021.03.27 |
---|---|
4.회원 서비스 개발 (0) | 2021.03.26 |
3. 서비스 개발 (0) | 2021.03.26 |
2. 테스트케이스 작성 (0) | 2021.03.26 |
spring_카테고리 길잡이_소개 글 (0) | 2021.03.24 |
댓글