[Spring 핵심원리 기본편] 비즈니스 요구사항과 회원 도메인 설계/개발
by mini_min
잘 만들어진 설계는 역할과 구현의 분리가 명확하기 때문에, 변경에 유연하게 대처할 수 있다.
환경설정
Gradle 전체 설정
: 스프링 부트 스타터에서 받은 그대로 인텔리제이에서 프로젝트를 오픈한다.
IntelliJ Gradle 대신, 자바가 직접 실행하도록 설정한다.
인텔리제이 버전으로 실행하면 속도가 매우 느려서, 실행속도를 빠르게 하기 위해 아래와 같이 설정에서 자바로 변경한다.
비즈니스 요구사항과 설계
회원
- 회원 가입, 조회 가능
- 회원 등급은 일반/VIP
- 회원 데이터는 자체 DB 를 구축할 수 있고, 외부 시스템과 연동도 가능하다. (미정)
주문과 할인
- 회원은 상품을 주문한다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 나중에 변경 가능하다.
회원 도메인 설계
회원 저장소라는 인터페이스를 만든다. 구현은 메모리 회원 저장소/DB회원저장소/외부 시스템 연동 회원 저장소를 만들어서 하나를 선택.
인터페이스를 추상체로, 그리고 인터페이스를 상속하는 구현체들을 따로 만들면 나중에 변경 사항이 생기더라도, 구현체만 변경하면 되기에 클라이언트에게 영향을 주지 않는다. (설계가 진~ 짜 중요한 이유!!)
개발
회원 등급, 회원 엔티티를 개발한다.
package hello.core.member;
public class Member {
private Long id;
private String name;
private Grade grade;
public Member(Long id, String name, Grade grade) {
this.id = id;
this.name = name;
this.grade = grade;
}
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;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
}
회원 저장소 인터페이스와, 메모리 회원 저장소 구현체를 개발한다.
데이터 베이스가 확정 되지 않았기 때문에, 가장 단순한 메모리 회원 저장소를 구현해서 개발을 진행한다. HashMap 은 동시성 이슈가 발생할 수 있기에, ConcurrentHashMap 을 사용한다.
package hello.core.member;
import java.util.HashMap;
import java.util.Map;
public class MemoryMemberRepository implements MemberRepository{
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) {
store.put(member.getId(), member);
}
@Override
public Member findById(Long memberId) {
return store.get(memberId);
}
}
회원 서비스 인터페이스와 회원 서비스 구현체를 만든다.
package hello.core.member;
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
회원 도메인 실행 및 테스트
애플리케이션 로직으로 테스트하지말고, JUnit 테스트를 사용한다.
package hello.core.member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemberServiceTest {
MemberService memberService = new MemberServiceImpl();
@Test
void join() {
//given
Member member = new Member(1L, "memberA", Grade.VIP);
//when
memberService.join(member);
Member findMember = memberService.findMember(1L);
//then
Assertions.assertThat(member).isEqualTo(findMember);
}
}
주문과 할인 도메인 설계
1. 주문을 만든다. (주문 서비스 요청)
2. 할인을 위해 회원 정보를 조회한다.
3. 할인을 적용한다.
4. 주문 결과를 클라이언트에게 반환한다.
역할을 먼저 만들고, 구현 객체를 나중에 만듬으로써 역할과 구현을 분리하여 구현 객체를 자유롭게 조립할 수 있게 설계한다! 회원 저장소는 물론이고, 할인 정책도 유연하게 변경할 수 있다.
클라이언트 -> 주문 서비스 구현체 -> 메모리 회원 저장소/정액 할인 정책
주문과 할인 도메인 개발
할인 정책 인터페이스와 구현체를 개발한다.
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class FixDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000; //1000원 할인
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP){
return discountFixAmount;
} else {
return 0;
}
}
}
주문 엔티티를 만든다.
package hello.core.order;
public class Order {
private Long memberId;
private String itemName;
private int itemPrice;
private int discountPrice;
public Order(Long memberId, String itemName, int itemPrice, int discountPrice) {
this.memberId = memberId;
this.itemName = itemName;
this.itemPrice = itemPrice;
this.discountPrice = discountPrice;
}
public int calculatePrice() {
return itemPrice - discountPrice;
}
public Long getMemberId() {
return memberId;
}
public void setMemberId(Long memberId) {
this.memberId = memberId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getItemPrice() {
return itemPrice;
}
public void setItemPrice(int itemPrice) {
this.itemPrice = itemPrice;
}
public int getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(int discountPrice) {
this.discountPrice = discountPrice;
}
@Override
public String toString() {
return "Order{" +
"memberId=" + memberId +
", itemName='" + itemName + '\'' +
", itemPrice=" + itemPrice +
", discountPrice=" + discountPrice +
'}';
}
}
주문 서비스 구현체를 만든다.
메모리 회원 레포지토리와 고정 금액 할인 정책을 구현체로 만든다.
package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId); // 2.회원정보 조회
int discountPrice = discountPolicy.discount(member, itemPrice); // 3.할인금액 조회
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
주문과 할인 정책 테스트
테스트 했을 때, 결과가 정상으로 출력되는 것을 볼 수 있다!
package hello.core.order;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class OrderServiceTest {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
@Test
void createOrder() {
Long memberId = 1L;
Member member = new Member(memberId, "aa", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "행주", 10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
👩💻 다음에는 구현체만 변경할 때, 클라이언트에 진짜 영향을 주지 않는지 확인해본다.
'Spring' 카테고리의 다른 글
[Spring 핵심원리 기본편] 스프링 컨테이너와 스프링 빈 (빈 조회) (0) | 2023.05.21 |
---|---|
[Spring 핵심원리 기본편] 객체 지향 원리 적용 (->스프링 컨테이너) (0) | 2023.05.21 |
[Spring 핵심원리 기본편] 객체 지향 설계와 스프링 2 (0) | 2023.05.20 |
[Spring 핵심원리 기본편] 객체 지향 설계와 스프링 (0) | 2023.05.16 |
[김영한 스프링 강의] JDBC, JPA, AOP 개념 (0) | 2023.05.15 |
블로그의 정보
개발자 미니민의 개발로그
mini_min