Search

회원 관리 예제 - 백엔드 개발

회원관리 예제를 만들어본다.

1. 비즈니스 요구사항 정리

김영한 강사님의 자료 사진입니다.
컨트롤러 : 웹 MVC의 컨트롤러 역할
서비스 : 핵심 비즈니스 로직 구현
도메인 : 비즈니스 도메인 객체
레포지토리: DB에 접근, 도메인 객체를 DB에 저장하고 관리
일반적인 계층형 구조를 따라간다.
김영한 강사님의 자료 사진입니다.
데이터 저장소가 정해지지 않아서 인터페이스로 구현 클래스 변경가능하도록 설계
(마이바티스 , jdbc , jpa 를 쓸지 저장하는 기술이 많아서 바꿔줄수있는 가정하에 인터페이스로 구현)

2. 회원 도메인과 리포지토리 만들기

회원 도메인과 그 도메인 객체를 저장하고 불러올 수 있는 저장소라 하는 레포지토리 생성한다.
domain 패키지 생성 후 Member.java 파일 생성해서 id, name 을 선언해주고 getter,setter 를 만들어준다
그리고 회원객체를 저장할 레포지토리 인터페이스 생성을 해줄거다
repository 패키지 생성 후 MemberRepository.java 인터페이스 생성 후 save 및 findById, findByName, findAll 함수 생성
repository 패키지 안에 MemoryMemberRepository.java 생성 implements를 MemberRepository하고 option키 누르면 메소드를 오버라이드 할 수 있다.

save()

@Override public Member save(Member member) { member.setId(++sequence); store.put(member.getId(), member); return member; }
Java
복사
id는 시스템에서 정해주는 값이여서 save할때 하나씩 올려준다

findById()

@Override public Optional<Member> findById(Long id) { return Optional.ofNullable(store.get(id)); }
Java
복사
Optional.ofNullable를 사용해서 null도 감쌀수 있게 해준다.

findByName()

@Override public Optional<Member> findByName(String name) { return store.values().stream() .filter(member -> member.getName().equals(name)) .findAny(); }
Java
복사
람다식을 사용해서
store.values().stream()
Java
복사
루프를 돌리면서
.filter(member -> member.getName().equals(name))
Java
복사
member.getName()이 여기 파라매터로 넘어온 name하고 같은지 확인하고 같으면 필터링 후 리턴
.findAny();
Java
복사
하나라도 찾는다.

finaAll()

@Override public List<Member> finaAll() { return new ArrayList<>(store.values()); }
Java
복사
store가 Map이기 때문에 List로 반환하기 위해서 new ArrayList 후 store의 모든 값을 넣어준다.
검증하기 위해서는 테스트 케이스를 작성해야한다.

3. 회원 리포지토리 테스트 케이스 작성

레포지토리 정상 작동하는지 테스트 케이스 작성한다.
자바의 main 메서드를 통해 실행
웹 애플리케이션의 컨트롤러를 통해서 해당 기능들을 각각 실행
하지만 이런 방법들은 너무 오래걸려서 자바에서 지원해주는 JUnit 프레임워크로 테스트를 실행해서 진행해본다.

JUnit 테스트

test 패키지 안에서 진행할거다
hellospring 안에 repository 패키지 생성 후 MemoryMemberRepositoryTest.java
@Test
Java
복사
를 붙여주면
왼쪽 이미지에 화살표 같은게 생긴데 해당 save 메소드를 실행시킬 수 있다

save()

@Test public void save(){ Member member = new Member(); member.setName("spring"); repository.save(member); Member result = repository.findById(member.getId()).get(); Assertions.assertEquals(member, result); }
Java
복사
Member result = repository.findById(member.getId()).get();
Java
복사
반환 타입이 옵셔널이라서 옵셔널에서 꺼내올때는 get()을 사용해서 꺼내올수있지만 좋은방법은 아니다.
(테스트 코드라서 사용)

데이터 값 검증 방법 - Assertions.assertEquals()

import org.junit.jupiter.api.Assertions;
Assertions.assertEquals(member, result);
Java
복사
을 사용해서 내가 넣은 spring 이름하고 내가 레포지토리에 넣은 이름하고 같은지 검증을 해준다.
검증 성공
Assertions.assertEquals(member, null);
Java
복사
null로 하고 돌리면

데이터 값 검증 방법 - Assertions.assertEquals()

import org.assertj.core.api.Assertions;
Assertions.assertThat(member).isEqualTo(result);
Java
복사
최근에 자주 쓰는 방식이면서 비교하는 검증 메소드다.
member가 result랑 같은지 비교.
option + enter 누르면 static으로 만들어주는거 클릭하면은
import static org.assertj.core.api.Assertions.*;
Java
복사
위에 import되고 하단에
assertThat(member).isEqualTo(result);
Java
복사
이렇게 쓸 수 있다.

findByName()

shfit + f6 누르면 rename 창이 뜨고 거기서 바꿔주면 같은 member1들이 변경된 단어로 수정된다.
@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); }
Java
복사

findAll()

@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.finaAll(); assertThat(result.size()).isEqualTo(2); }
Java
복사

통합 테스트

해당 클래스의 전체 테스트를 돌리면
갑자기 findByName이 오류난다.. 우리 아까까지만 해도 잘됐잖아..
오류난 이유는 finaAll()이 먼저 실행되고 메소드 안에서 save를 2번 진행해줄때 spring1을 저장해줬다
그 다음에 findByName에서 spring1을 저장해주고 같은지 검증할때 다른 객체에 저장된(finaAll에서 저장한) spring1을 찾아내면서 오류가 나버렸다.
해결 방법은 테스트 하나가 끝나면 이 데이터를 클리어 시켜줘야 한다.
clear()하는 기능을 하는 함수를 만들어서
@AfterEach public void afterEach(){ repository.clearStore(); }
Java
복사
메소드가 실행이 끝날때마다 이 메소드가 실행된다.
통합테스트 성공

결론

테스트는 순서와 관계없이 서로 의존관계 없이 설계가 되어야 한다
하나의 테스트가 끝날 때마다 저장소나 공용 데이터들을 깔끔하게 지워줘야 한다.

4. 회원 서비스 개발

실제 비즈니스 로직이 있는 회원 서비스 개발한다.
service 패키지 생성 후 MemberService.java 생성
command + option + v 누르면 return을 자동으로 맞춰준다.
회원가입에 대한 서비스로직을 만들건데
Optional<Member> result = memberRepository.findByName(member.getName()); result.ifPresent(m ->{ throw new IllegalStateException("이미 존재하는 회원입니다."); });
Java
복사
ifPresent()는 값이 존재한다면 실행되는 메소드다. 옵셔널이기 때문에 가능.
옵셔널로 감싸면 옵셔널안에 멤버 객체가 존재.
꺼내서 쓰고 싶으면 .get() 사용 ( 근데 비추천하고 보통은 .orElseGet() 사용)
근데 Optional로 감싸는게 이뻐보이지 않기 때문에
memberRepository.findByName(member.getName()) .ifPresent(m ->{ throw new IllegalStateException("이미 존재하는 회원입니다."); });
Java
복사
근데 findByName을 사용하는 로직은 method를 따로 뽑아서 사용하는게 좋다.
ctrl + T 클릭
validateDuplicateMember 라고 함수명을 지어주면
private void validateDuplicateMember(Member member) { memberRepository.findByName(member.getName()) .ifPresent(m ->{ throw new IllegalStateException("이미 존재하는 회원입니다."); }); }
Java
복사
생성 된다.

5. 회원 서비스 테스트

회원 서비스가 정상적으로 작동하는지 JUnit을 사용해서 테스트를 해본다.
command + shift + T 누르면
테스트 파일을 생성해준다.
다 선택 후 OK
package hello.hellospring.service; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class MemberServiceTest { @Test void join() { } @Test void findMember() { } @Test void findOne() { } }
Java
복사
자동으로 생성
테스트 파일에서는 join 이라고 영어로 써줘도 되고 한글로 써줘도 된다
@Test void 회원가입() { //given //when //then }
Java
복사
given
when
then
문법을 사용해서 테스트 코드짜는걸 추천한다
무언의 상황이 주어졌는데(given)
이거를 실행했을때 (when)
결과가 이게 나와야해 (then)
@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()); }
Java
복사
사실 이정도는 간단한 회원가입 프로세스고 이제 예외처리도 신경써줘야한다.
@Test public void 중복_회원_예외() { //given Member member1 = new Member(); member1.setName("spring"); Member member2 = new Member(); member2.setName("spring"); //when memberService.join(member1); try { memberService.join(member2); fail(); }catch (IllegalStateException e){ assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); } //then }
Java
복사
이렇게 try catch를 사용해서 검증할수도있지만
제공하는 assertThrows라는 함수도 있다.
assertThrows(IllegalStateException.class, () -> memberService.join(member2));
Java
복사
ctrl + R 누르면 이전에 실행한 테스트를 실행 할 수 있다
근데 애매한 부분이 있다
MemberService 에서
MemoryMemberRepository memberRepository = new MemoryMemberRepository();
Java
복사
이렇게 계속 new를 한거랑 MemberServiceTest에서 new를 한거랑은 서로 다른 객체다
이걸 두개 쓰는것보단 한개로 쓰는게 좋다.
혹시라도 뭔가 다른 인스턴스라서 내용물이 달라질수도있다
MemoryMemberRepository에서는 static으로 되어있어서 바로 문제는 안생기겠지만 static이 아니면 문제가 생긴다.
같은 인스턴스로 쓰게 할려면
MemberService에서
private final MemberRepository memberRepository; public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; }
Java
복사
이렇게 직접 new 해주는게 아니라 외부에서 넣어주는 방식으로 변경해준다.
그리고 MemberServiceTest에서
MemberService memberService = new MemberService(); MemoryMemberRepository memberRepository = new MemoryMemberRepository();
Java
복사
이 부분을
MemberService memberService; MemoryMemberRepository memberRepository; @BeforeEach public void beforeEach(){ memberRepository = new MemoryMemberRepository(); memberService = new MemberService(memberRepository); }
Java
복사
MemberService 입장에서는 내가 직접 new를 하지 않고 외부에서 멤버레포지토리를 넣어준다
이걸 Dependency Injection 이라고 한다. DI

*참고