RateDiscountPolicy 추가

  • 주문한 금액의 %를 할인해주는 새로운 정률 할인 정책인 RateDiscountPolicy 추가

RateDiscountPolicy 코드 추가

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class RateDiscountPolicy implements DiscountPolicy {

    private int discountPercent = 10;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return price * discountPercent / 100;
        } else {
            return 0;
        }
    }
}

테스트 코드 추가

  • [Ctrl] + [Shift] + [T]
    • 테스트 코드 자동 생성
    • JUnit5
      • JUnit5는 @DisplayName 어노테이션에 한글 이름 지정 가능
  • 테스트를 할 때는 실패 테스트도 만들어야 한다
  • 테스트를 돌렸을 때 실패한 테스트는 코드 창 왼쪽에 빨간색 느낌표로 표시된다.
    • 성공한 테스트는 녹색 체크로 표시된다.
  • Assertions는 static import하는 것이 좋다.
    • 메소드에서 [Alt] + [Enter] → Add on-demand static import for ~ 선택
    • ⇒ import 문에 static 추가됨
  • RateDiscountPolicyTest.java
package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class RateDiscountPolicyTest {

    RateDiscountPolicy discountPolicy = new RateDiscountPolicy();

    @Test
    @DisplayName("VIP는 10% 할인이 적용되어야 한다")
    void vip_o() {
        // given
        Member member = new Member(1L, "memberVIP", Grade.VIP);

        // when
        int discount = discountPolicy.discount(member, 10000);

        // then
        assertThat(discount).isEqualTo(1000);
    }

    @Test
    @DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다")
    void vip_x() {
        // given
        Member member = new Member(2L, "memberBASIC", Grade.BASIC);

        // when
        int discount = discountPolicy.discount(member, 10000);

        // then
        assertThat(discount).isEqualTo(0);
    }

}

 

※ 본 게시글은 인프런의 스프링 핵심 원리 - 기본편(김영한)을 수강하고 정리한 내용입니다.

OrderApp

package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

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.toString());
    }
}
  • main을 이용하여 테스트

OrderServiceTest

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() {
        // primitive type인 long으로 해도 되지만, 그러면 null이 들어갈 수 없음
        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA", 10000);
        // org.assertj.core.api를 사용해야 .을 이용한 메소드 체인을 사용 가능
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

전체 테스트

  • test.java.hello.core에서 Run test
  • 전체 테스트가 실행 됨
  • CoreApplicationTest처럼 @SpringBootTest 어노테이션이 붙어있으면 스프링을 띄우기 떄문에 오래 걸림
  • 단위 테스트를 잘 짜는 것이 중요
    • 단위 테스트 수 천개가 있어도 몇 초만에 끝남
    • 여기서 단위 테스트는 스프링이나 컨테이너의 도움 없이 순수하게 자바 코드로 테스트 하는 것

 

※ 본 게시글은 인프런의 스프링 핵심 원리 - 기본편(김영한)을 수강하고 정리한 내용입니다.

hello.core.discount 패키지 생성

  • DiscountPolicy 인터페이스와 그 구현체들이 위치

 

DiscountPolicy

  • discount 메소드
package hello.core.discount;

import hello.core.member.Member;

public interface DiscountPolicy {

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

 

FixDiscountPolicy

  • 정액 할인 정책
package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class FixDiscountPolicy implements DiscountPolicy {

    private int discountFixAmount = 1000;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return discountFixAmount;
        } else {
            return 0;
        }
    }
}
  • ※ enum의 비교는 ==로 비교하는 것이 올바름

 

hello.core.order 패키지 생성

  • 주문과 관련된 클래스들이 위치

 

OrderService

  • 주문 서비스에 대한 인터페이스
package hello.core.order;

public interface OrderService { 
	Order createOrder(Long memberId, String itemName, int itemPrice); 
}

 

OrderServiceImpl

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

        // OrderService 입장에서는 할인 정책에 대해서 모르고, discountPolicy에 위임함 -> 단일 책임 원칙 준수
        // 할인이 변경되어도 OrderService는 바뀌지 않음
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}
  • MemoryMemberRepository와 FixDiscountPolicy를 구현체로 사용

 

※ 본 게시글은 인프런의 스프링 핵심 원리 - 기본편(김영한)을 수강하고 정리한 내용입니다.

주문과 할인 정책

  • 회원은 상품을 주문 가능
  • 회원 등급에 따라 할인 정책 적용
  • 할인 정책은 모든 VIP는 1,000원을 할인해주는 고정 금액 할인 적용(추후 변경 가능)
  • 할인 정책은 변경 가능성이 높음(미확정)

주문 도메인 협력, 역할, 책임

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

주문 도메인 전체

  • 역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있게 설계했다. 덕분에 회원 저장소는 물론이고, 할인 정책도 유연하게 변경할 수 있다.

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

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

 

  • 클라이언트가 주문 서비스 구현체를 호출하면 MemoryMemberRepositoryFixDiscountPolicy를 구현체로 사용할 것이다.
  • 회원을 메모리에서 조회하고, 정액 할인 정책을 지원해도 주문 서비스를 변경하지 않아도 됨
  • 역할들의 협력 관계를 그대로 재사용할 수 있음

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

  • 클라이언트가 주문 서비스 구현체를 호출하면 DbMemberRepositoryRateDiscountPolicy를 구현체로 사용할 것이다.
  • 회원을 메모리가 아닌 DB에서 조회하고, 정률 할인 정책을 지원해도 주문 서비스를 변경하지 않아도 됨
  • 역할들의 협력 관계를 그대로 재사용할 수 있음

 

※ 본 게시글은 인프런의 스프링 핵심 원리 - 기본편(김영한)을 수강하고 정리한 내용입니다.

MemberApp 클래스 생성

  • MemberService의 join 기능이 정상 작동하는지 확인
package hello.core;

import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.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("new member = " + member);
        System.out.println("findMember = " + findMember);
    }
}
  • 출력
    • new member = memberA
    • find Member = memberA
  • 해당 코드는 스프링 관련 코드가 없음. 순수 자바로 개발
  • 하지만 일일이 눈으로 테스트 하는 것은 좋은 방법이 아님
    • ⇒ junit이라는 테스트 프레임워크 사용
    • 빌드 될 때 test에 대한 코드는 운영환경에 배포가 되지 않음

MemberServiceTest

  • 테스트 코드 작성
    • given - when - then으로 나누어 테스트 코드 작성
    package hello.core;
    
    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 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);
        }
    }
    
    • Assertions는 org.assertj.core.api.Assertions를 사용
    • join한 멤버와 findMember의 리턴 값이 같은지 확인하는 코드
    • 테스트가 실패하면 확실하게 표시가 되기 때문에 훨씬 테스트가 용이함
    • 테스트 코드는 선택이 아닌 필수

회원 도메인 설계의 문제점

  • 의존관계가 인터페이스 뿐만 아니라 구현까지 의존함(MemberServiceImpl)
  • 추상화에도 의존하고 구체화에도 의존함
  • DIP 위반 → 변경이 있을 때 문제가 됨

 

※ 본 게시글은 인프런의 스프링 핵심 원리 - 기본편(김영한)을 수강하고 정리한 내용입니다.

+ Recent posts