당니의 개발자 스토리

주문과 할인 도메인 설계 본문

스프링/스프링 핵심 원리 - 기본편

주문과 할인 도메인 설계

clainy 2024. 1. 17. 14:32

주문과 할인 도메인 설계

이번 시간에는 주문과 할인 도메인을 한번 설계해 보겠습니다. 이제부터 조금 복잡해집니다.

주문과 할인 정책을 다시 떠올려 볼게요. 회원은 상품을 주문할 수 있고, 회원 등급에 따라서 할인 정책을 적용할 수 있다고 했죠. 그런데 할인 정책은 모든 VIP는 1,000원을 할인해주는 고정 금액 할인을 적용할 거예요. 나중에 변경이 될 수도 있는 거예요. VIP 분들은 그냥 주문할 때마다 무조건 1,000원씩 할인을 해주는 거예요.

그런데 문제는 기획자가 '아... 아직 사장님 의사결정을 못했어요.. 할인 정책은 변경 가능성이 높습니다' 라고 얘기하는 거에요.

그래서 오픈 직전까지 고민을 미루고 싶대요. 본인은 1,000원 할인을 해주고 싶은데 아직 사장님이 승인을 안 한 거예요. 회사에서 1,000원 할인을 주면 비용이 많이 들어가니까..

그래서 최악의 경우에는 할인을 적용하지 않을 수도 있답니다.

'그럼 대체 어쩌라는거야?' 할 수 있는데, 여러분은 좋은 객체지향 설계를 하는 개발자이기 때문에 이렇게 설계를 합니다.

먼저 클라이언트는 주문을 생성할 수 있죠. 따라서 주문 서비스는 주문 생성의 역할을 해요. 그런데 어떻게 하냐, 회원 등급이 필요하기 때문에 먼저 회원 저장소에서 회원을 조회해서, 회원의 등급을 따져서 할인을 적용하는 할인정책 역할에다가 물어봅니다.

그래서 할인정책 역할, 얘가 '어 할인 적용할 수 있어' 라고 하면, 할인을 해서 그 결과를 주문 서비스에다가 내려주고, 주문 서비스는 최종적으로 할인까지 적용된 주문 결과를 클라이언트한테 반환을 해줍니다.


 하나씩 읽어보면요.

제일 처음에 클라이언트가 주문에 필요한 데이터를 가지고 주문 서비스에다가 "저 주문해주세요" 하고 요청을 해요.

여기서 클라이언트는 main 같은 코드가 되겠죠. 아니면 Spring MVC면, 컨트롤러 같은게 되겠죠.

아무튼 주문을 하면서 아이디랑 상품명이랑 상품의 가격을 주문 서비스에다가 넘깁니다. 이 예제에서는 간단하게 하기 위해서 상품에 대한 데이터를 안 만들었어요. 원래 실무에서는 상품 아이템이라는 객체가 있어야 되겠죠. 근데 그럼 또 복잡해져서 상품명이랑 상품 가격을 넘기기로 했습니다.

그러면 이제 주문 서비스는 할인을 하려면, 회원에 대한 등급이 있어야 되죠. 그래서 주문 서비스는 주문 생성할 때 넘어왔던 회원 아이디를 가지고 회원 저장소에서 findById로 회원에 대한 정보를 찾아옵니다.

그리고 그 회원의 등급 정보를 가지고 할인정책 역할한테 물어봐요. 즉, 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임해요. '이거 할인 가능하면 할인해서 결과 알려주세요~' 하고 위임을 하는 거죠.

주문 서비스는 그렇게 해서 할인 결과를 포함주문 결과를 반환합니다.

참고로 실제로는 이렇게 주문 서비스에서 주문 데이터를 만들어서 데이터베이스에 저장을 하겠죠. 그런데 예제가 너무 복잡해질 수 있어서 그냥 단순하게 주문 서비스는 단순히 주문 결과 객체를 만들어서, 클라이언트에 보내는 것까지만 하겠습니다. 실제 DB에 저장하는 과정은 생략하겠습니다.

이렇게 해서 주문 도메인의 협력과 역할과 책임에 대한 주문 도메인의 그림을 그렸고요.

이건 역할에 대해서만 그린거고, 역할과 구현까지 그린 게

이 그림입니다. 이 그림을 보면, 주문 서비스 역할, 회원 저장소 역할, 할인 정책 역할, 이렇게 역할 먼저 만들고 그 다음에 구현을 만들었죠.

이렇게 역할과 구현을 분리했기 때문에 자유롭게 구현 객체를 조립할 수 있게 설계가 된 거예요.

덕분에 회원 저장소는 물론이고, 할인 정책도 유연하게 변경할 수 있습니다.

지금 보면, 정액 할인 정책은 무조건 1,000원 할인해주는 거고, 정률 할인 정책은 1만 원을 주문하면 1,000원을 할인해주고 2만 원을 주문하면 2,000원을 할인해주는 것처럼 10% 할인을 해주는 거에요.

나중에 정액 할인 정책하다가 아니다 싶으면, 할인 정책을 바꿔 끼우기만 하면 되는 거예요.

역할과 구현을 잘 풀어서 설계를 하면, 이 그림이 잘 돌아가겠죠. 구현체만 거기에 맞도록 바꿔끼우면 됩니다.


그래서 실제 이거를 객체 레벨로 구현을 하면, 즉 클래스 다이어그램으로 그리면 이렇게 됩니다.

OrderService를 Interface로 만들 거고요. 그에 대한 구현체를 OrderServiceImpl 이라고 만듭니다.

원래 Interface에 대한 구현체가 딱 하나만 있으면, 인터페이스명 뒤에 Impl이라고 많이 붙입니다. 이렇게 하고 OrderServiceImpl이 MemberRepository랑 DiscountPolicy을 쓰는 거죠.

보면, 할인정책 역할이 구현으로 Fix 라는 고정 할인 정책과 Rate 라는 정률 할인 정책으로 나뉘게 됩니다.


자 그래서 객체 다이어그램 어떻게 되냐면요, 클래스 다이어그램과 다르게 객체 다이어그램은 실제 내가 new로 생성해서 application을 띄워서 동적으로 객체들의 연관관계가 맺어지는 그림이라고 했죠.

그래서 객체 다이어그램상 클라이언트가 주문 서비스 구현체를 호출하면, 주문 서비스 구현체는 new 해서 생성된 orderServiceImpl이 호출될 거고요. 그 다음에 new 해서 생성된 메모리 멤버 저장소가 호출될 거에요. 그리고 할인 정책은 이거 FixDiscountPolicy, 정액 할인 정책이 될 겁니다.

회원을 메모리에서 조회하고 정액 할인 정책, 즉 고정 금액을 지원해도 주문 서비스를 변경하지 않아도 되죠. 그리고 역할들의 협력 관계를 그대로 재사용할 수가 있습니다.

이게 무슨 말이냐면, 만약 MemberRepository가 메모리 멤버 리포지토리에서 DB 멤버 리포지토리로 바껴요. 그리고 그리고 DiscountPolicy가 정액 할인에서 정률 할인 정책으로 바뀌어요. 그래도 주문 서비스 구현체를 변경할 필요가 없다는 거죠. 이 협력 관계를 그대로 유지할 수가 있어요.


그래서 나중에 주문 도메인에 대한 객체 다이어그램이 이렇게 바뀔 수 있단 말이에요.

그래서 클라이언트가 주문 서비스 구현체를 쓰는데, '메모리가 아닌 db 회원 저장소로 하기로 했어요~' 라고 결정이 내려지면, db 회원 저장소 구현체를 만들어서 new 해서 넣으면 되죠. 그 다음에 '정액 할인이 아니고, 정률 할인 정책으로 바꿨어요' 라고 하면, 정률 할인 정책을 만들어서 딱 꽂으면 됩니다.

이렇게 회원을 메모리가 아닌, 실제 DB에서 조회하고 정률 할인 정책을 지원을 해도, 주문 서비스 역할에 대해서 전혀 변경할 필요가 없는 거예요.

따라서 협력 관계를 그대로 재사용 할 수가 있습니다.


다음 시간부터는 주문과 할인 도메인을 본격적으로 개발해 보겠습니다.