1. 비지니스 요구사항 정리
- 데이터: 회원ID, 이름
- 기능: 회원 등록, 조회
- 아직 데이터 저장소(DB)가 선정되지 않음(가상의 시나리오)
컨트롤러: 웹 MVC의 컨트롤러 역할
서비스: 서비스의 핵심 비지니스 로직이 들어있음 (ex.로그인 횟수제한)
리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
도메인: 비지니스 도메인 객체 (ex. 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨)
데이터 저장소가 선정되지 않아서, 우선 인터페이스로 구현 클래스를 변경할 수 있도록 설계
(데이터 저장소는 RDB, NoSQL 등 고민중인 상황으로 가정)
초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
2. 회원 도메인과 리포지토리 만들기
//멤버클래스
package hello.hellospring.domain;
public class member {
private Long id;
private String name;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setId(Long id) {
this.id = id;
}
}
//멤버Repository 인터페이스
package hello.hellospring.repository;
import hello.hellospring.domain.member;
import java.util.List;
import java.util.Optional;
public interface MemberRepository {
member save(member member);
Optional<member> findById(Long id);
Optional<member> findByName(String name);
List<member> findAll();
}
package hello.hellospring.repository;
import hello.hellospring.domain.member;
import java.util.*;
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
public Optional<member> findById(Long id) {
return Optional.ofNullable(store.get(id)); //널이 반환됐을 떄도 처리 가능
}
@Override
public Optional<member> findByName(String name) { //null이 나와도 처리가능!
return store.values().stream()
.filter(member->member.getName().equals(name)) //쭉 돌면서 같은 이름이 있나 찾기
.findAny(); //하나라도 찾으면
}
@Override
public List<member> findAll() { //실제 실수에서 리스트 많이 씀
return new ArrayList<>(store.values());
}
}
3. 회원 리포지토리 테스트 케이스 작성
test라는 폴더 안에 데스트 하기 위한 class를 만들어서 테스트를 진행한다.
package hello.hellospring.repository;
import hello.hellospring.domain.member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Member;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach //한 테스트 클래스 끝날때마다 실행됨
public void afterEach(){
repository.clearSore();
}
@Test
public void save(){
member member = new member();
member.setName("spring");
repository.save(member);
member result = repository.findById(member.getId()).get();
//optional에서 값을 꺼낼때는 get으로 꺼낼 수 있음.
//메모리에서 꺼낸것과 저장한 값이 똑같다면 참
Assertions.assertEquals(result,member);
org.assertj.core.api.Assertions.assertThat(member).isEqualTo(result)
//정산실행되면 참 빨간불뜨면서 에러 뜨면 거짓
}
@Test
public void findByName(){
member member1 = new member();
member1.setName("spring1");
repository.save(member1);
//shift+F6으로 한번에 이름바꾸기 가능
member member2 = new member();
member2.setName("spring2");
repository.save(member2);
member result = repository.findByName("spring1").get();
org.assertj.core.api.Assertions.assertThat(result).isEqualTo(member1);
}
@Test
public void findAll(){
member member1 = new member();
member1.setName("spring1");
repository.save(member1);
//shift+F6으로 한번에 이름바꾸기 가능
member member2 = new member();
member2.setName("spring2");
repository.save(member2);
List<member> result= repository.findAll();
org.assertj.core.api.Assertions.assertThat(result.size()).isEqualTo(2);
}
}
Assertions에는 편리한 기능들이 들어있다.
이를 이용하면 출력값을 하나하나 비교해 볼 필요가 없고 정상실행되는지 안되는지만 확인하면된다.
Assertions.assertEquals(result,member);
org.assertj.core.api.Assertions.assertThat(member).isEqualTo(result)
위 두 코드는 두개의 값이 값은지 비교해주는 두가지 방법이다.
findAll과같은 함수는 size가 맞는지 비교해봄으로써 테스트를 할 수 있다.
4. 회원 서비스 개발
회원 서비스는 회원 repository와 도메인을 이용하여 실제 로직을 짜는것이다.
회원 중복확인을 하는 과정에서 findByName함수를 사용하게되는데, 이 함수의 리턴타입은 Optional로 감싸져있다.
그렇기 때문에 이 isPresent와 같은 편리한 함수를 사용할수있다.
package hello.hellospring.service;
import hello.hellospring.domain.member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.IllformedLocaleException;
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 -> { //optional를 사용했기 때문에 사용가능
throw new IllegalStateException("이미 존재하는 회원입니다. ");
});
}
//전체회원조회
public List<member> findMembers(){
return memberRepository.findAll();
}
//회원검색
public Optional<member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
서비스 파드는 그전 파드보다 훨씬 실무적인 느낌이 난다.
이렇게 하는것이 좋다
5. 회원 서비스 테스트
테스트하고싶은 클래스를 켜둔 상태에서 Ctrl+Shift+T 를 입력하면 손쉽게 테스트케이스를 만들 수 있다.
테스트는 함수이름을 한글로 해도된다. (영어권사람들이랑 같이쓰는게 아니라면)
빌드할 때 테스트는 포함되지도 않기 때문데 보기좋게 한글로 해도된다.
given - when - then 형식으로 으로 짜는것을 추천!
테스트는 정상작동을 확인하는 것도 중요하지만
예외처리를 잘 하고 있는가를 확인하는것이 더 중요!
*shift + f10 : 전에 쓰던 페이지로 이동
테스트 코드를 만들다보면 객체안에서 만들 객체와 내가 사용하는 객체가 같은 클래스이지만
객체는 달라서 원하는대로 움직이지 않을 수 있다.
그렇기 때문에 객체 안에서 객체를 만들때 새로 new 해주지 않고 외부에서 만들어진 것을 활용하도록 할 수 있다.
이 방법을 DI(Dependency Injection)라고한다.
package hello.hellospring.service;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import hello.hellospring.domain.member;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void BeforeEach(){ //DI:직접 new 안하고 외부에서 넣어주는 방법 Di
memberRepository = new MemoryMemberRepository();
memberService=new MemberService(memberRepository);
}
@AfterEach //한 테스트 클래스 끝날때마다 실행됨
public void afterEach(){
memberRepository.clearSore();
}
@Test
void 회원가입() { //한글로해도됨
//회원가입을 한 회원과 리포지토리에 저장된 회원이 같은가?를 검증
//given
member member=new member();
member.setName("hello");
//when
Long saveId=memberService.join(member);
//then
member findMember=memberService.findOne(saveId).get();
org.assertj.core.api.Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void 중복_회원_예외(){
//테스트를 여러번하면 데이터가 계속 누적되기 때문에 두번쨰 시도에서는 첫번째 회원가입도 막힘!
//given
member member1=new member();
member1.setName("spring");
//then
member member2=new member();
member2.setName("spring");
//when
memberService.join(member1);
//예외처리 좀 더 쉬운 문법, 오른쪽 인자를 실행했을 때 왼쪽 인자의 예외가 터져야한다.
IllegalStateException e= assertThrows(IllegalStateException.class,()->memberService.join(member2)); //예외처리검증
org.assertj.core.api.Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다. "); //메세지검증
/*
try{
memberService.join(member2);
fail("예외가 발생해야 합니다.");
}catch(IllegalStateException e){
org.assertj.core.api.Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다. ");
}
*/
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
이 과정에서 MemberService의 리포지토리 선언부분이 아래와 같이 바뀌어야한다.
private final MemberRepository memberRepository;
//외부에서 레포지토리를 넣어줄 수 잇도록
public MemberService(MemberRepository memberRepository){
this.memberRepository=memberRepository;
}
'공부 > Spring - 김영한' 카테고리의 다른 글
[ spring ] 예제 - 회원관리 : 웹 MVC 개발 (0) | 2023.01.26 |
---|---|
[ spring ] 스프링 빈과 의존관계, 예제 - 회원관리 : 의존관계 설정 (0) | 2023.01.19 |
[ Spring ] 스프링 웹 개발 기초 : 정적컨텐츠, MVC와 템플릿엔진, API (0) | 2022.08.07 |
[ Spring ] Welcome Page 만들기 및 컴파일 (0) | 2022.08.06 |
[ Spring ] 환경설정 (0) | 2022.07.30 |