김영한 님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard
데이터: 회원ID, 이름
기능: 회원 등록, 조회
아직 데이터 저장소가 선정되지 않음(가상의 시나리오)
Controller : 웹 MVC의 컨트롤러 역할
Service : 비지니스 도메인 객체를 가지고 핵심 비즈니스 로직이 동작하도록 구현한 계층
Repositroy : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
Domain : 회원, 주문, 쿠폰처럼 주로 데이터베이스에 저장하고 관리되는 비즈니스 도메인 객체
아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정
개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
public interface MemberRepository {
// 저장소에 저장, 저장된 회원을 반환
Member save(Member member);
// 찾기
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findALl();
}
Optional<>
: 가져오는 값이 없으면 null이 반환되기 때문에 Optional로 감싸서 반환 (Java8)public class MemoryMemberRepository implements MemberRepository{
// 메모리에 저장을 위해 선언 (DB가 정해지지 않았기 때문)
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());
}
}
private static Map<Long, Member> store = new HashMap<>()
Optional.ofNullable(store.get(id))
store.values().stream().filter(member -> member.getName().equals(name)).findAny()
new ArrayList<>(store.values())
개발한 기능을 실행해서 테스트 할 때 자바의 main 메서드를 통해서 실행하거나, 웹 애플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다
이러한 방법은 준비하고 실행하는데 오래 걸리고, 반복 실행하기 어렵고 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다
자바는 JUnit이라는 프레임워크로 테스트를 실행해서 이러한 문제를 해결한다
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
void save() {
Member member = new Member();
member.setName("spring");
// 저장
repository.save(member);
// 검증
Member result = repository.findById(member.getId()).get();
Assertions.assertThat(member).isEqualTo(result);
}
}
Assertions.assertEquals(member, result);
Assertions.assertThat(member).isEqualTo(result);
< MemoryMemberRepository >
public void clearStore() {
store.clear();
}
< MemoryMemberRepositoryTest >
@AfterEach
public void afterEach() {
repository.clearStore();
}
테스트 메소드의 모든 member의 이름을 동일하게 작성하면 전체 테스트 진행 시 오류가 발생
@AfterEach
: 하나의 테스트 실행이 끝날 때마다 호출되도록 함
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// 회원 가입
public Long join(Member member) {
// 같은 이름이 있는 중복 회원 불가 조건
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
// 같은 이름의 회원이 있으면 예외 발생
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent( m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
// 전체 회원 조회
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
Service는 Repository와 domain을 활용
ifPresent()
: null이 아닌 값이 존재하는 경우
orElseGet
: 값이 있으면 꺼내고 없으면 디폴트를 실행
get()
: 이 메소드로 꺼낼 수 있는데 권장하지 않는 방식
given (주어진 상황)
when (어떤 것을 실행했을 때)
then (결과가 어떤 것이 나와야 함)
전체 테스트를 위해 clearStore()를 사용하려면 Test코드에서 repository 객체(인스턴스)를 생성해야함
그렇게 되면 MemberService에서 생성한 respository 객체가 있고 Test에서 생성한 repository 객체가 존재하게 되는데 이 때, 두 객체는 서로 다른 객체
오류가 발생하진 않진 않지만 굳이 같은 기능을 하는 객체를 두 개 만들 필요는 없기 때문에 아래처럼 코드를 수정하는 것이 좋음
< MemberService >
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
MemberService 내부에서 repository 객체를 생성하지 않는다
외부에서 객체를 넣어주도록 (DI)
다음 게시글에 자세히 설명
< MemberServiceTest >
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
Test를 실행하기 전, 실행되는 코드
repository 객체를 생성하고, Service 객체를 생성하면서 생성한 repository 객체를 주입