스프링 핵심 원리 - 기본편 : 예제 만들기

jkky98·2024년 6월 24일
0

Spring

목록 보기
5/77

개발 요구 사항

  • 회원
    • 회원을 가입하고 조회할 수 있다.
    • 회원은 일반과 VIP 두 가지 등급이 존재한다.
    • 회원 데이터는 자체 DB를 구축할 수도, 외부 시스템과 연동할 수도 있다 (미확정)
  • 주문과 할인 정책
    • 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책을 적용할 수 있다.
    • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라 근데 나중에 변경될 수도 있다.
    • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 정하지 못함. 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수도 있다.

개발 초기 설계

  • DiscountPolicy(interface)
    • FixDiscountPolicy
  • MemberRepository(interface)
    • memoryMemberRepository
  • MemberService(interface)
    • MemberServiceImpl
  • OrderService(interface)
    • OrderServiceImpl
  • Order, Grade(Enum), Member

Order, Grade, Member는 인터페이스 없이 존재하는 클래스이다. 각각은 주문정보객체, Member의 등급을 가지는 Enum클래스, 회원정보객체이다.

코드 설명

DiscountPolicy


public interface DiscountPolicy {

    int discount(Member member, int price);

}
///// impl /////
package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;

public class FixDiscountPolicy implements DiscountPolicy{

    private int discountFixAmount = 1000;

    @Override
    public int discount(Member member, int price) {
        if (member.getGrade() == Grade.VIP) {
            return discountFixAmount;
        } else {
            return 0;
        }
    }
}

구현 메서드인 discount는 Member와 price를 인자로 받아 VIP, BASIC에 따라 할인 금액을 리턴한다. 이 클래스는 OrderService에서 사용된다.

MemberRepository

package hello.core.member;

public interface MemberRepository {
    void save(Member member);
    Member findById(Long memberId);
}
////// impl ///////
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);
    }
}

MemberRepository는 회원을 저장하는 공간으로 save() 저장기능과 findById Id로 하여금 회원 조회 기능이 존재한다. 구현체인 MemoryMemberRepository는 일단 프로토타입으로써 DB를 사용하지 않고 java의 collection의 Map을 사용해서 저장한다.(휘발성이 일단 강하다 개발단계니까 잠깐 감수)

MemberService

package hello.core.member;

public interface MemberService {
    void join(Member member);

    Member findMember(Long memberId);
}
//// impl ////
package hello.core.member;

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

MemberService는 join MemberRepository에 Member객체를 저장하는 기능과 멤버를 찾는 기능이 존재한다. MemberRepository가 가진 기능을 복붙하듯이 구현된 모습이다.

예제코드가 벌써부터 불편한 지점이 있다. private final MemberRepository memberRepository = new MemoryMemberRepository(); 멤버 서비스에서 이렇게 고정될 경우 DIP가 깨진다. 아마 추후에 고치지 않을까 싶다. 자바 강의에서 배웠듯 결정을 뒤로 미뤄야한다. 그러기 위해서는 private final MemberRepository memberRepository; 만 선언하고 생성자로 받아 사용하는 방식을 취해 MemberService에서 사용될 리포지토리를 MemberService를 호출한 곳에서 결정할 수 있도록 결정을 미루면 리포지토리가 변경되더라도 MemberService에는 수정을 가하지 않아도 된다. SOLID를 지킬 수 있다. 미래에 수정을 통해 지켜지겠지하며 넘어가도록 한다.

Order

package hello.core.order;

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

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
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();
    private DiscountPolicy discountPolicy;

    public OrderServiceImpl(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

    @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에서는 createOrder 즉 주문을 생성한다. 이번 챕터에서는 Order부분까지 생성자 추가로 DI를 적용하여 DiscountPolicy를 미래에 결정할 수 있다. 아마 MemberRepository부분도 바뀌지 않을까 싶다.

TEST

테스트 코드는 하나만 첨부

package hello.core.order;

import hello.core.discount.RateDiscountPolicy;
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;

public class OrderServiceTest {

    MemberService memberService = new MemberServiceImpl();
    OrderService orderService = new OrderServiceImpl(new RateDiscountPolicy());
    @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);
    }
}

멤버를 생성하고 조인하고 주문정보를 생성했을 때(10000원이면서 VIP) 해당 주문의 할인금액이 1000원이면 테스트 통과 방식이다.

profile
자바집사의 거북이 수련법

0개의 댓글