Inflearn/스프링 핵심 원리_기본편

스프링 핵심 원리 이해 ① - 2 (주문, 할인 도메인 설계, 테스트 )

soohykeee 2023. 2. 17. 20:14
728x90

 

스프링 핵심 원리 이해 ① - 1 (프로젝트 생성, 요구 사항, 도메인 설계)

 

스프링 핵심 원리 이해 ① - 1 (프로젝트 생성, 요구 사항, 도메인 설계)

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard 스프링 핵심 원리 - 기본편 - 인프런 | 강의 스프링 입문자가 예제를 만들어가면서 스프링

soohykeee.tistory.com

 


 

앞서 회원의 도메인 설계를 했다면, 이번에는 주문과 할인의 도메인 설계를 할 것이다. 앞서 주문과 할인 정책에 대해 요구사항을 작성했지만, 다시 한번 보면 아래와 같다.

  • 주문과 할인 정책
    • 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책을 적용할 수 있다.
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (추후에 변경 될 수 있음)
    • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 있다. (미확정)

 

 

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

 

 

역할과 구현을 분리하여 자유롭게 구현 객체를 조립할 수 있도록 설계했다. 그렇기에 회원 저장소나 할인 정책을 변경하기에 용이하다. 

 

 

FixDiscountPolicy -> 정액 할인 정책
RateDiscountPolicy -> 정률 할인 정책

 

현재 요구사항으로 VIP인 경우 정액 할인 정책으로 1000원이 고정되서 할인되는 FixDiscountPolicy일 때의 객체 다이어그램으로, 회원을 메모리에서 조회하고 정액 할인 정책을 지원하는 것이다.

현재 요구사항이 아니지만, 추후에 변경이 가능할 수 있으므로 가격에 따라 금액이 할인되는 RateDiscountPolicy일 때의 객체 다이어그램으로, 회원을 메모리에서 조회하고 정액 할인 정책을 지원하는 것이다.

위 처럼 구현체를 따로 나눠서 구현하면 역할들의 협력관계를 변경하지 않고 그대로 재사용할 수 있다.

 


 

주문과 할인 코드 구현

할인

할인 코드를 구현해주기 위해, discount 디렉토리를 생성한 후, 해당 디렉토리 하위에 DiscountPolicy 인터페이스와 현재의 요구사항을 반영하여 고정 금액을 할인해주는 FixDiscountPolicy 클래스를 생성해준다.

 

public interface DiscountPolicy {

    /**
     *
     * @param member
     * @param price
     * @return 할인 대상 금액
     */
    int discount(Member member, int price);

}

 

public class FixDiscountPolicy implements DiscountPolicy {

    private int discountFixAmount = 1000;   // VIP일 시, 고정으로 1000원 할인

    @Override
    public int discount(Member member, int price) {

        if (member.getGrade() == Grade.VIP) {
            return discountFixAmount;
        } else {
            return 0;
        }
    }

}

 


 

주문

주문을 구현해주기 위해, order디렉토리를 생성한 후, 해당 디렉토리 하위에 Order 클래스, OrderService 인터페이스와 OrderServiceImpl 클래스를 생성해준다.

 

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 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 com.example.core.order;

public interface OrderService {

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

 

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);

    }
}

 

주문 생성 요청이 오면, 회원 정보를 조회하고 -> 할인 정책을 적용한 다음 주문 객체를 생성해서 반환한다. 메모리 회원 리포지토리와, 고정 금액 할인 정책을 구현체로 생성한다.

 


 

할인과 주문 테스트

테스트를 위해서 core 디렉토리 하위에 OrderApp 클래스를 생성 후, 코드가 정상적으로 동작이 되는지 테스트 코드를 작성하여 확인해보겠다.

 

public class OrderApp {

    public static void main(String[] args) {
        MemberService memberService = new MemberServiceImpl();
        OrderService orderService = new OrderServiceImpl();

        long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA", 10000);

        System.out.println("order = " + order);
    }
}

정상적으로 테스트가 실행되는 것을 확인할 수 있다. 하지만 그전에도 말했듯이 테스트 코드는 JUnit을 사용하여 작성하는 것이 더 좋은 방법이기에 아래와 같이 수정해줄 것이다.

 

test쪽 디렉토리하위에 order 디렉토리 생성 후, OrderServiceTest 클래스를 생성한다.

public class OrderServiceTest {
    MemberService memberService = new MemberServiceImpl();
    OrderService orderService = new OrderServiceImpl();

    @Test
    void createOrder() {
        long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA", 10000);

        System.out.println("order = " + order);
    }

}

위와 같이 수정해 준 후 테스트 코드를 실행해도 정상적으로 동작이 되는 것을 확인할 수 있다.

 

 


 

728x90