리포지토리 다음으로 핵심 비즈니스 로직이 구현되는 서비스를 작성해보겠습니다. /hello.hellospring/service
라는 패키지를 생성하고 MemberService.java
파일을 추가해 다음과 같이 작성해줍니다.
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);
}
}
join()
은 회원가입 메소드로 validateDuplicateMember()
로 동일한 이름을 가진 회원이 이미 있는지 검사하고 검사를 통과하면 memberRepository
에 전달받은 회원을 저장합니다.findByName()
의 반환값은 Optional
로 감쌌기 때문에 ifPresent()
사용이 가능합니다. private final MemberRepository memberRepository = new MemoryMemberRepository();
회원 리포지토리 메모리 구현체를 사용하기 위해 memberRepository
객체를 생성한 위의 코드를 아래와 같이 변형하면, MemberService
객체를 생성할 때 외부에서 선언된 memberRepository
객체를 주입받아 사용할 수 있게 됩니다. 이와 같은 디자인 패턴을 의존성 주입이라고 부릅니다.
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
✋ 의존성 주입이란?
- 객체가 의존하는 또 다른 객체를 외부에서 선언하고 이를 주입받아 사용하는 것입니다.
- 의존성을 주입하는 방법으로는 객체를 생성할 때 생성자를 통해 전달하는 방식과
Setter()
메소드를 통해 전달하는 방식이 있습니다.- 의존성 주입을 이용하면 주입받는 대상이 변하더라도 기존 구현자체를 수정할 일이 없어 의존성이 감소합니다. 또한 재사용성이 증가하며 테스트하기 좋은 코드가 됩니다.
아래의 코드는 의존성 주입을 설명하기 위해, 음식만 변경해 주입함으로써 각기 다른 주문을 받을 수 있도록 코드를 설계한 것 입니다.
class Menu {
private Food food;
public Menu(Food food) {
this.food = food;
}
}
class Order {
private Menu menu = new Menu(new Pasta());
public void seaFood() {
menu = new Menu(new Lobster());
}
public void ChineseFood() {
menu = new Menu(new Dumpling());
}
public void KoreanFood() {
menu = new Menu(new Bibimbap());
}
}
작성된 회원 서비스 코드를 테스트하기 위해 /test/java/hello.hellospring/service
라는 패키지를 생성하고 MemberServiceTest.java
라는 파일을 만들어 다음과 같이 작성해줍니다.
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 회원가입() {
//given
Member member = new Member();
member.setName("spring");
//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));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
@BeforeEach
어노테이션을 이용해 각각의 테스트 전에 beforeEach()
메소드를 실행시킵니다. 이를 통해 테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고 새로 의존성도 주입해줍니다.@AfterEach
어노테이션을 이용하여 각각의 테스트가 끝날 때마다 메모리 DB에 저장된 데이터를 삭제해줍니다.@BeforeEach
로 새로 의존성을 주입해줬는대도 @AfterEach
를 사용해 데이터를 삭제해주는 이유는 MemoryMemberRepository
내의 store
가 static
필드이므로 인스턴스를 새롭게 생성해줘도 데이터는 남아있기 때문입니다.member1
과 member2
를 회원가입시켜서 예외가 발생하는지 확인하는 테스트로, 테스트를 통과하면 예외가 정상적으로 발생했다는 것을 의미합니다. Try-Catch 문을 사용해 아래의 방식으로도 작성가능합니다. //when
memberService.join(member1);
try{
memberService.join(member2);
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
다음과 같이 테스트를 통과해 검증이 완료된 것을 확인할 수 있습니다.
🙏 이 포스트는 김영한 개발자님의 <스프링 입문 강의> 를 듣고 공부한 내용을 바탕으로 작성되었습니다.
👌