비즈니스 요구사항 정리
데이터: 회원ID, 이름
기능: 회원 등록, 조회
컨트롤러: 웹 MVC의 컨트롤러
서비스: 핵심 비즈니스 로직
도메인: 비즈니스 도메인 객체
리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
리포지토리는 아직 db가 선정되지 않았으므로 interface로 만든다.
나중에 구현 클래스로 변경이 가능하다.
초기 개발 단계에 구현체로 가벼운 메모리 기반의 데이터 저장소를 사용한다.
회원 도메인과 리포지토리 만들기
도메인: 데이터의 형태를 설계한다. getter, setter함수를 포함한다.
/domain/Memeber.java
리포지토리:
Optional: Java8에 추가되었다. 검색 결과로 나온 null을 처리하는 방법 중 하나가 된다.
회원 리포지토리 테스트 케이스 작성
개발한 기능을 실행해서 테스트 할 때 main 메서드를 통해서 실행하거나, 웹 어플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다. 자바의 JUnit이라는 프레임워크로 테스트를 실행하면 이러한 문제를 해결할 수 있다.
@AfterEach
public void afterEach(){
repository.clearStore();
}
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get(); //optional처리한 값을 가져오기
assertThat(member).isEqualTo(result);
}
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member result = repository.findByName("spring1").get();
assertThat(result).isEqualTo(member1);
}
@Test
public void findAll() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(2);
}
@AfterEach: 한번에 여러 테스트를 진행하면 메모리 DB에 직전 테스트의 결과가 남는 경우가 있다. 이런 경우 다음에 실행하는 테스트가 영향을 받을 수 있다. 따라서 테스트 이후 데이터를 비워주어야 한다. @AfterEach를 사용하면 각 테스트가 종료될 때 마다 이 기능을 실행한다.
테스트는 독립적으로 수행되어야 한다. 따라서 테스트는 의존관계를 가져서는 안된다.
회원 서비스 개발
package hello.hellospring.Service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
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 -> { //optional처리, 과거에는 if(null)로 처리했다.
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
public List<Member> findMember() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
command + option + v: return값의 타입에 맞는 변수 자동 생성
Optional형으로 리턴한 값은 Optional 안에 객체가 존재한다. 이후 Optonal관련 매서드를 이용해서 get한 데이터를 처리한다.
Optional값을 체인 메서드를 이용해서 처리할 수도 있다.
control + t: 리팩토링과 관련된 여러가지 기능을 지원한다. Exact Method를 통해 정의한 함수를 외부로 뺀다.
getorElseGet: 값이 있으면 꺼내고 없으면 특정 매서드를 수행
서비스는 비즈니스적인 로직을 처리하니 네이밍도 맞춰서 명명한다.
회원 서비스 테스트
테스트하고 싶은 클래스 안에서 단축키 command + shift + t를 하면 test를 바로 설정할 수 있다.
테스트코드 템플릿을 자동으로 생성해준다.
테스트코드의 함수는 한글로 작성해도 된다.
//given
//when
//then
위 3가지로 나누어 테스트코드를 작성하면 가독성이 올라간다.
package hello.hellospring.Service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
@Test
void join() {
//given
Member member = new Member();
member.setName("hello");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void 중복_회원_예외() {
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
//두번 째 콜백 함수를 실행시켰을 때, 앞의 Exception이 발생해야 한다.
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//return 값은 에러
// try {
// memberService.join(member2);
// fail(); //catch로 넘어가지 않았다면 fail 실행
// } catch (IllegalStateException e) {
// assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// }
//then
}
}
테스트는 되는 것도 중요하지만, 예외 처리가 제대로 발생하는지 검증하는 것도 중요하다.
ctrl + r: 이전에 실행했던 명령 반복
memoryMemberRepository를 서비스와 테스트에서 따로 두면
static이 없어지면 다른 db가 된다.
현재는 다른 리포지토리를 이용한다. 같은 인스턴스를 사용하려면
before each처리를 통해 같은 메모리 리포지토리를 사용한다.
직접 new 하지 않고 외부에서 멤버 리포지토리를 넣는다
이를 의존성 주입, 디펜던시 인젝션이라고 한다.
'Backend > spring' 카테고리의 다른 글
Spring ApplicationContext, IoC, Singleton (0) | 2022.01.08 |
---|---|
스프링 빈과 의존관계 (0) | 2021.11.24 |
스프링 웹 개발 기초 (0) | 2021.11.22 |
Spring 프로젝트 환경설정 (0) | 2021.11.22 |
메이븐으로 스프링 시작하기 (0) | 2021.11.19 |