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

chaen-ing·2024년 6월 2일
0

Spring 공부

목록 보기
5/13

프로젝트 생성

https://start.spring.io 사이트에서 스프링 프로젝트 생성

  • project : Gradle-Groovy
  • Spring Boot : 가장 최신 안정화 버전 (3.x) → java 17 이상, javax 패키지 이름을 jakarta로 변경
  • Language : Java
  • Packaging : Jar
  • Java : 17
  • groupId: hello
  • artifactId: core
  • Dependencies: 선택하지 않는다.

비즈니스 요구사항과 설계

회원

  • 회원을 가입하고 조회할 수 있다.
  • 회원은 일반과 VIP 두 가지 등급이 있다.
  • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)

주문과 할인 정책

  • 회원은 상품을 주문할 수 있다.
  • 회원 등급에 따라 할인 정책을 적용할 수 있다.
  • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)
  • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)

미확정인 부분이 결정될때까지 기다릴 수 없음 → 객체 지향 설계 : 역할과 구현을 구분하여 해결하도록 한다

회원 도메인 설계 & 개발 & 실행

회원

  • 회원을 가입하고 조회할 수 있다.
  • 회원은 일반과 VIP 두 가지 등급이 있다.
  • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다. (미확정)

저장소는 아직 미정임

→ 메모리 회원 저장소를 임시로 사용하기로 결정

회원 클래스 다이어그램

멤버서비스 인터페이스와 멤버서비스 구현(Implement) : 회원 서비스멤버 리포지토리 : 회원 저장소 → 구현체를 메모리 멤버 저장소, DB 회원 저장소로 구분 (외부 시스템 연동 회원 저장소도 추가 가능)

해당 그림으로 실제 개발을 한다고 보면됨

hello.core 폴더안에 member package 생성

  • Grade enum : 회원 등급
  • Member Class : 회원 정보. constructor, getter, setter
  • MemberRepository : 멤버 저장소 인터페이스 save 기능, findById 기능
  • MemoryMemberRepository 해쉬 맵 사용하여 인터페이스 함수 기능 구현 → put : 저장, get : 값 가져옴
  • MemberService : 회원 서비스 인터페이스 join 기능, findMember 기능 → 회원 가입, 회원 조회
  • MemberServiceImple : 회원 서비스 인터페이스 구현
    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);
        }
    }
    멤버저장소 인터페이스에 메모리 멤버 저장소 구현체 대입 join함수에 save, findMember에 findById 연결해 줌으로서 구현 완료

테스트

package hello.core;

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

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("find Member = " + findMember.getName());  // 회원 조회해서 찾은 멤버

    }
}

main 어플리케이션 대신 test 어플리케이션에서 테스트 가능 : Assertions를 활용

package hello.core.member;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class MemberSerivceTest {
    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);
    }
}

테스트 돌렸을 때 실패하면 빠르게 캐치 가능. 사용하면 좋다

회원 도메인 설계의 문제점

  • OCP 원칙 위반
  • DIP 원칙 위반 : private final MemberRepository memberRepository = new MemoryMemberRepository(); 위의 구문에서 memberRepository가 인터페이스뿐만아닌 구현체에도 의존

주문과 할인 도메인 설계 & 개발 & 실행

주문과 할인 정책

  • 회원은 상품을 주문할 수 있다.
  • 회원 등급에 따라 할인 정책을 적용할 수 있다.
  • 할인 정책은 모든 VIP는 1000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경 될 수 있다.)
  • 할인 정책은 변경 가능성이 높다. 회사의 기본 할인 정책을 아직 정하지 못했고, 오픈 직전까지 고민을 미루고 싶다. 최악의 경우 할인을 적용하지 않을 수 도 있다. (미확정)

위와같이 역할과 구현을 분리할 수 있음

  1. 주문 생성 : 클라이언트는 주문 서비스에 주문 생성을 요청
  2. 회원 조회 : 할인을 위해 주문서비스는 회원 저장소에서 회원 조회
  3. 할인 적용 : 주문 서비스는 회원 등급에 따른 할인 여부를 할인 정책에 위임
  4. 주문 결과 반환 : 주문 서비스는 할인결과를 포함한 주문 결과 반환

DB멤버 저장소로 변경하거나 정률 할인 정책으로 변경하는 것이 쉬움

구현체만 갈아끼우면 됨

현재는 메모리 멤버 저장소, 정액 할인 정책을 사용할 것

Discount 패키지

  • Discount Policy interface : 회원정보 조회해서 할인 금액 리턴
  • Fix Discount Policy class
    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;
            }
        }
    }
    

order 패키지

  • Order 클래스 : 회원id, 상품명, 상품가격 할인가격 리턴하는 함수 하나
  • Order Service interface
  • Order Service Impl
    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 memeber = memberRepository.findById(memberId);   // 회원정보 조회
            // 할인정책에 멤버 넘김 : 할인 정책 클래스에서 회원 등급에 따른 할인 금액을 계산해서 리턴해줌
            int discountPrice = discountPolicy.discount(memeber, itemPrice);
            return new Order(memberId, itemName, itemPrice, discountPrice);
        }
    }
    

test

package hello.core.order;

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

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

관련 채용 정보