middlemoon
Spring - 스프링 핵심 원리 이해1 본문
우선 이 강의에서 비즈니스 요구사항과 설계를 베이스로 설명을 하게끔 해준다. 실무에 맞는 기준으로 직접 개발자가 개발하는 것이 아닌
외부의 지시로 인해 요구사항을 던져주면 그에맞게 설계를 하는 것이 개발자의 몫이라 할수 있을것이다.
비즈니스 로직부터 차근차근 넘겨짚으며 공부를 해보려한다.
이 블로그는 김영한님 강의를 토대로 작성하였으니 참고용으로 봐주시면 좋을 것 같고, 기록용으로 남기기 위해 올렸다는 점 참고해주시면 감사하겠습니다.
회원 도메인
회원을 가입하고 조회를 할 수 있다.
회원은 일반과 VIP 두가지 등급을 나눈다.
회원 DB를 만들고 외부시스템과 연동을 할 수 있다.
회원 클래스를 구현하기 위한 다이어그램은 다음과 같다.

1. Member에 관한 패키지를 만든다 -> Enum을 이용해 Grade(Basic, VIP) 두가지 등급으로 나타낸다.
package hello.core.member;
public enum Grade {
BASIC,
VIP
}
2.Member 클래스를 생성한다.(회원에 대한 속성을 넣어주는 공간)
-엔티티의 역할을 한다고 봐도 무방하다. MVC패턴으로 DTO공간으로 봐도 무방하다
package hello.core.member;
import hello.core.member.Grade;
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;
}
}
여기까지하고 나면 위의 다이어그램을 보면 알듯이 interface부분에 MemberService 그리고 MemberRepository 두가지가 존재하게 되는데 회원에 관한 저장소를 만들어 줘야 하니 MemberRepository가 어울릴 것이다.
package hello.core.member;
public interface MemberRepository {
void save(Member member);
Member findById(Long memberId);
}
역할과 구현의 분리가 중요하다 하였는데 쉽게 말해 역할에서는 save 혹은 findById 같이 저장할수 있거나 findById 라는 찾을 수 있는
기능적인 것들을 표현할때 역할이라 하면 이해가 쉬울것이다. 구현은 save 혹은 findById가 나올때 어떤식으로 저장이나 찾기를 도와줄수 있는지? 그런 부분을 분리하기 위해서 나누는 작업이다.
이제 역할을 만들어주었으면 구현을 만들어 볼 차례이다.
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);
}
}
MemoryMemberRepository 가 MemberRepository 를 상속받는 관계이다.
Map을 통해 빈 곳의 객체를 생성해 준 뒤, 부모 클래스의 Member인 save와 findById를 각각 Override(오버라이딩)을 선언해준다.
이렇게 되면 최상위 부모 클래스 Member부터 시작해 역할과 구현을 해줄수 있는 Model 로직을 생성하게 된다.

회원 저장소를 만들어주었고, 회원 서비스의 기능을 하기 위해 만들어보려한다.
package hello.core.member;
public interface MemberService {
void join(Member member);
Member findMember(Long memberId);
}
이거 또한 interface로 선언 후 가입과 찾기를 이용할 수 있도록 선언해 주고,
package hello.core.member;
import org.springframework.stereotype.Repository;
public class MemberServiceImpl 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);
}
}
MemberService의 구현체를 만들어보았다.
위에 까지 구현을 했다면 이 코드가 잘 작성됬는지 확인하는 과정이 필요하다. 테스트 코드를 구현을 하여 잘 실행이 되는지 확인해 보려한다.
package hello.core.member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public 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);
}
}
주문과 할인 도메인
주문과 할인 정책
회원은 상품을 주문할 수 있다.
회원 등급에 따라 할인 정책을 적용할 수 있다.
할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)
할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)

1.discount에 들어가는 패키지를 생성한 뒤, DiscountPolicy의 인터페이스를 생성해준다.
package hello.core.discount;
import hello.core.member.Member;
public interface DiscountPolicy {
/*할인 대상 금액*/
int discount(Member member, int price);
}
FixDiscountPolicy 같은 경우는 변동할인이 아닌 고정으로 할인해주겠다는 구현기능이 들어와야하므로
밑에 사진처럼 변수값에 discountFixAmount = 1000; 라는 코드를 한줄 생성해준다.
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class FixDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000; //
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP){
return discountFixAmount;
}else {
return 0;
}
}
}
VIP의 등급인경우 member에서 getGrade로 만든 getter를 가져와 조건문으로 비즈니스 로직을 간단하게 구현하는 과정이다.
그 다음 Discount 클래스를 만들었으니 Order을 클래스로 만들어 주어 아래 그림과 같이 DTO형식으로 넘겨준다.
package hello.core.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 void setMemberId(Long memberId) {
this.memberId = memberId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getItemPrice() {
return itemPrice;
}
public void setItemPrice(int itemPrice) {
this.itemPrice = itemPrice;
}
public int getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(int discountPrice) {
this.discountPrice = discountPrice;
}
@Override
public String toString() {
return "Order{" +
"memberId=" + memberId +
", itemName='" + itemName + '\'' +
", itemPrice=" + itemPrice +
", discountPrice=" + discountPrice +
'}';
}
}
원가(itemPrice)가 10000원일때 할인금액(discountPrice) 1000원일 경우를 나타내주어야하므로
calculatePrice()의 함수를 만들어주어 반환값을 따로 설정해주는 작업을 거치면 된다.
package hello.core.order;
public interface OrderService {
Order createOrder(Long memberId, String itemName, int itemPrice);
}
OrderService부분은

1.주문생성과 같이 각 해당하는 리스트들을 변수안에 선언 후 ServiceImpl안에 주문 결과를 반환 할수 있도록 선언한다.
package hello.core.order;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
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);
}
}
단일 책임의 원칙을 잘 지킨 코드이다. 만약 할인률이 변경되어 할인률을 고쳐달라는 요청이 들어왔을 때 discount에 대해서만
수정부분이 이루어질수 있기 때문이다.
package hello.core.order;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public 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);
}
}
Member에서 진행했던 테스트 코드처럼 주문에서도 잘 실행되는지 확인하는 과정이 필요하다.
'Develop > Spring' 카테고리의 다른 글
| Spring - 컴포넌트 스캔 (0) | 2023.03.14 |
|---|---|
| Spring - 웹 에플리케이션과 싱글톤 (0) | 2023.03.05 |
| Spring - 스프링 컨테이너와 스프링 빈 (0) | 2023.02.18 |
| Spring - 스프링 핵심 원리 이해2 (객체 지향 원리 적용) (0) | 2023.02.06 |
| Spring - 객체지향 프로그래밍의 이해 (0) | 2023.01.20 |