비즈니스 요구사항과 설계
(1) 회원
- 회원을 가입하고 조회
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, - 외부 시스템과 연동할 수 있다.(미확정)
(2) 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라.
(나중에 변경 될 수 있다.) - 할인 정책은 변경 가능성이 높다.
- 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다.
최악의 경우- 할인을 적용하지 않을 수도 있다. (미확정)
- 할인을 적용하지 않을 수도 있다. (미확정)
회원 도메인 설계
회원 도메인 협력관계
회원 클래스 다이어그램
회원 객체 다이어그램
회원 도메인 개발
회원 요구사항에 따른 필요 클래스및 인터페이스
Member.class : DB 에 저장할 회원 정보 (회원 엔티티)
grade.enum : 일반회원,VIP회원 을 나눌 enum
MemberService.interface : 회원 서비스의 기능(역할)을 정한 인터페이스
MeberServiceImpl.class : MemberService인터페이스의 구현체 회원가입과 회원조회의 기능을 구현
MemberMemory.interface : DB의 기능(역할)을 정한 인터페이스
MemoryMemberRepository : MemberMemory인터페이스의 구현체 (아직 어떤 DB를 사용할 지 정해지지않음)
package hello.core.member;
//회원등급
public enum grade {
BASIC,VIP
}
package hello.core.member;
//회원 엔티티
public class Member {
private Long id;
private String email;
private String grade;
public Member(Long id, String email, String grade) {
this.id = id;
this.email = email;
this.grade = grade;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
package hello.core.member;
//회원 저장소 인터페이스
public interface MemberRepository {
void save(Member member);
Member findId(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<>();
//회원 요구사항에 아직 DB구축에대헤 미확정이여서 일단 단순한 회원 메모리인 Map의 구현체인 HashMap을 사용
//Map이라는 인터페이스로 자바의 다형성을 이용해 언제든지 구현체를 바꿀수있도록 했다.
@Override
public void save(Member member){
}
@Override
public Member findId(Long MemberId) {
return null;
}
}
package hello.core.member;
// 회원 (기능)서비스 인터페이스
public interface MemberService {
void 회원가입();
void 회원조회();
}
package hello.core.member;
//회원서비스 구현체
public class MemberServiceImpl implements MemberService{
@Override
public void 회원가입() {
}
@Override
public void 회원조회() {
}
}
회원 도메인 실행과 테스트
순수 자바코드 테스트
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImp;
public class MemberApp {
//psvm 단축키
public static void main(String[] args) {
//테스트할 객체 MemberService 의 구현채 생성
MemberService memberService = new MemberServiceImp();
//가입시킬 멤버 정보 입력
Member member = new Member(1L, "승빈", Grade.VIP);
//변수 추출 단축키 control+alt+V
memberService.회원가입(member);
Member fMember = memberService.회원조회(1L);
// 단축키 soutv 변수 바로 출력
System.out.println("member = " + member.getName());
System.out.println("fMember = " + fMember.getName());
//지금코드는 스프링의 도움없이 순수 자바코드로만 만들어진 테스트 코드이며
//main함수로 테스트하는거는 한게가 있기 때문에 JUnit 이라는 프레임워크를 사용한다.
}
}
단축키 정리
public static void main(String[] args) {} = psvm
변수 추출 단축키 = control+alt+V
System.out.println("member = " + member.getName()); 변수 추출 = soutv
실행(테스트)결과
- 회원가입 정보와 회원조회의 결과가 같은지 테스트하는결과 이고 테스트결과 같게나와 테스트는 통과하였으나,
이 테스트는 순수 자바코드로만 만들어진 테스트 코드이여서 main함소로 일일이 콘솔창을 보며
다 테스트하기에는 한계가 있기 때문에 JUnit 이라는 프레임워크를 사용하여 테스트 하자!
JUnit Test
package hello.core.member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class MemberServiceTest {
MemberService memberService = new MemberServiceImp();
@Test
void join(){
//given = 이런게 주어진다.
Member member = new Member(1L, "승빈", Grade.VIP);
//when = 이럴때
memberService.회원가입(member);
Member member1 = memberService.회원조회(1L);
//then = 이렇게 된다.(검증)
Assertions.assertThat(member).isEqualTo(member1);
}
}
junit테스트 결과
- Assertions이라는 검증 API를 이용해서 member와 member1이 같은지 확인한후 같으면 위와
같이 초록체크가되면서 테스트가 통과된다.
회원 도메인 설계의 문제점
public class MemberServiceImp implements MemberService {
MemberRepository memberRepository = new MemberMemoryRepository();
- 위 코드를 보면 MemberServiceImp클래스는 MemberRepository 인터페이스를 의존하고 MemberMemoryRepository 구현체도 의존한다.
MemberServiceImp클래스가 인터페이스를 의존하는건 상관없지만, MemberMemoryRepository라는 구현체를 의존한다는게
문제가 된다.(추상화도의존하고 구체화에도 의존한다는 문제) 따라서 나중에 변경이있을 때 문제가된다.(DIP위반)
== 의존관계가 인터페이스 뿐만아니라 구현까지 모두 의존하는 문제점이 있다.
주문까지 만들고나서 문제점과 해결방안을 찾아보자!
주문과 할인 도메인 설계
- 주문과 할인 정책
- 회원은 상품을 주문할 수 있다.
- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수
있다.) - 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을
- 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)
위 도메인 설계의 핵심은 어떤 할인 정책을 사용할지 미확정 이기 때문에 할인정책을 역할과 구현으로 구분해서 언제든지 할인정책을 바꿀수있도록 설계하는 것이 핵심이다.
주문 도메인 전체 설계
- 역활과 구현을 분리해서 저장소와 할인 정책을 유연하게 변경할 수 있도록 설계 했다.
주문과 할인 도메인 개발
할인 정책 인터페이스
package hello.core.discount;
import hello.core.member.Member;
public interface DiscountPolicy {
int discount(Member member ,int price);
}
할인정책을 유연하게 변경하기위해 인터페이스로 생성
리턴값은 int형 discount 변수에 member,price를 받아서 리턴
할인 정책 구현체
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class FixDiscountPolicy implements DiscountPolicy{
private int DiscounFixAmount = 1000;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return DiscounFixAmount;
} else {
return 0;
}
}
}
할인정책의 구현체(고정할인)
member를 파라미터로 받았을때 객체의 등급이 VIP일때 DiscounFixAmount 리턴
아니면 기본등급이기때문에 0원을 리턴합니다.
주문 엔티티
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 finalPrice(){
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 +
'}';
}
}
주문생성시 필요한 필드값으로 회원id,상품명,상품가격,주문결과를 리턴할때 할인가를 리턴해야함으로
private Long memberId; //회원id
private String itemName; //상품명
private int itemPrice; //상품가격
private int discountPrice; //할인가
이라는 필드 생성한다.
그리고 파라미터를받아와 객체를 초기화하기위한 생성자 생성,
정보은닉화와 객체의 다향성을 위해 게터와 세터 생성
주문 서비스 인터페이스
package hello.core.order;
public interface OrderService {
Order orderCreate(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.MemberMemoryRepository;
import hello.core.member.MemberRepository;
public class OrderServiceImpl implements OrderService{
MemberRepository memberRepository = new MemberMemoryRepository();
DiscountPolicy discountPolicy = new FixDiscountPolicy();
@Override
public Order orderCreate(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.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.*;
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 MemberServiceImp();
OrderService orderService = new OrderServiceImpl();
Long memberId = 1L;
Member member = new Member(memberId, "승빈", Grade.VIP);
memberService.회원가입(member);
Order order = orderService.orderCreate(1L, "몰통", 10000);
System.out.println("order = " + order);
}
}
결과
할인 금액이 1000원으로 잘 실행되는걸 확인 할 수 있지만, 애플리케이션 로직으로 이렇게 테스트 하는거는 좋은 방법이 아니기때문에
JUnit을 이용해서 테스트 해자!.
주문과 할인 정책 테스트(JUnit 테스트)
package hello.core.order;
import hello.core.member.*;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
public class OrderServiceTest {
@Test
void join(){
OrderService orderService = new OrderServiceImpl();
MemberService memberService = new MemberServiceImp();
MemberRepository memberRepository = new MemberMemoryRepository();
//given
Long memberId = 1L;
Member member = new Member(memberId, "승빈", Grade.VIP);
memberService.회원가입(member);
//when
Order order = orderService.orderCreate(memberId, "물통", 10000);
//then
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
결과
할인금액을 확인할 회원을 가입시켜 주문객체를 만들어 Assertions API를 사용해서 회원의 할인금액이 1000이 맞는지 확인해 테스트를 통과하였다.
'강의 > 스프링_핵심원리_기본' 카테고리의 다른 글
스프링 컨테이너와 스프링 빈-스프링 핵심원리 기본 (0) | 2022.04.29 |
---|---|
스프링 핵심 원리 이해2 - 객체지향원리 적용 (0) | 2022.04.26 |
스프링, JPA의 탄생 (0) | 2022.04.14 |