[Spring 기본] 1. 스프링 핵심 원리 이해

Spring 기초편 강의를 보면서 스프링프레임 워크를 사용해보며 사용법을 알 수 있었다.
기본편을 통해 스프링이 어떤 원리로 돌아가는지, 왜 쓰는지 이해해보자

예제 만들기

스프링을 사용하지 않았을 때, 어떤 방법으로 흐름이 흘러가는지 이해해 보자

프로젝트를 생성한다. 개발 환경을 세팅하기에는 많은 시간이 소비되기때문에 스프링 부트 스타터 사이트를 이용한다.

https://start.spring.io

프로젝트 선택

  • Project: Gradle - Groovy Project Spring Boot: 2.3.x
  • Language: Java
  • Packaging: Jar
  • Java: 11
    자바 11버전을 사용하면 2.3.X 버전을 선택하지 않으면 오류가 난다.
    3 이상의 버전을 선택하려면 자바 17 이상을 사용해야 한다.

Project Metadata

groupId: hello
artifactId: core

IntelliJ의 Preference를 통해 Gradle 대신에 자바 직접 실행으로 변경
-> 실행속도 향상

비즈니스 요구 사항

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

회원 도메인 개발

회원 엔티티


Member

id,name,grade 가 존재한다. grade는 Grade 를 사용한다(getter,setter)

Grade

BASIC 과 VIP가 존재한다.
enum으로 생성해준다.

public enum Grade {
  BASIC,
  VIP 
}

회원 저장소 (Repository)


여기부터 중요한 내용이다.
우선은 메모리를 이용해 사용하지만 추후에 데이터 베이스를 이용할 예정이다.

회원 저장소 인터페이스

public interface MemberRepository {
      void save(Member member);
      Member findById(Long memberId);
}

메모리 회원 저장소 구현

store를 HashMap을 사용해서 만들어 준다.

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

회원 서비스

회원 서비스 인터페이스

public interface MemberService {
  void join(Member member);
  Member findMember(Long memberId);
}

회원 서비스 구현

현재는 저장소MemoryMemberRepository를 사용한다는 사실을 기억해두자.

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

테스트

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

지금까지의 코드들은 작동 잘 된다. 하지만 이 코드들은 SOLID 원칙을 잘 준수하고 있을까?
1. 다른 저장소로 변경할 때(Repository) OCP 원칙을 잘 준수하나?

  • OCP : 개방-폐쇄 원칙
  1. DIP를 잘 지키나?
  • 의존성 역전 원칙
    => 의존 관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제가 있다.

일단은 주문까지 설계하고 다시 돌아보도록 하자

주문과 할인 도메인

할인 인터페이스

discount(멤버,가격) => 할인 금액 return

public interface DiscountPolicy {
  int discount(Member member, int price);
}

정액 할인 구현 (고정 할인값)

member가 VIP이면 고정 할인값을 돌려준다

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

주문 엔티티


Order

memberId,itemName,itemPrice,discountPrice 가 존재한다.
(Constructor,getter,setter)

주문 서비스 인터페이스

public interface OrderService {
  Order createOrder(Long memberId, String itemName, int itemPrice);
}

주문 서비스 구현체

주문 생성 요청이 오면, 회원 정보를 조회하고, 할인 정책을 적용한 뒤 주문 객체를 생성해서 반환한다.

여기서도 마찬가지로 저장소를 MemoryRepositoryFixDiscountPolicy를 사용한다는 점을 기억해두자

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


#### 주문 Test
```java
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);
  }
}

지금까지 저장소와, 서비스, 엔티티를 만들어 프로젝트를 만들어 보았다.
의존 관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제를 어떻게 해결해줄지 다음 챕터에서 확인해보자

profile
주경야독

0개의 댓글

Powered by GraphCDN, the GraphQL CDN