이번 시간에는 앞서 구성한 회원 Repository와 Domain을 활용해서 실제 비즈니스 로직인 회원 서비스 코드를 짜보도록 한다.
Service Package를 생성하고, MemberService 클래스를 생성한다.

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 { // ctrl + shift + t 하면 test 생성
private final MemberRepository memberRepository = new MemoryMemberRepository();
public Long join(Member member){
//같은 이름이 있는 중복 회원은 안된다.
까지 작성하고, 회원 가입시 중복을 방지하기 위한 코드를 아래에 덧붙혀 작성한다.
memberRepository.findByName(member.getName());
//를 작성하고 단축키 Ctrl + Alt + v 해주면 리턴 받는 변수를 아래 코드처럼 자동으로 생성해준다.
Optional<Member> result = memberRepository.findByName(member.getName(());
result.ifPresent( m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
//ifPresent는 null이 아니라 값이 있으면 해당 로직이 작동한다.
//기존에는 if문으로 null이 아닌지 확인한 후에 해당 로직을 작성해야하지만 우리는 Optional로 감쌌기때문에 ifPresent 같은 메서드를 사용할 수 있다.
});
//그렇지만 Optional로 감싸서 반환하는 건 좋지 않다고 한다. 후에 강의 참조.
// Optional 코드를 아래와 같이 작성할 수 있다.
memberRepository.findByName(member.getName()).ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.")});
위 코드에 커서를 대고, 단축키 Ctrl + Alt + Shift + T 를 이용하여 Refactoring 할 수 있는 단축키들을 확인할 수 있고,
그중에 단축키 Ctrl + Alt + M 을 이용하여 해당 코드를 하나의 메서드로 묶어주자.
회원 가입하는 join 메서드부터 refactoring 된 코드로 다시 작성해보자.
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("이미 존재하는 회원입니다.");
}); //
}
join, validateDuplicateMember와 같이 서비스 클래스는 서비스 로직에 관련된 용어를 사용해야 개발자든 기획자든 용이하다.
/**
*
* 전체 회원 조회
*/
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
이전 장에서는 기능을 테스트하기 위해 src/test/java 하위 폴더에 패키지를 생성하고, 테스트 클래스를 직접 생성하여서 테스트하였었다. 이 과정을 단축키를 이용하여 간략하게 할 수 있다.
상단에 작성해 놓은 코드를 보면 다음과 같이 작성되어있다.
public class MemberService { // ctrl + shift + t 하면 test 생성
하면 자동 생성이 된다.
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
MemberService memberService = new MemberService();
@Test
void 회원가입() {
//given
Member member = new Member();
member.setName("hello");
//when
//then
}
테스트 코드는 한글로 작성해도 상관 없다. 테스트 코드는 실무에서 국내라면 한글로 작성하면 직관적으로 바로바로 알아볼 수 있기 때문에 유용하다. 뿐만 아니라 테스트 코드는 빌드될 때 실제 코드에 포함되지 않으므로 한글로 작성해도 괜찮다.
위에 코드에 보면 회원가입 메서드에 given, when, then이 작성되어 있다.
이제 비즈니스 로직 테스트 코드를 작성하고 테스트 한다.
void 회원가입() {
//given
Member member = new Member();
member.setName("hello");
//when
Long saveId = memberService.join(member); //반환 받는 변수 만들기 ctrl + alt + v
//then
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
//static import는 alt + enter
}
@Test
public void 중복_회원_예외(){
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
memberService.join(member1);
try{
memberService.join(member2);
fail();
} catch (IllegalStateException e){
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
memberService 클래스의 join 메서드가 내포하고 있는 validateDuplicateMember 메서드에서 회원 이름이 중복인 경우에 에러메세지를 리턴하기로 작성하였다.
그래서 Test case에서 에러 메세지를 처리하기 위해 try-catch문을 사용하였다.
try-catch 코드를 assertThrows( )로 줄여서 사용할 수 있다.
주석 처리하고, 아래 코드를 추가하자.
memberService.join(member1);
/*try{
memberService.join(member2);
fail();
} catch (IllegalStateException e){
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}*/
assertThrows(IllegalStateException.class, () -> memberService.join(member2));
여기까지 작성하고 실행하면 에러 없이 잘 실행될 것이다. 그러나 회원가입의 setName("hello")를 setName("spring")으로 저장하게 되면 에러가 나타날 것이다. 왜 그런 걸까?
바로 아래 중복회원예외( ) 메서드에서 member1 또한 setName("spring")으로 저장하고 있기 때문이다. 하나의 메모리 저장소를 같이 사용하고 있는데, 같은 이름으로 저장하게 되면 중복이기 때문에 저장이 안되는 것이다.
이를 test case에서 방지하기 위해 우리는 각 test가 실행될 때마다 각 저장소를 clear해줄 필요가 있다.
MemoryMemberRepository와 MemberService 객체를 선언해준 부분부터 아래와 같이 수정한다.
class MemberServiceTest {
//MemberService memberService = new MemberService(memberRepository);
//MemberService에서 생성된 new MemoryMemberRepository와 Test case에서 생성된 MemoryMemberRepository가 서로
//다른 객체이다. 서로 다른 repo를 쓰면 저장이 원활히 안되거나 문제가 발생할 수 있기 때문에, 같은 reposit을 공유하도록 해야한다.
// 멤버 서비스를 수정해준다. memberServie의 &&표시 부분 참고
MemoryMemberRepository memberRepository;
// 메모리를 클리어하기 위해 repository를 불러준다.
MemberService memberService;
@BeforeEach //매 테스트가 동작하기전에 작동하는 annotation
public void beforeEach(){
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
// 같은 메모리 memberRepository 가 사용될 수 있도록 한다.
// 외부에서 메모리를 넣어주는 것 D.I dependency injection 이라고 한다.
}
코드를 실행하게 되면서 주석 처리를 해놓은 부분은 @BeforeEach annotation을 사용하여 재작성하였다.
@BeforeEach는 각 테스트 케이스가 실행되기 전에 먼저 실행되는 부분인데, 이번에 하는 역할은 각 테스트 케이스가 실행될 때 객체를 새로 생성하여 메모리를 clear함으로써 db가 비워지도록 하였다.
memberService = new MemberService(memberRepository);
로 작성한 것을 볼 수 있는데,
MemberService memberService = new MemberService();
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
위가 원래 코드였다. 하지만 이렇게 되면 test 코드에서 생성된 memberRepository가 MemberService class에서 생성된 memberRepository와 다른 객체가 된다. 이렇게 되는 경우 같은 역할을 한다고 하여 생성해놓은 객체가 한쪽에는 저장이 되고, 한쪽에는 저장이 안되는 등 문제가 발생할 수 있으므로, 하나로 공유하게끔 해주어야 한다.
그래서 해당 코드를 위에 작성한 부분과 같이 수정하여 같은 메모리 memberRepository가 사용될 수 있도록 한다.
위와 같이 외부에서 메모리를 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라고 한다.
이번 장에서는 메모리 공유, DI 등 이제 스프링에 입문하는 나에게는 꽤 복잡한 내용들이 함유되어 있었고, 꼼꼼한 강의라기보단 스프링이 어떻게 작동하는 지에 대한 강의이기 때문에 느낌만 파악하게끔 설명하고 지나간 부분이 많다. 설명이 부족하거나 미흡한 부분들은 추후 강의를 수강하면서 해당 내용들을 보충해나가도록 할 것이다.