비즈니스 요구사항 ( 최대한 간단히 개발 )
- 데이터: id, name
- 기능: 회원가입, 조회
- 아직 어떤 db를 쓸 지 정하지 않았음 -> interface로 구현
일반적인 웹 어플리케이션 계층 구조
- 도메인: db에 저장할 객체
- 리포지토리: db에 접근, 도메인 객체를 db에 저장하고 관리
- 서비스: 비즈니스 기능 로직(비즈니스 요구사항에서 요구한)
- 컨트롤러: MVC의 Controller
[main 코드 작성]
1. domain/Member
package hello.helloSpring.domain;
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;
}
}
DB에 저장할 멤버를 class로 구현한다.
id는 시스템에서 정해주고 name은 회원가입할 때 사용자가 입력한다.
2. repository/MemberRepository, repository/MemoryMemberRepository
//MemberRepository
package hello.helloSpring.repository;
import java.util.List;
import hello.helloSpring.domain.Member;
import java.util.Optional;
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
MemoryMemberRepository에서 사용할 메서드를 적어준다.
#MemoryMemberRepository
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) {
//get을 했을때 null일 수 있어서 Optional로 감싸고 return 한다.
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
//변수 store의 value는 Member 객체이기 때문에 다 돌면서 name과 같은 member가 있는지 확인
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
// store의 모든 member를 List로 반환
return new ArrayList<>(store.values());
}
public void clearStore(){
// store를 깨끗이 비운다~
store.clear();
}
}
여기서 꿀팁이 있다면 implements MemberRepository를 쓰고 alt + Enter를 누르고 Implement methods를 누르면
알아서 생성해야할 메서드들의 틀을 잡아준다.
3. service/MemberService
package hello.helloSpring.service;
import hello.helloSpring.domain.Member;
import hello.helloSpring.repository.MemberRepository;
import java.util.*;
public class MemberService {
private final MemberRepository memberRepository;
//Dependency Injection
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);
}
}
Dependency Injection이란?
쉽게 말하면
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
}
선언과 동시에 생성을 해주는게 아니라
public class MemberService {
private final MemberRepository memberRepository;
//Dependency Injection
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
생성자를 통해 인자로 받아서 넣어주는 방식이다.
MemberService 입장에서는 injection되는 입장이라 Dependency Injection이라고 한다.
validateDuplicateMember 메소드에서 드래그한 코드와 안한 코드가 기능적으로는 같다.
하지만 Optional로 바로 뽑아서 사용하는(드래그한 코드) 건 별로 안좋다고 한다.
따라서 체이닝 메서드를 이용하여 코드를 작성했다.
[테스트 코드 작성]
테스트 코드는 service와 repository를 테스트하기 위해 작성하였다.
테스트하기위한 클래스에 Test라는 문자열을 붙여서 이름을 짓는게 일반적이라고 한다.. 절대적인가? 하여튼!!
테스트 코드를 작성할 때, 어떤 상태가 주어지고->given(), 어떤 행동을 할 때->when(), 그러면 이런 결과가 나와야한다-> then() 를 작성하면 된다고 한다. (아래 코드 참조)
1. repository/MemoryMemberRepositoryTest
package hello.helloSpring.repository;
import hello.helloSpring.domain.Member;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
// 각 test를 마치고 항상 호출되는 메서드
@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(); //get()을 통해 Optional에서 Member를 뽑아냄
assertThat(member).isEqualTo(result);
//Assertions.assertEquals(member, result); -> static으로 import 해줘서 윗 줄처럼 사용가능
}
@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);
}
}
2. service/MemberServiceTest
package hello.helloSpring.service;
import hello.helloSpring.domain.Member;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
import hello.helloSpring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.*;
class MemberServiceTest {
MemberService memberService;
MemoryMemberRepository memberRepository;
// 각 test가 시작되기 전 호출되는 메서드
@BeforeEach
public void beforeEach() {
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
//// 각 test를 마치고 항상 호출되는 메서드
@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();
assertThat(member.getName()).isEqualTo(findMember.getName()); //member.getName()대신 member도 가능
}
@Test
public void 중복_회원_예외() { //test코드는 메서드 이름을 한글로 해도 무방하다고 한다!!
//given(같은 이름을 가진 회원이 존재한다면)
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//when(join할 때 중복 회원 검증인 validateDuplicateMember할 때 터져야 정상)
memberService.join(member1);
// 원래 exception error는 try-catch로 잡는데, 이걸 편하게 해주는게 assertThrows()
// try {
// memberService.join(member2);
// }catch (IllegalStateException e){
// assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// }
// 에러 메세지 비교를 통한 test
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join((member2)));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
IntelliJ 꿀팁 단축어
- alt + insert -> getter, setter, constructor 등 자동 생성
- ctrl + alt + m -> 메소드 추출하기
- shift + F6 -> 같은 단어 찾고 바꿀 수 있음
'Backend > Spring' 카테고리의 다른 글
[인프런 스프링 입문] 스프링 DB 접근 기술 (0) | 2021.01.09 |
---|---|
[인프런 스프링 입문] 회원 관리 예제 - 웹 MVC 개발 (0) | 2021.01.07 |
[인프런 스프링 입문] 스프링 빈과 의존관계 (0) | 2021.01.07 |
[인프런 스프링 입문] 스프링 웹 개발 기초 (0) | 2021.01.05 |
[인프런 스프링 입문] 프로젝트 환경설정 (0) | 2021.01.04 |