[Study] BE/Spring & SpringBoot

[스프링 입문 - 김영한] 세션 3 회원 관리 예제

stop-zero 2023. 9. 18. 21:59

섹션 3-4 : 회원 서비스 개발 

회원 서비스 개발을 위한 service 패키지 안에 서비스 클래스를 생성한다.

 

public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();

}

회원 서비스가 메모리 회원 리포지토리를 직접 생성한다. 회원 서비스를 위한 MemberRepository를 선언해 준다. 

 기획자와의 원활한 소통을 위해 서비스 클래스에 관련된 이름은 비즈니스 적인 이름을 사용하는 것을 권장한다. 

ex) join, findmember

 

회원 가입

public Long join(Member member){
        // 같은 이름이 있는 중복 회원x
        memberRepository.findByName(member.getName());
        
        memberRepository.save(member);
        return member.getId();
    }

회원 관리 예제에서 같은 이름은 가입할 수 없도록 했다. 

 

단축키 Extract -> Introduce Variable : command+option+v
public Long join(Member member){
        // 같은 이름이 있는 중복 회원x
        Optional<Member> result = memberRepository.findByName(member.getName());
        result.ifPresent(m -> {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        });

        memberRepository.save(member);
        return member.getId();
    }

Optional로 반환되고, 값이 있으면 throw 이미 존재하는 회원이다. 

* 감싸지 않고 그냥 꺼내고 싶으면 result.get / .orElseGet : 값이 있으면 꺼내고 값이 없으면 default로 하는 경우도 있음 

 

But, Optional 을 바로 반환하는 것은 좋지 않다. 

권장하는 방법

	// 같은 이름이 있는 중복 회원x
        memberRepository.findByName(member.getName())
            .ifPresent(m -> {
                throw new IllegalStateException("이미 존재하는 회원입니다.");
            });

findByName 반환 결과가 Optional 이기에 바로. ifPresent로 들어가는 로직이 더 나은 방식이다. 

로직이 나왔다? > 메소드를 뽑자!

단축키 Refactor This : Ctrl + T > Extract Method : Command + Option + M
public Long join(Member member){
        // 같은 이름이 있는 중복 회원x
        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);
    }

전체 회원 조회하기 findMembers()

- MemberRepository Interface에서 findAll 반환 타입 확인하고 List로 return 해주기

 

*findOne 일단 작성.

 

섹션 3-5 : 회원 서비스 테스트

위에서 만든 중복 회원 검증하기 

이전에는 package 직접 생성했지만, 편하게 하는 방법이 있다. 

단축키 Navigate -> Go to Test : Command + Shift + T

사용한 메소드가 바로 다 뜨고 체크 후 OK 누르면 자동 생성된다. 

 

회원가입()

테스트는 한글로 바꿔도 괜찮다. build 될 때 실제 코드에 포함되지 않는다. 

 

given : 데이터가 이런 상황일 때
when : 어떤 작업을 수행하면
then : 이런 결과가 나온다. 

이런 식으로 주석을 깔고  test 작성하면 한눈에 뭐를 검증하는지 한눈에 알아볼 수 있다. 

 

단축키 이전 실행 반복 : Ctrl + R

자동 import 해주는 junit의 Assertions는 assertThat이 없기에 assertj의 Assertions 사용한다. 

    @Test
    void 회원가입() {
        //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());
    }

+ JUnit, AssertJ 개념 및 기초적인 사용법

 

//given 에서 member 객체를 생성하고 회원의 이름을 지정해 준다. (=hello)

//when 회원을 저장해주는 동작이 실행된다.

//then 저장한 회원을 조회해서 이름이 제대로 설정되었는지 확인한다. 

 

But, 예외 처리가 더 중요하다.

 

중복_회원_예외()

같은 이름으로 두번 join 했을 때 "이미 존재하는 회원..."이 나와야 한다. 

        //when
        try{
            memberService.join(member2);
            fail();
        }catch (IllegalStateException e){
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다. ");
        }

1) try catch 문으로 잡기

애매하다.

더보기

try - catch 문은 보통 try 문의 예외처리를 위해 사용한다. 위의 코드에서는 같은 이름의 회원이 이미 존재할 때 예외를 반환하는 것을 검증하기 위한 의도이기에 try-catch문으로 예외를 잡아서 처리하는 것은 협업하는 다른 개발자로부터 혼란을 줄 수 있다. 또한, 테스트 프레임워크의 기능을 사용하는 것이 훨씬 가독성이 좋다. 

 

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

2) assertThrows > member2를 넣으면 Exception이 터져야 함

 - 메시지 검증을 위해 반환하고 asserThat()

 

값의 누적 해결

    // 돌 때마다 다 끝나면 DB의 값을 날려줌.
    @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }

clear하려는데 memberservice밖에 없으니까 memberRepository 가져와서 clearStore() 하면 돌 때마다 메모리가 클리어된다. 

 

문제점 

MemberService에서 만든 MemoryMemberRepository()랑 MemberServiceTest 에서 만든 MemoryMemberRepository()랑 서로 다른 인스턴스이다. 물론 지금은 static이라 무관하지만, static이 아니면 문제다. 

같은 인스턴스 쓰게 변경하자.   

private final MemberRepository memberRepository;

유지하고 Constructor 생성

단축키 Generate : Command + N

 

    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

외부에서 넣어주게 바꾸고 테스트에 가서 테스트 실행할 때마다 독립적으로 실행한다. 

MemberService 입장에서는 직접 생성하지 않는다. =DI(Dependency Injection)에 관해서는 다음시간에 더 자세히 공부