스프링 핵심 원리 섹션2 스프링 핵심 원리 이해 1 by 김영한

공부한것 다 기록해·2023년 5월 14일
0
post-thumbnail

비즈니스 요구사항과 설계

회원

  • 회원 가입
  • 회원 조회
  • 회원 종류 : 일반, VIP
  • 회원 데이터 자체 DB 구축, 외부 시스템 연동 가능(미확정)

주문과 할인 정책

  • 회원은 상품을 주문
  • 회원 등급에 따라 할인 정책을 적용
  • 할인 정책 : 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용
  • 할인 정책은 변경 가능성이 높으므로, 아직 할인정책에 대해서 정하지 않았고, 심지어 할인 정책을 적용하지 않을 수 도 있다.

회원 데이터, 할인 정책 같은 부분은 지금 결정하기 어려운 부분
인터페이스를 만들고 구현체를 언제든지 갈아끼울 수 있도록 설계해야 한다.

회원 도메인 설계

회원 도메인 관계

회원 클래스 다이어그램

회원 객체 다이어그램

  • 회원 서비스 : MemberServiceImpl

코드 구현

Grade

package sangyunpark.core.member;

public enum Grade { // 회원 등급
    BASIC,
    VIP
}

Member

package sangyunpark.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;
    }
}

MemberRepository

package sangyunpark.core.member;

public interface MemberRepository {
    void save(Member member); // 회원 데이터 저장

    Member findById(Long memberId); // 회원 아이디 찾기
}
  • 회원 데이터 저장
  • 회원 아이디 찾기

MemoryMemberRepository

package sangyunpark.core.member;

import java.util.HashMap;
import java.util.Map;

public class MemoryMemberRepository implements MemberRepository{

    public 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);
    }
}
  • 메모리 저장소 이므로 모든 인스턴스가 공유할 수있게 static으로 선언
  • 오버라이드로 함수 재정의

MemberService

package sangyunpark.core.member;

public interface MemberService {
    void join(Member member);

    Member findMember(Long memberId);
}

MemberServiceImpl

package sangyunpark.core.member;

public class MemberServiceImpl implements MemberService{

    private MemberRepository memberRepository = new MemoryMemberRepository();

    @Override
    public void join(Member member) { // 회원 가입
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) { // 회원 찾기
        return memberRepository.findById(memberId);
    }
}

회원 도메인 실행과 테스트

순수 Java로 구현

package sangyunpark.core;

import sangyunpark.core.member.Grade;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberService;
import sangyunpark.core.member.MemberServiceImpl;

public class MemberApp {
    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        Member member = new Member(1L, "memberA", Grade.VIP);
        memberService.join(member);

        Member findMember = memberService.findMember(1L);
        System.out.println("newMember = " + member.getName());
        System.out.println("find Member = " + findMember.getName());
    }
}

Junit으로 구현

package sangyunpark.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(member.getId());

        //then
        Assertions.assertThat(member).isEqualTo(findMember);
    }
}

회원 도메인 설계의 문제점

  • 의존관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제점이 있음

주문과 할인 도메인 설계

  • 회원은 상품을 주문할 수 있다.
  • 회원 등급에 따라 할인 정책을 적용할 수 있다.
  • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해라(나중에 변경 됨)

- 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다.(미확정)


1. 주문 생성 : 클라이언트는 주문 서비스에 주문 생성을 요청
2. 회원 조회 : 할인을 위해서는 회원등급이 필요하므로, 회원 저장소에서 회원을 조회
3. 할인 적용 : 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임
4. 주문 결과 반환 : 주문 서비스는 할인 결과를 포함한 주문 결과를 반환

주문 도메인 클래스 다이어그램

주문 도메인 객체 다이어그램 1

주문 도메인 객체가 바뀐다면..?! 상관 없다!!

주문 도메인 객체 다이어그램 2

  • 회원을 메모리에서 DB로 할인 정책을 정액 할인 정책에서 정률 할인 정책으로 변경하여도 주문 서비스를 변경하지 않아도 된다.
  • 협력 관계를 그대로 재사용 할 수 있다.

주문과 할인 도메인 개발

주문

order/Order

package sangyunpark.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 +
                '}';
    }
}

order/OrderService(inteface)

package sangyunpark.core.order;

public interface OrderService {

    Order createOrder(Long memberId, String itemName, int itemPrice);
}

order/OrderServiceImpl(구현체)

package sangyunpark.core.order;

import sangyunpark.core.member.discount.DiscountPolicy;
import sangyunpark.core.member.discount.FixDiscountPolicy;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberRepository;
import sangyunpark.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);
        int discountPrice = discountPolicy.discount(member, itemPrice); // 할인 금액 첨부

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

할인

discount/DiscountPolicy(interface)

package sangyunpark.core.member.discount;

import sangyunpark.core.member.Member;

public interface DiscountPolicy {

//  @return 할인대상 금액
    int discount(Member member, int price); // 할인 정책
}

discount/FixDiscountPolicy - 고정 할인

package sangyunpark.core.member.discount;

import sangyunpark.core.member.Grade;
import sangyunpark.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 sangyunpark.core.order;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import sangyunpark.core.member.*;

public class OrderServiceTest {

    OrderService orderService = new OrderServiceImpl();
    MemberService memberService = new MemberServiceImpl();

    @Test
    void createOrder(){

        Long memberId = 1L;

        Member member = new Member(memberId,"박상윤", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "상품 1", 10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
        // VIP 인경우 1000원
    }

}
  • 멤버의 Grade가 VIP이므로, 할인 가격이 1000원인지 확인

0개의 댓글