출처 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한)
컨트롤러: 웹 MVC의 컨트롤러 역할, API 생성 시 컨트롤러 역할
서비스 : 서비스 클래스의 핵심 비즈니스 로직이 들어가 있음 (ex. 회원 가입 중복 불가)
도메인 : DB에 주로 저장되고 관리되는 (ex. 회원, 주문, 쿠폰 ...) 비즈니스 도메인 객체
리포지토리 : DB에 접근, 도메인 객체를 DB에 저장하고 관리
비즈니스 도메인 객체를 가지고 핵심 비즈니스 로직이 돌아가도록 구현한 계층
클래스 의존관계
출처 : 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 (김영한)
아직 DB가 선정되지 않음 → 인터페이스로 설계 (회원 리포지토리에) → 메모리 구현체로 생성
메모리 구현체 : 비교적 간단하게 생성 가능, 메모리에 삽입 삭제 가능 (가벼운 메모리 기반 데이터 저장소)
구현체 = 인터페이스를 구현한 클래스
[interface MemberRepository] -- [class MemoryMemberRepository implements MemberRepository] 가 있을 때, MemoryMemberRepository 는 MemberRepository 인터페이스를 구현한 구현체
추후에 DB가 선정되면 바꿔끼우기 위해 인터페이스가 필요함 (구현 클래스 변경 가능)
확장에 대한 요구사항 (변경에 닫혀있고, 확장엔 열려있게 설계) → 객체간 느슨한 결합 필요 ⇒ 인터페이스
2) 회원 도메인과 리포지토리 만들기
1. 회원 객체 생성
domain 패키지 생성 -> Member 클래스 정의
- 맴버 변수로 id, name 선언
- 이때 id는 시스템에서 데이터 식별 위해 임의로 부여하는 숫자
2. 회원 리포지토리 인터페이스 생성
4가지 기능 구현 in repository
save: 회원을 저장소에 저장하는 기능.
findById: save로 저장된 회원 정보를 id로 찾아서 반환하는 기능
findByName : save로 저장된 회원 정보를 name으로 찾아서 반환하는 기능
findAll : 지금까지 저장된 회원 리스트를 전부 반환
Optional
Java 8에 들어가는 기능
null 반환 시, null을 반환하는 방식 중 하나
null을 그대로 반환하는게 아닌, 포장해서 반환하는 기능
3. 회원 리포지토리 메모리 구현체 생성
Alt + Shift + Enter를 통해 implements가 손쉽게 가능하다.
실무에서는 동시성 문제가 발생 가능함 ➔ 공유 되는 변수일 경우 HashMap 대신 ConcurrentHashMap 사용해야 함 ➔ 마찬가지로 sequence에서 long 대신 atumnlong 등으로 대체 필요
Optional.ofNullable
- null값이 return될 경우 감싸서 반환 해줌 → Client에서 뭐 해줌(?)
Stream 생성 → 필터 (조건에 부합하는 데이터 거름) → 찾음 (findAny() = 병렬처리 때 먼저 찾아진 요소 반환)
⇒ 하나 찾는 순간 바로 반환 , (if: 다 돌렸는데도 일치하는게 없음 = Optional 에 null이 포함 돼 반환)
Assertions (assertj)의 assertThat(A).isEqualTo(B) 메서드 이용
A가 B랑 일치하는지 확인 ∴ A = 비교할 것 / B = 비교 대상
✔ Alt + Enter ⇒ static import 가능 (이후 assertThat만 바로 이용 가능)
==> 빌드 툴에서 빌드시, 테스트 케이스를 통과하지 않으면 다음단계로 넘어가는 것이 방지됨
Ⅱ) findByName 메서드 정상 동작 테스트
1. 테스트 할 findByName 메서드 생성
2. 비교할 객체 member1, member2 생성 후 name할당 → repository에 저장
✔ Rename 단축키 = Shift + F6
3. 검증하기 - assertion.assertThat()
result(="spring1"이 name인 member) 과 member1이 동일
✔만약 비교 대상이 같지 않다면("spring2"로 findByName 실행하므로 member2 저장)
result(=member2) =/= member1
⁂ Test case의 장점 - (1) save, (2) findById 를 동시에 테스트 가능 : 한번에 여러 메서드 동시에 테스트 가능 (Test클래스 레벨에서 Run하거나, Run Tests in practice~ 에서 실행하면 전체 class 테스트 가능) test/.../ repository 패키지 우클릭 > Run Tests~ 실행 시 하위 클래스 한번에 테스트 진행한꺼번에 테스트가 가능하다
반응형
Ⅲ) findAll 메서드 정상 동작 테스트
✔ 저장된 전체 회원객체를 불러와 List에 저장하고, 그 길이를 비교함으로써 검증 가능
✔ 기준 변수 추출 단축키 = Ctrl + Alt + v
But 다시 테스트 클래스에 대해 Run 할 경우 에러 발생
➔ member의 name이 중복 되기 때문! ("spring1", "spring2")
테스트 메서드는 순서 보장 X (∵ 모든 메서드 케이스는 생성 순서 상관없이 메서드 별로 병렬 수행 처리됨)
각 메서드 테스트 실행 후 repository를 주기적으로 초기화 시켜줄 메서드인 AfterEach에 대해 정의해야 함! (Ex. findAll() >> AfterEach() >> findByName() >> AfterEach() >> save() 순서대로 실행) + MemoryMemberRepository 클래스에 clearStore 메서드 정의 ⇒ 메모리(store) 초기화 기능 추가
이후 실행 시켜주면 오류 없이 작동한다.
♘ 테스트 케이스 작성시 유의할 점 - 테스트는 서로 의존관계없이 설계돼야 함 ∴ 테스트가 끝날 때마다 저장소 or 전역 변수 (=공유 데이터)를 초기화 해주는 메서드 필수
+)
테스트 케이스(=틀)을 먼저 생성한 뒤, 거기에 맞는지 테스트 해가며 진행하는 개발 방식도 존재