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

heyhey·2023년 1월 30일
0

Spring

목록 보기
9/23

[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개의 댓글