[Spring Introduction] 3. 회원 관리 예제 - 백엔드 개발
[1] 비즈니스 요구사항 정리
데이터: 회원 ID, 이름
기능: 회원 등록/조회
아직 데이터 저장소가 선정되지 않음 (가상 시나리오)
일반적인 웹 애플리케이션 계층 구조
데이터: 회원 ID, 이름
기능: 회원 등록/조회
아직 데이터 저장소가 선정되지 않음 (가상 시나리오)
- 컨트롤러: 웹 MVC의 컨트롤러 역할
- 서비스: 핵심 비즈니스 로직 구현 (ex. 회원 중복 타입 X)
- 리포지토리: 데이터베이스에 접근, 도메임 객체를 데이터베이스에 저장하고 관리
- 도메인: 비즈니스 도메인 객체 (ex. 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리)
- 아직 DB가 결정되지 않았기 때문에 인터페이스로 해놓고 추후 구현 클래스를 변경할 수 있도록 설계
- 데이터 저장소는 RDB, NoSQL 등 다양한 저장소를 염두해둠
- 개발을 진행하기 위해서 초기 개발 단계에선 구현체로 가벼운 메모리 기반 데이터 저장소 사용할 것
[2] 회원 도메인과 리포지토리 만들기
Optional<>
: 비어있다면 null이 반환되는데 이때 optional로 감싸 반환되는 것을 선호함
✔️ MemoryMemberRepository.java 주석참조
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); // map에 저장시킴
return member;
}
@Override
public Optional<Member> findById(Long id) { // store에서 꺼내오면 됨
return Optional.ofNullable(store.get(id)); // null이어도 감쌀 수 있음
// 감싸서 반환해주면 클라이언트가 뭘 할 수가 있어짐
}
@Override
public Optional<Member> findByName(String name) {
// 루프를 돌면서 찾으면 반환해주고 없으면 optional에 null을 감싸 반환
return store.values().stream()
.filter(member -> member.getName().equals(name)) // 파라미터로 넘어온 이름과 같은지 확인
.findAny(); // 그리고 찾으면 반환해주는
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
}
[3] 회원 리포지토리 테스트 케이스 작성
JUnit이라는 프레임워크로 테스트를 실행해 여러 테스트를 한 번에 실행
✔️ MemoryMemberRepository.java 주석참조
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); // map에 저장시킴
return member;
}
@Override
public Optional<Member> findById(Long id) { // store에서 꺼내오면 됨
return Optional.ofNullable(store.get(id)); // null이어도 감쌀 수 있음
// 감싸서 반환해주면 클라이언트가 뭘 할 수가 있어짐
}
@Override
public Optional<Member> findByName(String name) {
// 루프를 돌면서 찾으면 반환해주고 없으면 optional에 null을 감싸 반환
return store.values().stream()
.filter(member -> member.getName().equals(name)) // 파라미터로 넘어온 이름과 같은지 확인
.findAny(); // 그리고 찾으면 반환해주는
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore() {
store.clear();
}
}
이런 에러를 방지하기 위해 Aftereach
메소드 작성
(테스트가 하나 끝나면 데이터를 clean 시켜주도록 함)
모든 테스트는 동작 순서가 보장되지 않기 때문에 순서에 의존해 설계하면 절대 안됨.
@AfterEach
: 한 번에 여러 테스트를 실행하면 메모리 DB에 직전 테스트 결과가 남아있을 수 있다. 그럼 그 잔여물 때문에 다음 진행할 테스트에 실패할 수 있다.
@AfterEach
를 사용하면 각 테스트가 종료될 때마다 해당 메소드를 실행한다. 이 예제에서는 DB를 비워주었기 때문에 다음 테스트도 성공할 수 있었다.
[4] 회원 서비스 개발
command + option + m
: 메소드로 뽑아내기
✔️ MemberService.java 주석참조
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.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) {
// optional이기 때문에 ifPresent 이런 값도 사용할 수 있는 것. (null이라면 이런 문을 안써도 됨)
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);
}
}
[5] 회원 서비스 테스트
테스트 메소드 이름은 과감하게 한글로 해도 됨.
그리고 given
, when
, then
을 지켜서 짜는 것을 추천
테스트의 핵심은 예외잡기. 예외상황 발생시 잘 동작하는지를 확인하는 것이 중요함
command + option + m
: 메소드로 뽑아내기
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.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) {
// optional이기 때문에 ifPresent 이런 값도 사용할 수 있는 것. (null이라면 이런 문을 안써도 됨)
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);
}
}
테스트 메소드 이름은 과감하게 한글로 해도 됨.
그리고
given
,when
,then
을 지켜서 짜는 것을 추천테스트의 핵심은 예외잡기. 예외상황 발생시 잘 동작하는지를 확인하는 것이 중요함
기존에는 회원 서비스가 메모리 회원 리포지토리를 직접 생성하도록 했으나 회원 리포지토리 소스가 회원 서비스 소스를 DI 가능하도록 변경했다.
@BeforeEach
: 각 테스트 실행 전 호출
테스트가 서로 영향받지 않도록 항상 새로운 객체를 생성하고 의존 관계를 새로 맺어줌.
Author And Source
이 문제에 관하여([Spring Introduction] 3. 회원 관리 예제 - 백엔드 개발), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dbsrud11/Spring-Introduction-3.-회원-관리-예제-백엔드-개발저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)