비지니스 요구사항
- 비지니스 요구사항을 정리하여 데이터, 기능 들을 확립 하는 것이 중요하다.
- 일반적인 웹 애플리케이션 계층 구조
- 컨트롤러 : 웹 MVC 의 컨트롤러 역할
- 서비스 : 핵심 비지니스 오직 구현
- 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인 : 비지니스 도메인 객체
- 클래스 의존 관계
- 아직 데이터 저장소가 선정 되지 않았을 때, 인터페이스로 구현 클래스로 변경 할 수 있도록 설계한다.
- 데이터 저장소는 RDBMS, NoSQL 등의 저장소를 고민
- 개발 초기 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사한다.
회원 도메인과 리포지토리 만들기
- 해당 강의의 실습을 진행하였음
- 회원 도메인을 만들어주기 위해서 우선 main/java/ 안에 domain 이라는 package를 생성
- domain 안에 Member 클래스 생성
public class Member {
private Long id;
private String 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;
}
}
- main/java 안에 repository 라는 package 생성
- repository 안에 MemberRepository 라는 interface 생성
import com.example.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();
}
- 같은 디렉토리에서 MemeoryMemverRepository 라는 클래스 생성
import com.example.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) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
회원 도메인과 리포지토리 테스트 케이스 작성
- 개발한 기능을 실행해서 테스트 할 떄, 자바의 main메소드로 실행해보거나 , 웹 애플리케이션의 컨트롤러를 통해서 해당기능을 확인 한다.
- 이러한 방법든은 준비 + 실행하는데 오래 걸리고 반복 실행이 어려움
- Junit이라는 프레임워크로 테스트를 실행해서 위 문제를 해결할 수 있음
- test 디렉토리 안에 프로젝트명 안에 repository package 생성하고 [테스트할파일명Test] 라는 이름으로 만들어줌
import com.example.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
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.assertThat(member).isEqualTo(null);
}
@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();
Assertions.assertThat(result).isEqualTo(member1);
}
@Test
public void finaAll(){
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);
}
}
- 테스트 하고자 하는 함수 하나하나 추가해주어 맞는지 틀린지를 확인
- @afterEach 태크를 통해 함수 하나가 끝날때 마다 실행하게 만듬
- test 할 내용이 너무 많을 때
위와 같이 실행시키면 모든 함수에 대해서 Test가 가능함
회원 서비스 개발
- 서비스를 개발하는 방식에 대한 설명
import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;
import com.example.hellospring.repository.MemoryMemberRepository;
import java.util.*;
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 -> {
throw new IllegalStateException("이미 존재하는 회원입니다");
});
}
/**
* 전체 회원 조회
*/
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Optional<Member> findOne(Long memberId){
return memberRepository.findById(memberId);
}
}
위 코드를 main/java/[프로젝트 명] 안에 service 라는 package를 만들어 주고 MemberService라는 class를 만들어서 넣어줌
- 회원 가입 : join 함수이며 안에 같은 이름이 있는 중복 회원은 안되게끔 구현
- 전체 회원 조회 : findMembers 함수이며 memberrepository 안에 있는 데이터 전부를 list 안에 담겨서 받음
- 한명의 회원 검색 : findOne 함수이며 memberId를 통해서 한명의 회원을 찾음
위 와 같이 구현한 것들을 전시간에 배웠던 testcase를 만들어서 test해보는 방식으로 디버깅
회원 서비스 테스트
- 만들어 줬던 MemberService 클래스를 에서 cmd+shift + t 를 누르면
위와 같은 화면이 뜬다. test하고자 하는 함수를 다 선택하고 OK 를 눌러주면
해당 디렉토리의 testcase가 만들어짐
import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemoryMemberRepository;
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 static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach(){
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
@Test
void 회원가입() {
//given
Member member = new Member();
member.setName("hello");
//when
Long saveId = memberService.join(member);
//then
Member findMember = memberService.findOne(saveId).get();
Assertions.assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public 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));
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다");
// try{
// memberService.join(member2);
// fail();
// }catch (IllegalStateException e){
// Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다");
// }
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
- test case를 만들때는 given(주어졌을 때) -> when (이런 상황이면)-> then(이런 결과 값)이 중요
- 현재 한국말로 된 함수명은 한국말로 해도 상관이 없다는 걸 보여주기 위함
- @beforeEach를 통해 실행하기전에 실행되게끔 만들어준다.
MemberService 안에 MemberRepository 를 MemberService를 만들때 안에 넣어주는 형식으로 매번 새로운 repository를 생성할 필요가 없게 만듦
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
//본인이 생각했을 때, 디자인 패턴 (싱글턴 패턴)을 응용한 것이 아닌가 싶습니다.
- 회원가입을 test하는 과정에서 중복 회원 또한 검사해봐야함으로 이에 대한 함수도 만들어 주었음
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다");
---------------------------------------------
try{
memberService.join(member2);
fail();
}catch (IllegalStateException e){
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다");
}
- 위 두개의 코드는 같은 동작을 하는 코드임 assertThrow함수가 어떻게 사용되는지를 보여주신 사례
- // assertions에 대해서 공부가 필요할 듯 싶다.
'Spring' 카테고리의 다른 글
5장 회원관리예제 - 웹 MVC 개발 (0) | 2023.08.29 |
---|---|
4장 스프링 빈과 의존관계 (0) | 2023.08.25 |
제 2장 정적 컨텐츠, MVC, API (0) | 2023.08.21 |
1장 -3 View 환경설정 + Build 하기 (0) | 2023.08.18 |
1장-2. Spring 라이브러리, Dependencies (0) | 2023.08.18 |