Home 회원 관리 예제
Post
Cancel

회원 관리 예제

회원 관리 예제

비즈니스 요구사항

  • 데이터: 회원 ID, 이름
  • 기능: 회원 등록, 조회
  • 아직 데이터 저장소가 선정되지 않았다고 가정.

image

  • 컨트롤러: 웹 MVC 컨트롤러 역할
  • 서비스: 핵심 비즈니스 로직 구현 ( 중복 가입 방지 등 )
  • 리포지토리: DB에 접근, 도메인 객체를 DB에 저장하고 관리
  • 도메인: 비즈니스 도메인 객체 ( 회원, 주문, 쿠폰 등 DB에 주로 관리되는 ) (= Model)

image

아직 DB가 선정되지 않았음을 가정해, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계

추후 RDB, NoSQL 등등 다양한 저장소를 사용한다고 가정할 때 수정이 용이하도록 구현체로 가벼운 메모리 기반의 데이터 저장소 사용. (MemoryMemberRepository)

도메인 및 리포지토리

도메인은 MVC의 M에 해당된다.

  • Domain.Member
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Member {  
    private Long id;  
    private String name;  
  
 public Long getId(){  
        return id;  
  }  
  
    public void setId(Long id){  
        this.id = id;  
  }  
  
    public String getName(){  
        return name;  
  }  
  
    public void setName(String name){  
        this.name = name;  
  }  
}
  • Repository.MemberRepository (인터페이스)
1
2
3
4
5
6
public interface MemberRepository {  
    Member save(Member member);  
	Optional<Member> findById(Long id);  
    Optional<Member> findByName(String name);  
    List<Member> findAll();  
}
  • Repository.MemoryMemberRepository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MemoryMemberRepository implements MemberRepository{  
  
    private static Map<Long, Member> store = new HashMap<>();  
    private static long sequence = 0L; // key값을 생성해주는.  
  
	@Override  
	public Member save(Member member) {  
        member.setId(++sequence);  
	    store.put(member.getId(), member);  
	    return member;  
	}  
  
    @Override  
	public Optional<Member> findById(Long id) {  
        return Optional.ofNullable(store.get(id)); 
		//get(id)에서 null 값이 있을 수 있어 이럴 때 Optional.ofNullable()을 통해 null일때 활용가능.
	}  
  
    @Override  
	public Optional<Member> findByName(String name) {  
        return store.values().stream()  
                .filter(member -> member.getName().equals(name)) // name과 같은애들 filter 
                .findAny(); //하나라도 찾는다. 없으면 null 반환
	}  
  
    @Override  
	public List<Member> findAll() {  
        return new ArrayList<>(store.values());  
	}  
}

테스트케이스 작성

Main 메서드를 통해 실행하거나 웹 애플리케이션의 컨트롤러를 통해 기능을 실행할 수 있지만 실행하는데 오래걸리고, 반복 실행하기 어려워 여러 테스트를 한번에 실행하기 어렵다.

자바는 JUnit이라는 프레임워크를 통해 테스트를 실행할 수 있다.

  • test.java.Repository.MemoryMemberRepositoryTest Package 명과 자바파일 이름은 이런식으로 동일하게 하고 뒤에 Test를 붙이는 방식이다.

@Test 어노테이션을 붙여주면 해당 함수를 바로 실행 가능하다. (왼쪽 줄 수 옆 재생버튼)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class MemoryMemberRepositoryTest {  
  
    MemoryMemberRepository repository = new MemoryMemberRepository();  
  
  @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();  
	    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 활용)

@AfterEach를 활용하면 class 단위로 Test를 한번에 실행했을 때, 각 함수 실행 후 AfterEach 함수도 같이 실행한다.

  • @AfterEach 어노테이션을 통해 repository에 clear 메서드를 만든 후 각 메서드가 끝날 때 마다 호출해주어 데이터를 초기화한다.

  • Assertions.assertThat() 을 이용해 테스트 결과를 확인 할 수 있다.

** Test Code를 먼저 짜고 구현하면 TDD.

회원 서비스

  • MemberService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MemberService {  
    private final MemberRepository memberRepository;  
  
	public MemberService(MemberRepository memberRepository){  
	    this.memberRepository = memberRepository;
    }
     
    //회원가입  
	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);  
	}  
}

** MemberService 생성자를 통해 Test에서 MemberService를 테스트 할 때 같은 MemberRepository를 사용할 수 있다.

** class 드래그 하고 Ctrl + Shift + T 하면 자동으로 Test package, method 다 만들어줌.

  • Service.MemoryMemberRepositoryTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class MemberServiceTest {  
  
	//MemberService memberService = new MemberService();  
	//MemoryMemberRepository memberRepository = new MemoryMemberRepository(); 
	
	//이렇게 했을 때 memberService에서 생성한 MemoryMemberRepository랑 이 MemoryMemberRepository는 다른 객체.  
	//현재 static으로 Map이 선언되어 있기 때문에 같은 저장소를 참조할 수 있어 오류발생 X
	
    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();  
	    Assertions.assertEquals(member.getName(), findMember.getName());
	}  
  
	@Test  
	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));  
	    //memberService.join(member2)를 실행할 때 IllegalStateException 이 발생해야 한다를 검증.
		
	    assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
	}   
}
This post is licensed under CC BY 4.0 by the author.