회원을 위한 간단한 예제 프로그램을 작성하려고 합니다. 프로그램을 작성하기 전에 요구 사항과 디자인을 살펴보겠습니다.
이 예제는 Spring을 사용하지 않고 순수한 Java 코드로 계속됩니다.
프로젝트 생성
먼저 Spring 프로젝트를 생성합니다.
요구 사항 및 디자인
회원
- 회원은 로그인하여 문의하실 수 있습니다.
- 회원등급은 일반회원과 VIP회원이 있습니다.
- 구성원 데이터는 자체 데이터베이스를 구축하고 외부 시스템에 연결할 수 있습니다. (미확인)
주문 및 할인 정책
- 회원은 상품을 주문할 수 있습니다.
- 회원 등급에 따라 할인 정책이 적용될 수 있습니다.
- 할인 정책은 모든 VIP에게 1,000원 할인(변경 가능)을 제공하는 정액 할인을 적용합니다.
- 할인 정책은 변경 될 수 있으며 개봉 직전까지 고민을 미루고 싶습니다. 최악의 경우 할인이 적용되지 않습니다. (미확인)
요구 사항을 살펴보면 미확인되거나 변경 될 수 있는 부분이 있습니다. 하지만 정책이 결정되기 전까지는 개발을 게을리 할 수 없기 때문에 앞에서 배운 역할과 구현을 분리하는 방식으로 개발을 진행해 봅시다.
참고 :
Spring Boot를 사용하여 프로젝트 환경을 편리하게 설정하고 개발은 Spring 없이 순수한 Java 코드로만 수행됩니다.
멤버십 도메인 설계 및 개발(테스트)
회원 도메인 요구 사항
- 회원가입, 신청가능
- 회원등급은 일반회원과 VIP회원이 있습니다.
- 멤버 데이터 자체 DB 구축 또는 외부 시스템 연동(미정)
멤버십 도메인 파트너십

클라이언트가 있으며 멤버십 서비스는 필요에 따라 가입 및 멤버십 요청 기능을 제공합니다. 가맹점은 불확실한 상태이므로 가능한 DB 가맹점, 외부 시스템 연결점 및 점포 가맹점을 생각하고 이를 위한 인터페이스로 가맹점을 구현할 수 있다.
멤버 클래스 다이어그램

멤버 개체 다이어그램

- 회원 서비스: MemberServiceImpl
회원사
회원 등급
package hello.core.member;
public enum Grade {
BASIC,
VIP
}
회원사
package hello.core.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;
}
}
가맹점
가맹점 인터페이스
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인지 외부 DB인지 아직 결정되지 않았으므로 먼저 메모리 멤버 저장소를 구현합니다.
회원 서비스
회원 서비스 인터페이스
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);
}
}
구성원 도메인 구현 및 테스트
회원 도메인 – 회원 등록 메인
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 테스트를 사용해 봅시다.
멤버십 도메인 – 멤버십 테스트(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);
}
}
구성원 도메인 설계의 과제
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
이 코드에서 MemberServiceImp는 MemberRepository 인터페이스와 그 구현인 MemoryMemberRepository에 의존합니다.
→ 종속성이 인터페이스뿐만 아니라 구현에도 의존한다는 문제가 있습니다.
주문 및 할인 도메인 설계 및 개발(테스트)
주문 및 할인 정책
- 회원은 상품을 주문할 수 있습니다.
- 회원 등급에 따라 할인 정책이 적용될 수 있습니다.
- 할인 정책은 고정 금액 리베이트를 적용하여 모든 VIP에게 1,000원 할인(변경 가능 O)
- 할인 정책은 변경될 수 있음(미정)
임무 영역 협업, 역할, 책임

1. 주문 생성 : 고객이 주문 서비스에 주문 생성을 요청합니다.
2. 회원 신청 : 할인을 위해서는 회원등급이 필요합니다. 따라서 순서 지정 서비스는 구성원 디렉토리에서 구성원을 검색합니다.
3. 할인 적용 : 주문 서비스는 회원 등급에 따라 할인 정책을 위임합니다.
4. 주문 결과 반송 : 주문 서비스는 할인 결과를 포함한 주문 결과를 반환합니다.
전체 작업 영역

역할수업 화신구현 객체를 자유롭게 조합할 수 있도록 분리 설계되어 있습니다.
예) 역할과 시행이 분리되어 있어 정액환급제도(1,000원 고정)와 고정환율(10%)환급제도로 환원정책의 역할을 자유롭게 구성할 수 있다.
주문 도메인 클래스 다이어그램

주문 도메인 객체 다이어그램 1)

주문 도메인 개체 다이어그램 2)

주문서비스는 회원이 매장에서 문의를 하여도 변경이 필요 없으며 정액할인정책(정액)을 지원하거나 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시 1,000원 할인, 할인 없음!
작업 인스턴스
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 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);
}
}
주문 생성 요청 → 회원 정보 요청 → 할인 정책 적용 → 주문 상품 생성 및 반품
스토리지 구성원 저장소 및 FixDiscountPolicy를 구현으로 작성하십시오.
도메인 주문 및 할인 실행 및 테스트
주문 및 할인 정책 실행 – 가장 중요한 것
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);
}
}
주문 및 할인 정책 테스트 – 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);
}
}