이 게시글은 [인프런]스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의를 기반으로 작성되었으며 강의를 기억하기 위한 기록물입니다. 순수하게 복습하기 위한 용도로 작성되었음을 미리 밝힙니다.
회원 관리 예제 - 백엔드 개발
비즈니스 요구사항 정리
학습을 위한 것이므로 단순한 비즈니스 요구사항을 전제로 한다.
- 데이터 : 회원ID, 이름
- 기능 : 회원 등록, 조회
- 데이터 저장소(DB)가 선정되지 않은 가상의 시나리오
- 일반적인 웹 애플리케이션 계층구조
- 컨트롤러 : 웹 MVC의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현
- 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인 : 비즈니스 도메인 객체, 예)회원, 주문, 쿠폰 등 DB에 저장하고 관리
- 클래스 의존 관계
- 아직 데이터 저장소가 선정되지 않아서, 인터페이스로 구현 클래스를 변경할 수 있도록 설계
- 데이터 저장소는 RDB, NoSQL 등등 다양한 저장소를 고민중인 상황 가정
- 개발을 진행하기 위해서 초기 개발단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
회원 도메인과 리포지토리 만들기
//src\main\java\eyijin\hellospring\domain\Member.java
package eyijin.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;
}
}
//src\main\java\eyijin\hellospring\repository\MemberRepository.java
package eyijin.hellospring.repository;
import eyijin.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();
}
//src\main\java\eyijin\hellospring\repository\MemoryMemberRepository.java
package eyijin.hellospring.repository;
import eyijin.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());
}
}
회원 리포지토리 테스트 케이스 작성
- 모든 테스트는 순서에 상관없이 메소드 별로 따로 동작하게 설계해야함
- @AfterEach : 하나의 메소드의 동작이 끝날 때마다 실행하게 되는 메소드, 여기에서는 저장소에 저장된 데이터를 삭제하는 역할을 한다.(한번에 여러개의 테스트를 진행하다보면 db에 직전 테스트 결과가 남아 있을 수 있는데, 이 결과는 다음 테스트에 영향을 줄 수도 있기 때문에 저장소 청소를 해줘야한다.)
- 테스트 주도 개발(TDD) : 테스트 클래스를 먼저 작성한 뒤 구현 클래스를 작성할 수도 있다.(틀을 미리 작성하는 것)
- given - when - then : 뭔가가 주어지면서(데이터) 이걸 실행했을때(검증 해야할 것) 결과가 이게 나와야함(검증하는 부분)
회원 서비스 개발
- repository : 단순 개발스러운 용어들 사용함, 저장소에 넣어 빼 이런것들
- service : 서비스 로직에 가까운 내용들을 넣어야함, 비즈니스 로직을 처리해야함
//src\main\java\eyijin\hellospring\service\MemberService.java
package eyijin.hellospring.service;
import eyijin.hellospring.domain.Member;
import eyijin.hellospring.repository.MemberRepository;
import eyijin.hellospring.repository.MemoryMemberRepository;
import java.util.List;
import java.util.Optional;
public class MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
/**
* 회원가입
*/
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);
}
}
회원 서비스 테스트
테스트 코드는 한글로 적기도 합니다.
한글로 작성하면, 좀 더 직관적으로 이해할 수 있습니다.
//src\test\java\eyijin\hellospring\service\MemberServiceTest.java
package eyijin.hellospring.service;
import eyijin.hellospring.domain.Member;
import eyijin.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
class MemberServiceTest {
MemberService memberService = new MemberService();
MemoryMemberRepository memoryMemberRepository = new MemoryMemberRepository();
@AfterEach
public void afterEach(){
memoryMemberRepository.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());
}
@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));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// try {
// memberService.join(member2);
// fail();
// }catch (IllegalStateException e){
// assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// }
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
MemoryMemberRepository 의 중복 해결 → 의존성 주입(DI)
위의 코드와 같이 사용하면, MemoryMemberRepository 의 사용이 중복되어서 문제가 발생할 수 있습니다. 물론 현재의 코드에서 MemoryMemberRepository
에서 static map을 사용하기 때문에 당장 문제가 발생하지는 않으나, static을 이용하지 않을 경우 문제가 생길 수 있습니다. 따라서 아래와 같이 코드를 변경해줍니다.
//src\main\java\eyijin\hellospring\service\MemberService.java
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
/**
* 회원가입
*/
//...
}
//src\test\java\eyijin\hellospring\service\MemberServiceTest.java
package eyijin.hellospring.service;
import eyijin.hellospring.domain.Member;
import eyijin.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 java.util.Optional;
import static org.assertj.core.api.Assertions.*;
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 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());
}
@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));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// try {
// memberService.join(member2);
// fail();
// }catch (IllegalStateException e){
// assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// }
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
'Backend > Spring' 카테고리의 다른 글
JPA vs MyBatis (0) | 2023.07.09 |
---|---|
[Spring] 회원 관리 예제 - 웹MVC 개발 (0) | 2022.07.02 |
[Spring] 스프링 빈과 의존관계 (0) | 2022.07.01 |
[Spring] 스프링 웹 개발 기초 (0) | 2022.06.25 |
[Spring] 프로젝트 환경설정 (0) | 2022.06.24 |
댓글