[Spring] 기본편 02. 스프링 핵심 원리 이해1 - 예제 만들기
이 글은 스프링 핵심 원리 - 기본편 을 듣고 정리한 내용입니다.
📌 프로젝트 세팅
- https://start.spring.io/ 에서 다음과 같이 설정하고 GENERATE를 통해 다운받기.
- Intellij에서 [Open or Import]-> 다운받은 [core] 폴더 안에서 -> build.gradle 누르고 [Open] -> [Open as Procject]
- 이번 강의에서는 순수 java로만 진행해볼 거라서 Dependencies는 선택하지 않음.
- Intellij IDEA -> preference에서 'gradle'입력하고 'Build and run using' 부분과 'Run tests using' 부분을 gradle->Intellij IDEA로 바꿔준다.
- 이렇게 설정해야 Java를 실행할 때, Gradle을 거치지 않고, Intellij에서 바로 실행해서 더 빠름.
📌 회원 도메인 설계
[회원 도메인 요구사항]
- 회원을 가입하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)
- 도메인 협력관계는 기획자들도 볼 수 있음.
이것을 바탕으로 개발자가 구체화에서 클래스 다이어그램을 만들어냄.
📌 회원 도메인 실행과 Test
- Member.class
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;
}
}
- Grade.enum
package hello.core.member;
public enum Grade {
BASIC,
VIP
}
- MemberRepository.interface
package hello.core.member;
public interface MemberRepository {
void save(Member member);
Member findById(Long memberId);
}
- MemoryMemberRepository.class
(메모리에 존재하는 MemberRepository)
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);
}
}
- MemberService.interface
package hello.core.member;
public interface MemberService {
void join(Member member);
Member findMember(Long MemberId);
}
- MemberServiceImpl.class
package hello.core.member;
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository = new MemoryMemberRepository();
//memberRepository interface필요, but interface만 가지고 있으면 nullPointerException 터질것임.
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
- MemberApp.class
package hello.core.member;
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("findMember = "+findMember.getName());
}
}
Member TestCode 작성
-
폴더가 main, test 폴더로 나뉘어져 있는데 나중에 빌드해서 나가면 main에 대한 코드만 나가고, test에 대한 코드는 빌드될 때 빠진다.
-
Test Code 작성은 이제 필수임!
-
Test 디렉토리에 member패키지 만들어서 그 안에 작성.
- Test Code는 given, when, then으로 나눠서 작성하면 안헷갈림.- then의 Assertions.assertThat 부분에서 검증 이루어짐.
-
MemberServiceTest.java
package hello.core.member;
public class MemberServiceTest {
MemberService memberService = new MemberServiceImpl();
@Test // 이 어노테이션이 import 되어야 함.
void join(){
//givn - 멤버 생성
Member member= new Member(1L, "memberA", Grade.VIP);
//when - 멤버 회원가입하고, 그 멤버 찾음.
memberService.join(member);
Member findMember = memberService.findMember(2L);
//then //여기서 검증
Assertions.assertThat(member).isEqualTo(findMember);
}
}
📌 주문과 할인 도메인 설계
- 주문과 할인 목록
- 회원은 상품을 주문할 수 있다.- 회원 등급에 따라 할인 정책을 적용할 수 있다.
- 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있음)
- 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수도 있음(미확정)
- 주문 도메인 협력, 역할, 책임
- 주문 생성 : 클라이언트는 주문 생성을 요청한다.
- 회원 조회 : 할인을 위해서는 회원 등급이 필요하다. 그래서 주문 서비스는 회원 저장소에서 회원을 조회한다.
- 할인 적용 : 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임한다.
- 주문 결과 반환 : 주문 서비스는 할인 결과를 포함한 주문 결과를 반환한다.
- 역할먼저 만들고, 그 다음에 구현을 만들었다.
- 즉 역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있다.
- 덕분에 회원 저장소는 물론이고, 할인 정책도 유연하게 변경할 수 있다.
ex) 미래에 할인 정책이 바뀌면, 할인 정책 부분만 바꿔 끼우면 됨.
-
Interface에 대한 구현체가 1개만 있으면 보통 InterfaceImpl 이라고 적는다. -> (Interface이름)+(Impl)
-
할인 정책은 고정 할인정책과 정률 할인정책이 있음.
- 객체 다이어그램은 클래스 다이어그램과 다르게, 실제 new로 생성해서, 동적으러 객체들의 연관관계가 맺어지는 그림이다.
- 회원을 메모리에서 조회하고, 정액 할인 정책(고정금액)을 지원해도 주문 서비스를 변경하지 않아도 된다.
-> 역할들의 협력 관계를 그대로 재사용 가능.
- 회원을 메모리가 아닌 실제 DB에서 조회하고, 정률 할인 정책(주문 금액에 따라 % 할인)을 지원해도 주문 서비스를 변경하지 않아도 된다.
-> 협력 관계를 그대로 재사용 할 수 있다.
📌 주문과 할인 도메인 실행 및 Test
-
디렉토리 구조는 다음과 같다.
-
먼저 Order 패키지를 생성하여 주문 관련된 파일들을 만든다.
-
OrderService의 주요 기능은 주문을 생성하는 것인데 회원ID, 상품명,상품 가격을 넘겨받아서 주문을 리턴한다.
-
Order.class
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 +
'}';
}
}
- OrderService.interface
package hello.core.order;
public interface OrderService {
Order createOrder(Long memberId, String itemName, int itemPrice);
// 주문 생성할때, 회원id, 상품명, 상품 가격 넘김.
}
- OrderServiceImpl.class
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 );
}
}
- 설계가 잘됐다고 할 수 있음.
- 왜냐하면 orderservice 입장에서는 할인에 대해서는 모름.
- 할인에 대해서는 discountPolicy에서 알아서 해주므로, 단일 책임 원칙을 잘 지킴.
- 할인에 대한 변경이 필요하면 주문쪽까지 안가고, 할인쪽 부분만 수정하면 됨.
discount 패키지 생성
- DiscountPolicy.interface
package hello.core.discount;
import hello.core.member.Member;
public interface DiscountPolicy {
/**
* @return 할인 대상 금액
*/
int discount(Member member, int price);
}
- FixDiscountPolicy.class
- 회원 등급이 VIP인 경우에만 1000원 할인
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;
}
}
}
- OrderApp.class
- 멤버 생성 후, join하고 주문 생성후 주문정보 출력 및 할인 적용된 금액 출력
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);
System.out.println("order.calculatePrice= "+order.calculatePrice());
}
}
Order TestCode 작성
TEST 폴더에 Order 패키지 생성하고, 그 안에 작성
- OrderServiceTest.class
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 패키지와 order 패키지가 들어있는 hello.core패키지에서 실행시켜주면 됨.
Author And Source
이 문제에 관하여([Spring] 기본편 02. 스프링 핵심 원리 이해1 - 예제 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@somyeong0623/스프링-기본편-02.-스프링-핵심-원리-이해1-예제-만들기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)