이번 강의에서는 회원, 주문, 할인 도메인 설계 및 테스트까지 진행하였다.
인터페이스와 구현체에 관해 기초적인 부분을 이해할 수 있어서 좋았다.
평소에 프로젝트를 설계할 때 이부분에 관해서 깊게 생각해 본적이 없는데 앞으로 신경..써야함을 뼈저리게 느꼈다.
1. 회원 도메인 설계
회원 도메인 요구 사항은 아래와 같다
- 회원 가입 / 조회 가능
- 회원은 일반/VIP 두가지 등급
- 회원 데이터는 자체 DB 구축할 수 있고, 외부 시스템과 연동 가능성 있음(미확정)
협력 관계의 다이어그램을 보자면 위와 같다.
회원 클래스의 다이어그램을 보자면 위와 같은데, 이때 이 그림은 정적이라고 볼 수 있다.
회원 객체 다이어그램은 위와 같은데 이 때 이 그림은 동적이다.
2. 회원 도메인 개발
회원 등급
public enum Grade {
BASIC,
VIP
}
등급은 BASIC(일반), VIP(vip) 등급 두가지로 나눠져 있고, 이넘으로 생성한다.
회원 엔티티
public class Member {
private Long id;
private String name;
private Grade grade;
//생성자
public Member(Grade grade, String name, Long id) {
this.grade = grade;
this.name = name;
this.id = id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Grade getGrade() {
return grade;
}
public void setGrade(Grade grade) {
this.grade = grade;
}
}
회원 엔티티를 생성한다.
회원을 식별할 수 있는 id와 회원의 이름 그리고 등급을 나타내는 grade로 필드를 선언한다.
이 후 생성자와 getter setter를 작성한다.
회원 저장소 인터페이스
public interface MemberRepository {
//회원 저장
void save(Member member);
//회원 id로 찾는거
Member findById(Long memberId);
}
회원 저장소 인터페이스를 선언한다.(역할)
메모리 회원 저장소 구현체
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);
}
}
회원 저장소의 구현체 생성
데이터 베이스가 아직 확정이 되지 않았기 때문에 단순한 메모리 회원 저장소를 우선 구현한다.
회원 서비스 인터페이스
public interface MemberService {
//회원 가입
void join(Member member);
//회원 조회
Member findMember(Long memberId);
}
회원 서비스 인터페이스 (역할)을 생성한다.
회원 서비스 구현체
public class MemberServiceImpl implements MemberService {
//DIP 위반
private final MemberRepository memberRepository = new MemoryMemberRepository();
//comm shit enter
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
회원 서비스 구현체를 생성한다.
이때 서비스 구현체는 회원 저장소 인터페이스와 구현체 두개에 모두 의존하고 있기 때문에 DIP를 위반하게 된다.
3. 회원 도메인 - 회원 가입 테스트
강의에서는 애플리케이션 로직으로 테스트를 먼저 하지만 나는 이 부분은 생략하겠다
바로 JUnit 테스트로 넘어간다.
public class MemberServiceTest {
MemberService memberService = new MemberServiceImpl();
@Test
void join(){
//given
Member member = new Member(Grade.VIP, "memberA", 1L);
//when
memberService.join(member);
Member findMember = memberService.findMember(1L);
//then
assertThat(member).isEqualTo(findMember);
}
}
실행 결과 잘 돌아간다는 것을 알 수 있다.
다음으로는 주문과 할인 정책에 대한 도메인 설계이다.
4. 주문과 할인 도메인 설계
주문과 할인 정책에 대한 요구 사항은 다음과 같다
- 회원은 상품을 주문할 수 있음
- 회원 등급에 따라 할인 정책 적용 가능
- 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인 적용(나중에 변경 가능성 있음)
- 할인 정책은 변경 가능성 높음 -> 아직 정하지 못함. 오픈 직전까지 고민 중
역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있게 설계했다.
정적인 클래스 다이어그램이다.
이 다이어 그램을 통해 인터페이스에 여러 구현체가 매핑 될 수 있음을 알 수 있다.
회원을 메모리에서 조회하고, 정액 할인 정책을 지원해도 주문 서비스를 변경하지 않아도 된다.
역할들의 협력 관계를 그대로 재사용 할 수 있다.
회원을 메모리가 아닌 실제 DB에서 조회하고, 정률 할인 정책을 지원해도 주문 서비스를 변경하지 않아도 된다.
할인 정책 인터페이스
public interface DiscountPolicy {
//@retrun 할인 대상 금액
int discount(Member member, int price);
}
할인 정책 구현체
public class FixDiscountPolicy implements DiscountPolicy {
//옵션 + 엔터
private int discountFixAmount = 1000; //1000원 할인
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return discountFixAmount;
} else {
return 0;
}
}
}
회원 등급이 VIP인 경우 1000(할인 금액)을 반환하고 아닐 경우 0(할인 금액)을 반환한다.
주문 엔티티
public class Order {
private Long memberId;
private String itemName;
private int imtemPrice;
private int discountPrice;
public Order(Long memberId, String itemName, int imtemPrice, int discountPrice) {
this.memberId = memberId;
this.itemName = itemName;
this.imtemPrice = imtemPrice;
this.discountPrice = discountPrice;
}
public int calculatePrice() {
return imtemPrice - 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 getImtemPrice() {
return imtemPrice;
}
public void setImtemPrice(int imtemPrice) {
this.imtemPrice = imtemPrice;
}
public int getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(int discountPrice) {
this.discountPrice = discountPrice;
}
@Override
public String toString() {
return "Order{" +
"memberId=" + memberId +
", itemName='" + itemName + '\'' +
", imtemPrice=" + imtemPrice +
", discountPrice=" + discountPrice +
'}';
}
}
주문 서비스 인터페이스
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);
}
}
주문 생성이 요청 오면
회원 정보를 조회하고
할인 정책을 적용한 다음
주문 객체를 생성해서 반환한다.
5. 주문과 할인 도메인 테스트
public class OrderServiceTest {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
@Test
void createOrder() {
Long memberId = 1L;
Member member = new Member( Grade.VIP,"memberA",memberId);
memberService.join(member);
Order order = orderService.createOrder(memberId, "ItemA", 10000);
assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
잘 돌아감을 알 수 있다.
🙏 이 포스트는 김영한 개발자님의 <스프링 핵심원리 - 기본편> 를 듣고 공부한 내용을 바탕으로 작성되었습니다.
'🍎 Spring > 김영한 스프링 핵심원리' 카테고리의 다른 글
[김영한 스프링 핵심원리 - 기본편] 4. 객체 지향 원리 적용(3) (1) | 2025.01.17 |
---|---|
[김영한 스프링 핵심원리 - 기본편] 4. 객체 지향 원리 적용(2) (2) | 2025.01.17 |
[김영한 스프링 핵심원리 - 기본편] 4. 객체 지향 원리 적용(1) (0) | 2025.01.17 |
[김영한 스프링 핵심원리 - 기본편] 2. 좋은 객체 지향 설계의 5가지 원칙 SOLID (0) | 2025.01.07 |
[김영한 스프링 핵심원리 - 기본편] 1. 다형성 (0) | 2025.01.07 |