출처 : 김영한님의 스프링 핵심 원리 - 기본편 인프런 강의
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
비즈니스 요구사항과 설계
회원
- 회원 가입, 조회
- 일반, VIP 두가지 회원 등급
- DB 미확정
주문과 할인 정책
- 회원은 상품을 주문
- VIP에게 1000원 할인 (변경 가능)
- 할인 정책 미확정
미확정인 요구사항이 있어 결정이 어렵다.
하지만 결정이 될 때까지 개발을 미룰수도 없기 때문에 인터페이스를 만들고 구현체를 갈아끼울 수 있도록 설계하는 객체 지향 설계를 적용하여 개발을 시작한다.
회원 도메인 설계
회원 객체 다이어그램 : 실제 서버에 올라왔을 때 객체간 메모리 참조 관계
회원 도메인 개발
Member Domain
- Grade
public enum Grade { BASIC, VIP }
- Member
public class Member { private Long id; private String name; private Grade grade; public Member(Long id, String name, Grade grade) { this.id = id; this.name = name; this.grade = grade; } 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; } }
Member Repository
- MemberRepository
public interface MemberRepository { void save(Member member); Member findById(Long memberId); }
- MemoryMemberRepository
Member Service
- MemberService
public interface MemberService { void join(Member member); Member findMember(Long memberId); }
- 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.getName());
System.out.println("find Member = " + findMember.getName());
}
}
이렇게 애플리케이션 로직으로 테스트 하는 것은 좋은 방법이 아니다.
JUunit 테스트를 사용하여 테스트해보자
import java.lang.reflect.Member;
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);
}
}
테스트는 통과하지만 이 코드는 설계상의 문제점이 있다.
- 회원 저장소가 미확정이기 때문에 다른 저장소로 변경시 OCP의 원칙을 위반하게 된다.
- MemberServiceImpl을 보면 MemoryRepository 인터페이스 뿐만 아니라 MemoryMemberRepository 구현체까지 의존하고 있기 때문에 DIP 원칙을 위반하게 된다.
주문과 할인 도메인 설계
주문 도메인 전체 : 역할과 구현을 그린 그림
회원 저장소 역할과 메모리 회원 저장소, DB 회원 저장소 구현을 분리하여 설계해서 DB 변경이 필요할 때 유연하게 변경하여 대처가능하도록 설계하였다. (할인 정책도 마찬가지)
주문 도메인 클래스 다이어그램
주문 도메인 객체 다이어그램
MemberRepository의 구현체로 MemmoryMemberRepository를 선택하고 DiscountPolicy로 FixDiscountPolicy를 선택했을 경우 객체 다이어그램은 다음과 같다.
여기서 만약 DbMemberRepository나 RateDiscountPolicy로 변경하고 싶다면 위의 클래스 다이어그램에 따라 협력 관계를 그대로 재사용해서 주문 서비스 구현체를 변경할 필요 없이 변경이 가능하다.
주문과 할인 도메인 개발
Discount
- DiscountPolicy 인터페이스
import java.lang.reflect.Member; public interface DiscountPolicy { /** * @return 할인 대상 금액 */ int discount(Member member, int price); }
- FixDiscountPolicy 구현체
import java.lang.reflect.Member; 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; } } }
Order Domain
- Order
public class Order { private Long memberId; private String itemName; private int itemPrice; private int discountPrice; public Order(Long memberId, String itemName, int itemPrice, int discountPrice) { this.memberId = memberId; this.itemName = itemName; this.itemPrice = itemPrice; this.discountPrice = discountPrice; } public int calculatePrice() { return itemPrice - discountPrice; } public Long getMemberId() { return memberId; } public String getItemName() { return itemName; } public int getItemPrice() { return itemPrice; } public int getDiscountPrice() { return discountPrice; } @Override public String toString() { return "Order{" + "memberId=" + memberId + ", itemName='" + itemName + '\'' + ", itemPrice=" + itemPrice + ", discountPrice=" + discountPrice + '}'; } }
Order Service
- OrderService 인터페이스
public interface OrderService { Order createOrder(Long memberId, String itemName, int itemPrice); }
- OrderServiceImpl 구현체 : 고객이 주문하면 회원 정보를 조회하고 할인 정책을 적용한 후 주문 객체를 생성해서 반환
OrderService 입장에서는 지금 할인에 대해서는 전혀 모르는 상태이고 discountPolicy 너가 알아서 할인해주고 결과만 던져달라고 설계한 것이다. -> 단일 책임 원칙이 잘 지켜진 좋은 설계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); } }
주문과 할인 도메인 실행과 테스트
이번에도 순수한 자바 코드로 한번 테스트를 짜보자
import java.lang.reflect.Member;
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);
}
}
[ 결과 ]
order = Order{memberId=1, itemName='itemA', itemPrice=10000, discountPrice=1000}
할인 금액이 잘 적용되는 것을 볼 수 있다.
이번엔 JUnit 테스트를 사용하여 짜본다.
class OrderServiceTest {
MemberService memberService = new MemberServiceImpl();
OrderService orderService = new OrderServiceImpl();
@Test
void createOrder() {
long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
테스트가 잘 동작하는 것을 볼 수 있지만 위의 회원 도메인과 마찬가지로 OCP, DIP 원칙을 위반하고 있다.
'Spring > Spring 핵심 원리 - 기본' 카테고리의 다른 글
[Spring] 컴포넌트 스캔 (0) | 2022.02.23 |
---|---|
[Spring] 싱글톤 컨테이너 (0) | 2022.02.23 |
[Spring] 스프링 컨테이너와 스프링 빈 (0) | 2022.02.22 |
[Spring] 객체 지향 원리 적용 (0) | 2022.02.22 |
[Spring 핵심 원리] 객체 지향 설계와 스프링 (0) | 2022.02.16 |