Back-end Skill/Springboot

스프링 핵심 원리 이해1 - 예제 만들기

Sophie소피 2022. 1. 31. 14:41

회원과 도메인 설계 

회원 도메인 요구사항 

 

주문과 할인 도메인 설계 주문과 할인 정책 회원은 상품을 주문할 수 있다.

회원 등급에 따라 할인 정책을 적용할 수 있다

회원 객체 다이어그램 

회원 도메인 개발 

 

회원 엔티티 

 

package hello.spring.hello.spring;

import java.lang.reflect.Member;

public class Member {
    private Long id;
    private String name;

    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    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;
    }

}

    

회원 저장소 

회원저장소 인터페이스 

package hello.core.member;
public interface MemberRepository {
 void save(Member member);
 Member findById(Long memberId);
}

 

메모리 회원 저장소 구현체 

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);
    }
}

db가 아직 확정되있지 않았다 

그렇기 때문에 단순한 메모리 회원 저장소를 구현해서 우선 개발을 진행하였다 

참고; HashMap은 동시성 이슈가 발생한다 그럴 땐  ConcurrentHashMap을 사용하자 

 

 

회원서비스  인터페이스 

package hello.core.member;
public interface MemberService {
 void join(Member member);
 Member findMember(Long memberId);
}

 회원서비스 구현체 

서비스 구현체
package hello.core.member;
public class MemberServiceImpl implements MemberService {
 private final MemberRepository memberRepository = new
MemoryMemberRepository();
 public void join(Member member) {
 memberRepository.save(member);
 }
 public Member findMember(Long memberId) {
 return memberRepository.findById(memberId);
 }
}

회원 도메인 실행과 테스트 

회원 도메인 - 회원가입 - main 

package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.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());
 }
}

애플리케이션 로직으로 이렇게 테스트 하는 것은 좋은 방법이 아니기에 Junit 테스트를 사용하자 

 

회원 도메인 회원가입 테스트 

package hello.core.member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
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);
 }
}

회원 도메인 설계의 문제접 

의존관계가 인터페이스 뿐만 아닐하 구현까지 모두 의존하는 문제점이 있다 

주문까지 만들고 나서 문제점과 해결 방안을 설명  

 

주문과 할인 도메인 설계 

1. 주문 생성: 클라이언트는 주문서비스에 주문생성을 요청한다

2. 회원 조회: 할인을 위해서는 회원 등급이 필요 

-> 주문 서비스는 회원 저장소에서 회원을 조회한다.

3.할인 적용 주문 서비스는 할인여부를 할읹 정책에 위임한다. 

4. 주문 결과 반환: 주문서비스는 할인결과를 포함한 주문 결과를 반환한다. 

 

실제로는 주문데이터를 db에 저장한다 지금 실습은 간단히 주문 결과를 반환하겠다. 

 

주문 도메인 전체 

역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있게 설계했다 

덕분에 회원 저장소와 할인 정책도 유연하게 설계 

 

주문 도메인 클래스 다이어그램 

회원을 메모리에서 조회하고, 정액할인 정책을 지원래도 주문서비스를 변경하지 않아도 된다 

역할들의 협력 관계를 그대로 잭 사용 

 

회원을 메모리가 아닌 실제 db에서 조회하고 정률 할인 정책을 지원하여도 주문 서비스를 변경하지

않아도 되고, 협력 관계 그대로 재사용 가능 

주문과 할인 도메인 개발 

할인 정책 인터페이스 

package hello.core.discount;
import hello.core.member.Member;
public interface DiscountPolicy {
 /**
 * @return 할인 대상 금액
 */
 int discount(Member member, int price);
}

정액 할인 정책 구현체 

package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.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;
 }
 }
}

VIP면 1000원 할인, 아니면 할인 없음

 

 주문 엔티티 

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 +
 '}';
 }
}

주문 서비스 인터페이스 

package hello.core.order;
public interface OrderService {
 Order createOrder(Long memberId, String itemName, int itemPrice);
}

주문 서비스 구현체 

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);
 }
}

주문 생성 요청이 오면, 회원 정보를 조회하고, 할인 정책을 적용한 다음 주문 객채를 생성해서 반환

메모리 회원 레포지토리와 고정 금액 할인 정책을 구현체로 생성한다. 

 

주문과 할인 도메인 실행과 테스트 

 주문과 할인 정책 실행 

package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
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 테스트를 사용하자 

 

주문과 할인 정책 테슽트 

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;
import static org.junit.jupiter.api.Assertions.*;
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);
 }
}