비즈니스 요구사항과 설계
- 회원
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 1000원을 할인해 주는 고정 금액 할인을 적용해 달라. (나중에 변경될 수 있다.)
- 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수도 있다. (미확정)
위의 경우, 미확정 인 부분에 대해서 현재 결정하기 어렵다. 개발을 뭐든 기획이 다 끝날때까지 기다렸다가 할 수는 없으니, 역할과 구현을 구분해서 진행한다.
회원 데이터는 DB가 자체구축이 가능하며, 외부 시스템과 연동 할 수 있기 때문에 회원 데이터에 접근하는 계층은 따로 만들어서 관리한다.
public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
이대로 개발할 경우, 다른 저장소로 변경하면 OCP원칙 (개방-폐쇄 원칙)을 잘 지키는 걸까? DIP(의존관계 역전 원칙)는?
-> 실제로 의존 관계가 인터페이스 뿐 아니라 구현까지 모두 의존하고 있다. = 추상화 / 구체화 모두 의존
실제 구현 상태는 다음과 같아진다. 기능을 확장하게 되면 클라이언트에 영향을 주고, OrderServiceImpl은 구현클래스인 FixDiscountPolicy와 RateDiscountPolicy 에 의존하고 있기 때문에 OCP와 DIP를 어긴다.
별도의 AppConfig 사용
구현 객체를 생성하고, 연결하는 AppConfig를 생성한다. (어떤 구현 객체가 들어와야하는지 정하는 역할)
OrderServiceImpl에 하지 않는 이유는, SRP(단일책임)원칙을 지키기 위해서이다.
public class AppConfig {
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
ths.discountPolicy = discountPolicy;
}
}
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
OrderService orderService = appConfig.orderService();
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP
Order order = orderService.createOrder(memberId, "itemA", 20000);
System.out.println("order = " + order);
}
}
이렇게 설계하면, OrderServiceImpl은 구현 클래스들을 의존하지 않는다.
OrderServiceImpl에 어떤 구현 객체가 들어올지는 AppConfig에서 결정한다.
위와같이 클라이언트는 주문 서비스에 주문 생성을 요청하고,
할인을 위해서는 회원 등급이 필요하므로 주문 서비스가 회원 저장소에서 회원을 조회하고,
할인 정책 저장소에서 할인 정책을 가져오도록 한다.
이렇게 될 경우, '주문'이라는 방식에 대해서 회원 및 할인 정책을 유연하게 바꿀 수 있다. 바꾼다 한들 주문 서비스에는 영향이 가지 않기 때문에 협력 관계를 그대로 사용할 수 있다.
테스트 코드 작성 방식
- given : 테스트를 위한 준비 . 변수, 입력 값 등을 정의함
- when : 테스트를 실행하는 과정 . 검증하는 내용을 확인 가능
- then : 실제 실행으로 결과를 검증