본문 바로가기

스프링/기본편

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

728x90

프로젝트 생성


start.spring.io에 가서 스프링 부트 생성

 

인텔리제이 - open file - 만든 폴더 안에 있는 build.gradle 가져와서 open as project

 

비즈니스 요구사항과 설계


 

 

회원 도메인 설계


 

회원 도메인 협력 관계

 

- 클라, 서비스, 저장소는 역할

- 아직 저장소가 정해지지 않았기 때문에 메모리 저장소를 만들어서 사용

 

회원 클래스 다이어그램

 

회원 객체 다이어그램

 

- 회원 서비스의 구현체는 MemberServiceImpl

 

회원 도메인 개발


회원 엔티티

회원 등급

//회원 등급
public enum Grade {
    Basic,
    Vip
}

 

회원 엔티티

//회원 엔티티
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;
    }
}

 

회원 저장소

회원 저장소 인터페이스

public interface MemberRepository {

    //회원 저장
    void save(Member member);

    //회원 아이디로 찾는 기능
    Member finById(Long memberId);
}

 

메모리 회원 저장소 구현체

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 finById(Long memberId) {
        return store.get(memberId);
    }
}

 

회원 서비스

회원 서비스 인터페이스

//회원 서비스 관련 인터페이스
public interface MemberService {
    //회원 가입, 조회 2가지 기능

    void join(Member member);

    Member findMember(Long memberId);
}

 

회원 서비스 구현체

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.finById(memberId);
    }
}

- 문제점 : 추상화에도 의존하고, 구체화에도 의존(DIP 위반)

 

회원 도메인 실행과 테스트


회원 가입 테스트

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

 

현재의 코드는 의존관계가 인터페이스 뿐 아니라 구현까지 모두 의존하고 있음

 

주문 할인 도메인 설계


 

 

주문 도메인 전체

 

역할과 구현을 분리해서 자유롭게 구현 객체를 조립할 수 있게 설계했다. 덕분에 회원 저장소는 물론이고, 
할인 정책도 유연하게 변경할 수 있다.

 

 

 

협력관계를 그대로 유지할 수 있다는 말은 MemberRepository의 구현체와 DiscountPolicy의 구현체가 변경되어도 주문 서비스 구현체를 변경할 필요가 없다는 뜻이다.

 

주문과 할인 도메인 개발


주문 엔티티

//주문 엔티티
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;
    }
    
    //객체를 출력하면 toString의 결과가 쭉 나오게 됨
    @Override
    public String toString() {
        return "Order{" +
                "memberId=" + memberId +
                ", itemName='" + itemName + '\'' +
                ", itemPrice=" + itemPrice +
                ", discountPrice=" + discountPrice +
                '}';
    }
}

 

주문 서비스 인터페이스

//주문 서비스 역할
public interface OrderService {

    //주문을 생성할 때 1.회원아이디 2.상품명 3.상품가격 파라미터로 넘겨야 함
    Order createOrder(Long memberId, String itemName, int itemPrice);
}

 

주문 서비스 구현체

//주문 서비스 구현체
public class OrderServiceImpl implements OrderService{
    //2개가 필요
    //리포지토리에서 회원 찾아야하기 때문에 필요
    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.finById(memberId);
        //DiscountPolicy로 그냥 정보를 넘겨서 알아서 처리하고 결과를 던지도록 설정
        //회원 정보와 아이템 가격 던져서 FixDiscountPolicy 여기서 계산결과 넘김
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

 

할인 정책 인터페이스

public interface DiscountPolicy {

    //return : 할인 대상 금액
    int discount(Member member, int price);
}

 

할인 정책 구현체

//정액 할인 정책
public class FixDiscountPolicy implements DiscountPolicy{

    private int discountFixAmount = 1000; //1000원 할인

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.Vip){//enum타입은 ==사용
            return discountFixAmount;
        }else{
            return 0;
        }
    }
}

 

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


테스트

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, "itemtA", 10000);
        //할인 금액이 1000원과 같은지 비교
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

 

 

728x90

'스프링 > 기본편' 카테고리의 다른 글

컴포넌트 스캔  (0) 2022.05.22
싱글톤 컨테이너  (0) 2022.05.22
스프링 컨테이너와 스프링 빈  (0) 2022.05.22
스프링 핵심 원리 이해2 - 객체 지향 원리 적용  (0) 2022.05.21
객체 지향 설계와 스프링  (0) 2022.05.21