비즈니스 요구사항과 설계
아래와 같은 요구사항을 보면 회원데이터, 할인 정책 같은 부분은 지금 결정하기 어려운 부분이다.
그치만 정책이 결정 나기 전까지 기다릴 수만은 없다.
이때 우리는 객체 지향 설계 방법을 활용하면 된다.
인터페이스를 만들고 언제든지 구현체를 갈아 끼울 수 있도록 설계하면 된다.
회원
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)
주문과 할인 정책
- 회원은 상품을 주문할 수 있다. 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해 달라. (나중에 변경될 수 있다.)
- 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루 고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)
회원 도메인 실행과 테스트
순수 자바 코드를 활용한 객체 비교
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MembrServiceImpl;
public class MemberApp {
public static void main(String[] args) {
MemberService memberService = new MembrServiceImpl();
Member memberA = new Member(1L, "memberA", Grade.VIP);
memberService.join(memberA);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + memberA.getName());
System.out.println("findMember = " + findMember.getName());
}
}
Main 메소드를 이용하면 눈으로 확인해야 하는 한계가 있다.
Junit 테스트 프레임을 이용하여 확인해 보자!
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MembrServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemberServiceTest {
MemberService memberService = new MembrServiceImpl();
@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);
}
}
회원 도메인 설계의 문제점
- 이 코드의 설계상 문제점은 무엇일까요?
- 다른 저장소로 변경할 때 OCP 원칙을 잘 준수할까요?
- DIP를 잘 지키고 있을까요?
- 의존관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제점이 있음
- 주문까지 만들고나서 문제점과 해결 방안을 설명
package hello.core.member;
import java.util.HashMap;
import java.util.Map;
public class MemoryMemberRepository implements MemberRepository {
private 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);
}
}
package hello.core.member;
public class MembrServiceImpl implements MemberService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
- MeberServicImpl 서비스는 MemberRepository 의 인터페이스를 의존을 잘하고 있지만 newMemoryMemberRepository(); 부분이 구현체를 의존하고 있다는 문제점이 있다.
- 결국 MeberServicImpl 은 MemberRepository 의 인터페이스와 MemoryMemberRepository 구현체 (추상화, 구현체)모두 의존하고 있다. 즉 DIP 원칙에 어긋난다.
주문과 할인 도메인 설계
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해 달라. (나중에 변경될 수 있다.)
- 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루 고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)
주문 도메인 협력, 역할, 책임
1. 주문 생성: 클라이언트는 주문 서비스에 주문 생성을 요청한다.
2. 회원 조회: 할인을 위해서는 회원 등급이 필요하다. 그래서 주문 서비스는 회원 저장소에서 회원을 조회한다.
3. 할인 적용: 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임한다.
4. 주문 결과 반환: 주문 서비스는 할인 결과를 포함한 주문 결과를 반환한다.
참고: 실제로는 주문 데이터를 DB에 저장하겠지만, 예제가 너무 복잡해 질 수 있어서 생략하고, 단순히 주문 결과를 반환한다
- 역할과 구현을 분리 해서 자유롭게 구현 객체를 조립할 수 있게 설계했다. 덕분에 회원 저장소는 물론이고, 할인 정책도 유연하게 변경할 수 있다
- 나중에 정책이 바뀐다면?
회원을 메모리가 아닌 실제 DB에서 조회하고, 정률 할인 정책(주문 금액에 따라 % 할인)을 지원해도 주문 서비스를 변 경하지 않아도 된다. 협력 관계를 그대로 재사용 할 수 있다.
주문과 할인 도메인 실행과 테스트
main 메소드 활용하여 테스트
package hello.core;
import hello.core.Order.Order;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MembrServiceImpl;
public class OrderApp {
public static void main(String[] args) {
MemberService memberService = new MembrServiceImpl();
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);
System.out.println("order.calculaterPrice = " +order.calculaterPrice());
}
}
할인 금액이 잘 출력되는 것을 확인할 수 있다. 애플리케이션 로직으로 이렇게 테스트 하는 것은 좋은 방법이 아니다. JUnit 테스트를 사용하자!
package hello.core;
import hello.core.Order.Order;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MembrServiceImpl;
public class OrderApp {
public static void main(String[] args) {
MemberService memberService = new MembrServiceImpl();
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);
System.out.println("order.calculaterPrice = " +order.calculaterPrice());
}
}
'Spring' 카테고리의 다른 글
[스프링 핵심 원리 - 기본편] 스프링 컨테이너와 스프링 빈 2 (0) | 2024.10.16 |
---|---|
[스프링 핵심 원리 - 기본편] 스프링 컨테이너와 스프링 빈 1 (0) | 2024.10.15 |
[스프링 핵심 원리 - 기본편] 스프링 핵심 원리 이해3 - 객체 지향 원리 적용 (3) | 2024.10.14 |
[스프링 핵심 원리 - 기본편] 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (3) | 2024.10.11 |
[스프링 핵심 원리 - 기본편]객체 지향 설계와 스프링 (4) | 2024.10.08 |