1 비지니스 요구사항 정리
(1) 비지니스 요구사항
- 데이터 : 회원ID, 이름
- 기능 : 회원 등록, 조회
- 아직 데이터 저장소는 선정되지 않은 상태
(2) 일반적인 웹 애플리케이션 계층 구조
- 컨트롤러 : 웹 MVC의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현
- 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인 : 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리
(3) 클래스 의존관계
- 아직 데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
- 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황으로 가정
- 개발을 진행하기 위해서 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
2 회원 도메인과 리포지토리 만들기
(1) 파일 구조
(2) 소스코드
- Member.jave
package jeong.jeongspring.domain;
public class Member {
private Long id; // 시스템 상에서 고객에게 부여하는 Number
private String name; // 고객에 회원가입 시에 입력하는 Name
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
- MemberRepository.java
public interface MemberRepository {
Member save(Member member);
/* Oprionnal은 null일 때 감싸서 반환하는 객체 */
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
- MemoryMemberRepository.java
/* MemberRepository를 implements하여 기존의 메소드를 모두 불러옴 */
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
/* 회원 저장 */
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
/* 시스템 상에서 부여한 회원 id 찾기 */
public Optional<Member> findById(Long id) {
/* Opfional로 감싸서 반환하면 클라이언트에서 조작 가능 */
return Optional.ofNullable(store.get(id));
}
@Override
/* 회원이 직접 입력한 name 찾기 */
public Optional<Member> findByName(String name) {
/* findAny()를 통해 하나라도 찾으면 반환하도록 */
return store.values().stream()
.filter(member -> member.getName().equals(name)).findAny();
}
@Override
/* 지금까지 저장된 회원 List 형태로 불러오기 */
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
3 회원 리포지토리 테스트 케이스 작성
(1) 테스트 케이스
- 내가 작성한 리포지토리가 정상적으로 작동하는지 테스트하는 코드 작성
- JUnit : 여러 테스트를 한 번에 실행할 수 있는 프레임워크
(2) MemoryMemberRepositoryTest_1
- 파일 구조
- MemoryMemberRepositoryTest.java
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
System.out.println("result = " + (result == member));
}
}
- save() 함수 실행 결과 > 항상 이런식으로 실행결과 확인할 수 없어 !
(3) MemoryMemberRepositoryTest_2
- MemoryMemberReposotoryTest.java
> @AfterEach를 사용하면 각 테스트 종료 시에 이 함수를 실행하므로 순서에 의존적이지 않음
> 각각의 테스트는 독립적으로 실행되어야 하며 순서에 의존적이지 않아야 함
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();
/* Assertions로 변경하여 실행하면 출력은 없으나 참일 경우 실행이 정상적으로 됨 */
// Assertions.assertEquals(member, result);
/* 최근에는 assertThat을 더 많이 사용함 */
Assertions.assertThat(member).isEqualTo(result);
}
@Test
public void findByName() {
Member member3 = new Member();
member3.setName("spring3");
repository.save(member3);
Member member4 = new Member();
member4.setName("spring4");
repository.save(member4);
/* name으로 Member 객체를 get 해와서 동일한 객체인지 확인 */
Member result = repository.findByName("spring4").get();
Assertions.assertThat(result).isEqualTo(member4);
}
@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();
Assertions.assertThat(result.size()).isEqualTo(2);
}
}
- Memory MemverRepository.java에 clearStore() 추가
/* store 비우기 */
public void clearStore() {
store.clear();
}
- 실행
4 회원 서비스 개발
package jeong.jeongspring.service;
import jeong.jeongspring.domain.Member;
import jeong.jeongspring.repository.MemberRepository;
import jeong.jeongspring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
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 -> { // ifPresent는 값이 있을 경우에 동작
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
/* 전체 회원 조회 */
public List<Member> findMembers() {
return memberRepository.findAll();
}
/* Id로 회원 조희 */
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}
5 회원 서비스 테스트
- 파일 구조
- Given-When-Then 구조로 설계하자 !
- assertThrows(발생할 오류, () -> 이 로직을 수행할 때)
- MemberService.java 수정
public class MemberService {
private final MemberRepository memberRepository;
/* memberRepository를 직접 생성하는 것이 아니라 외부에서 넣어주도록 설정 */
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- MemberServiceTest.java
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
/* 테스트 하기 전에 리포지토리를 MemoryMemberRepository생성하여 MemberService에 넣어줌 */
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
/* 테스트가 끝나고 나면 리포지토리를 깨끗하게 지워줌 */
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
@Test
public void 회원가입() throws Exception {
//Given
Member member = new Member();
member.setName("hello");
//When
Long saveId = memberService.join(member);
//Then(검증)
Member findMember = memberRepository.findById(saveId).get();
assertEquals(member.getName(), findMember.getName());
}
@Test
public void 중복_회원_예외() throws Exception {
/* name 중복 회원 저장을 시도 */
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
memberService.join(member1);
/* 오류 발생 */
/* memberService.join(member2) 로직을 수행할 때 > IllegalStateException.class 오류가 발생 */
IllegalStateException e = assertThrows(IllegalStateException.class,
() -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
- MemberServiceTest 실행