Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 자바의 정석 기초편 ch1
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch3
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch7
- 스프링 mvc2 - 검증
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch6
- jpa - 객체지향 쿼리 언어
- 스프링 고급 - 스프링 aop
- 스프링 mvc2 - 로그인 처리
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch4
- 스프링 mvc1 - 스프링 mvc
- 게시글 목록 api
- @Aspect
- 스프링 입문(무료)
- 자바의 정석 기초편 ch9
- 스프링 mvc1 - 서블릿
- 자바의 정석 기초편 ch14
- 스프링 db1 - 스프링과 문제 해결
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch5
- 2024 정보처리기사 시나공 필기
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch8
- jpa 활용2 - api 개발 고급
Archives
- Today
- Total
나구리의 개발공부기록
회원관리 예제 - 비즈니스 요구사항 정리, 회원 도메인과 리포지토리 만들기, 테스트 케이스 작성, 회원 서비스 개발, 서비스 테스트 본문
인프런 - 스프링 완전정복 코스 로드맵/코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술(무료)
회원관리 예제 - 비즈니스 요구사항 정리, 회원 도메인과 리포지토리 만들기, 테스트 케이스 작성, 회원 서비스 개발, 서비스 테스트
소소한나구리 2024. 1. 23. 22:05비즈니스 요구사항
- 데이터 : 회원ID, 이름
- 기능: 회원 등록, 조회
- 아직 데이터 저장소(DB)가 선정되지 않음(가상 시나리오)
- 같은 이름이 있는 중복 회원은 안됨
일반적인 웹 애플리케이션 계층 구조
- 컨트롤러 : 웹 MVC 혹은 API의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현
- 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인: 비즈니스 도메인 객체 ex) 회원, 주문 등등 주로 데이터베이스에 저장하고 관리 됨
클래스 의존 관계
- 데이터 저장소가 선정되지 않아 인터페이스 작성하여 구현 클래스를 변경할 수 있도록 설계
- 개발진행을 위해 초기 개발 단계에서는 구현체로 가벼운 메모리 기반의 데이터 저장소 사용
회원 도메인과 리포지토리 만들기
- 도메인 class 작성
package start.startspring.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;
}
}
- 회원 리포지토리 작성 -> 인터페이스로 작성
package start.startspring.repository;
import start.startspring.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();
}
- 인터페이스를 구현하기 위한 클래스 작성
package start.startspring.repository;
import start.startspring.domain.Member;
import java.util.*;
public class MemoryMemberRepository implements MemberRepository {
// 실무에서는 공유되는 변수일 경우 ConcurrentHashMap을 써야하지만 예제는 단순하게 HashMap사용
private Map<Long, Member> store = new HashMap<>();
// 위와 마찬가지로 실무에서는 AtomicLong을 사용
private static long sequence = 0L;
@Override
public Member save(Member member) {
// id를 name에 연결하여 자동으로 1씩 증가 시켜 store변수의 value에 저장
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
// store의 key에 대응하는 value를 반환, null이 반환될 가능성이 있어 Optional로 감싸서 반환하도록 작성
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
// store에 저장된 모든 value값과 파라미터로 넘어온 name이랑 같은지 확인 -> 하나라도 찾으면 반환
return store.values().stream()
.filter(member -> member.getName().equals(name)).findAny();
}
@Override
public List<Member> findAll() {
// 멤버를 모두 반환
return new ArrayList<>(store.values());
}
}
회원 리포지토리 테스트 케이스 작성
- JUnit이라는 프레임 워크로 테스트를 실행 하여 아래의 문제들을 해결
- main메서드나 웹 애플리케이션의 컨트롤러를 통해 기능을 실행 할 수 있으나 번거로운점이 많음.
- 준비 -> 실행하는데 오래 걸림
- 반복 실행하기 어려움
- 여러 테스트를 한번에 실행하기 어려움
- 실무에서는 테스트케이스를 Build툴이랑 엮어서 Build할 때 테스트케이스를 통과하지 않으면 못넘어 가도록 막음
리포지토리 테스트 코드
- ~test/java/프로젝트/ 위치에 테스트 패키지 or 테스트 클래스 생성
- 테스트 코드는 각 메서드가 순서와 관계없이(의존관계없이) 설계가 되어야 함
- 하나의 테스트가 끝날 때마다 clear()할 수 있는 코드를 작성
- 테스트 코드(틀을 만듦)를 작성 후 실제 코드를 구현하는 TDD(테스트 주도 개발)로 개발도 가능함
package start.startspring.repository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import start.startspring.domain.Member;
// option + 엔터를 통해 작성된 static import문
import java.util.List;
import static org.assertj.core.api.Assertions.*;
// 클래스레벨에서 test를 하면 전체를 test할 수 있음
public class MemoryMemberRepositoryTest {
// 객체 생성
MemoryMemberRepository repository = new MemoryMemberRepository();
// 테스트가 실행 되고 끝날 때 마다 repository를 지워주는 코드를 작성
@AfterEach
public void afterEach() {
repository.clearStore();
}
@Test
public void save() {
Member member = new Member();
member.setName("Spring");
repository.save(member);
// findById메서드 호출을 하여 Optional에 반환된 값을 result에 저장
Member result = repository.findById(member.getId()).get();
//검증 1.println사용 : result의 값과 member의 값이 같은지 비교하여 출력
System.out.println("(result == member) = " + (result == member));
//검증 2. Assertions(JUnit)사용 : (기대값, 비교값) - 같으면 녹색불이 뜸
//다르면 에러메세지 뜸
Assertions.assertEquals(result, member);
// Assertions.assertEquals(result, null);
// 검증 3. (요즘 많이쓰는 방법) Assertions(org.assertj)사용 : assertThat(값1).isEqualTo(값2) 문법 사용
// option + 엔터하여 import static 문을 자동 작성 가능
assertThat(member).isEqualTo(result);
// 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();
// Member result = repository.findByName("spring2").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);
}
}
회원서비스 개발
- 회원 리포지토리와 도메인을 활용해서 실제 비즈니스 로직을 작성
- 서비스 클래스는 네이밍을 비즈니스에 의존적으로 네이밍(설계)를 해야 추후 매칭이 잘됨
- 그에 반에 리포지토리는 단순하게 용어를 선택
package start.startspring.service;
import start.startspring.domain.Member;
import start.startspring.repository.MemberRepository;
import start.startspring.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
// ifPresent() (Optional에서 사용할 수 있는 메서드) : 값이 있으면 아래의 로직을 수행
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);
}
}
회원서비스 테스트 코드
package start.startspring.service;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import start.startspring.domain.Member;
import start.startspring.repository.MemoryMemberRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
class MemberServiceTest {
// Dependency Injection (DI)
MemberService memberService;
MemoryMemberRepository memberRepository;
@BeforeEach
public void beforeEach(){
memberRepository = new MemoryMemberRepository();
memberService = new MemberService(memberRepository);
}
@AfterEach
public void afterEach() {
memberRepository.clearStore();
}
// 테스트는 한글로 작성해도 무방함
// given - when - then 문법으로 주석을 달고 테스트코드 작성을 권장
@Test
void 회원가입() {
//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
// assertThrows 활용
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다");
// try - catch 문을 활용
/* memberService.join(member1);
try {
memberService.join(member2);
fail();
} catch(IllegalStateException e) {
// 중복이 되었을 때 나오는 문장이 같은지 체크
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다");
}*/
//then
}
@Test
void findMembers() {
}
@Test
void findOne() {
}
}
PS.
- Command+Option+V 를 누르면 자동으로 return
출처 : 인프런 - 스프링 입문(무료) / 김영한님
'인프런 - 스프링 완전정복 코스 로드맵 > 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술(무료)' 카테고리의 다른 글
스프링 DB 접근 기술 2 - 스프링 JdbcTemplate, JPA, 스프링 데이터 JPA / AOP - AOP가 필요한 상황, AOP적용 (1) | 2024.01.24 |
---|---|
스프링 DB 접근 기술 1 - H2데이터베이스 설치, 순수JDBC, 스프링 통합 테스트 (0) | 2024.01.24 |
스프링 빈과 의존관계 - 컴포넌트스캔과 자동 의존관계 설정, 자바 코드로 직접 스프링 빈 등록 / 회원 관리 예제 - 웹 MVC개발(홈 화면 추가, 회원등록, 회원조회) (1) | 2024.01.24 |
스프링 웹 개발 기초 - 정적 콘텐츠, MVC와 템플릿 엔진, API (2) | 2024.01.23 |
프로젝트 환경설정 - 프로젝트생성, 라이브러리 탐색, View환경설정, 빌드 및 실행 (1) | 2024.01.22 |