21.04.02-03 TIL - Spring 핵심원리 - 1편

김정훈·2021년 4월 3일
0

Spring

목록 보기
4/4
post-thumbnail

📌 Spring 핵심원리 - 1편

<출처 : 인프런 Spring 핵심원리 : 김영한 강사님>

https://www.inflearn.com/roadmaps/373

Spring 포스팅 시작하기 전에

Spring 관련 포스팅은 인프런의 김영한님의 강의를 듣고 정리하는 것이기 때문에, 디테일한 소스코드 까지는 첨부할 수 없는 점 양해 부탁드립니다.

  • 김영한님 강의를 들어보신 분은 제 글을 보고 정리하시는데 도움이? 되실겁니다.😄
  • 더 깊게 이해하고 싶으시다면 강의를 들으시는 걸 추천드립니다!

⌘ 인터페이스를 통한 예제, SOLID원칙 중 SOD만 간단히,의존성 주입

SOLID중 S,O,D만 간단히
SRP 단일 책임 원칙 : 한 클래스는 하나의 책임만 가져야 한다.
DIP 의존 관계 역전 원칙 : 프로그래머는 추상화(InterFace)에 의존해야하고, 구체화(구현체)에 의존하면 안된다.
OCP 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

➡️간단한 기능을 구현하면서 공부

  • 회원
    회원가입, 조회
    회원은 일반, VIP 두 가지 등급
    <미확정> 자체DB or 외부 시스템과 연동 → 인터페이스를 통한 구현이 필요
  • 주문, 할인 정책
    회원은 상품을 주문할 수 있다.
    회원 등급에 따라 할인 정책 적용이 가능하다.
    할인 정책은 변경 가능성이 높다. → 인터페이스를 통한 구현이 필요

다이어그램을 한 번 보면 Interface가 MemeberService, MemberRepository 총 2가지가 있다.
우선은 MemberRepository부분을 확인해보자.

⚡️MemberRepository(Interface) - MemoryMemberRepository? DbMemberRepository?

요구사항처럼 자체DB, 외부 시스템 연동을 해야할지 정해지지 않은 상황이고, 이 강의의 예제에서는 DbMemberRepository는 만들지 않고, MemoryMemberRepository를 통해서 처리한다.

이런 방식으로 구현하는 이유를 간단한 예시를 살펴보자.
ex. A라는 회사는 에어컨을 만들고 있다.
그리고 그 에어컨에 맞는 필터의 최소한의 조건(interface)이 있다.
B공장, C공장에 최소한의 조건(interface:MemberRepository)를 주면서 필터를 만들어 오라고 지시한다.
그렇다면 B공장(MemoryMemberRepository)를 만들어왔고, C공장(DbMemberRepository)를 만들어왔다.
이제 A회사는 B공장이 만들어서 온 것이 마음에 들어서 B공장의 필터로 결정했다.
??? 갑자기 B공장이 망했다...
상관없다. A회사는 C공장의 필터를 선택하면 된다.

  • 이해를 돕기 위한 극단적?예시이다. 디테일하게 들어간다면 이렇게 단순하게 처리가 안되는 부분도 존재할 것이다.
  • 만약 DBMemberRepository를 사용해서 해보고 싶다면 김영한 강사님의 스프링 무료강의를 한 번 들어보는 것을 추천드립니다.

🔷First. 도메인 개발

➡️ 회원등급을 담당하는 ENUM클래스 public enum Grade를 생성한다.
➡️ 회원의 속성을 담당하는 public class Member를 생성(id, name, grade)를 가진다. getter,setter도 생성해준다.
➡️ 회원 저장소 인터페이스인 MemberRepository를 만들어준다.![]

public interface MemberRepository {

    void save(Member member);
    // 회원을 저장을 하는 역할을 한다.

    Member findById(Long memberId);
    // 회원 아이디를 찾는 역할을 한다.
}

➡️ 이제 이 인터페이스를 구현하는 구현체 MemoryMemberRepository(B공장)를 구현해준다.

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 class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository = new MemoryMemberRepository();
	// 지금 보면 MemberRepository와 MemoryMemberRepository 두 가지다 의존중이다. 최악이다 ==> 추후 수정
       
    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

테스트 코드 부분은 생략.(But 테스트 코드 작성은 중요!, 유료+무료 강의참조)

🔱WARNING : 지금까지 코드를 보면 MemberServiceImpl클래스가 인터페이스+구현체까지 의존하는 문제점이 있다. 이 문제는 AppConfig라는 연결을 담당하는 별도의 클래스를 만들어서 처리한다.

🔷Second. 주문과 할인 도메인 개발

⚙️할인
➡️ 할인 정책 부분을 보면, 회원 저장소 부분과 동일한 방식으로 구현되는 것을 확인할 수 있다.
➡️ 할인 정책 인터페이스를 하나 만들고 int discount(Member member, int price); 그리고 정액 할인 구현체, 정률 할인 구현체를 만들어준다. 두 구현체 모두 if문를 사용하면 어렵지 않게 구현할 수 있다.
⚙️주문
➡️ 주문 서비스 인터페이스와, 구현체를 만들어보자.
➡️ InterFace로 Order createOrder(Long memberId, String itemName, int itemPrice)로 만들어주고 OrderServiceImpl을 만들어주자.

public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    // 두 가지중에 하나 선택
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
   
    @Override
    public Order creatOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

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

주문 요청 → 회원 정보 조회 → 할인 정책 적용 후 다음 주문 객체 생성해서 반환

🔱WARNING : 그럼 이제 인터페이스+구현체를 모두 의존하는 문제를 한 번 처리해보자.(모두 의존하면 안되는 이유는 좋은 객체 지향의 SOLID원칙 때문인데, 이 부분은 한 번 찾아보면 좋을 것 같다. 정리하게 된다면 이곳에 링크를 걸어둘 예정이다.) 지금은 다이어그램으로 표현하면 이런 상황이다.

♻️ 내가 원하는 다이어그램은 이런 그림이다.
정리하자면 OrderServiceImpl, MemberServiceImpl은 인터페이스만 의존하도록 설계하는 것이다.
그림과 같이 만들려면 값을 할당해주는 것을 별도의 설정 클래스를 만들고, 지금 코드 부분은
private DiscountPolicy discountPolicy; 이렇게 만들어준다.(값이 할당되지 않았다.)
그리고 이 값의 할당은 별도 클래스인 AppConfig를 통해서 주입받는다.

이러한 과정을 테스트코드를 통해서 한 번 정리해보자.

class MemberServiceImplTest {

    MemberService memberService;

    @BeforeEach
    public void beforeEach(){
        AppConfig injection = new AppConfig();
        memberService = injection.memberService();
    }
    // 이 부분이 중요하다. 테스트를 실행하기 전에 AppConfig를 통해서 의존성을 주입시켜준다.
    
    @Test
    void join() {

        //이렇게 주어지고(given)
        Member member = new Member(1L, "memberA", Grade.VIP);

        // 이렇게 실행한다면(when)
        memberService.join(member);
        Member findMember = memberService.findMember(1L); // Id가 1L인것을 찾아라.

        // 결과확인(then)
        assertThat(member).isEqualTo(findMember);
    }
}

@BeforeEach부분의 흐름
injection.memberService() → AppConfig.memberService() → memberService는MemberServiceImpl(memberRepository())을 리턴한다. → 파라미터 memberRepository는 memoryMemberRepository를 할지, DbMemberRepository를 할지 리턴한다, 여기서는 memoryMemberRepository로 정하자
⟹ MemberServiceImpl(memoryMemberRepository()) 인 셈이다. 하지만 MemberServiceImpl은 memory인지 db인지 모른다.
⟹ why? 이 부분은 AppConfig에서 처리해주는 부분이기 때문이다. 즉 구현체 부분은 모르고, 오직 InterFace인 memberRepository에만 의존한다.
⥤⥤ 이런 과정을 통해서 memoryMemberRepository를 저장소로 가지는 memberService가 만들어진다.

@Test부분
위와 같이 만들어진 memberService객체를 이용해서 MemberServiceImpl에서 구현된 join, findMember 함수를 실행할 수 있게 된다.

➽ 정리 : 인터페이스를 통해서 구현하는 법과, 별도 설정 클래스를 통해서 의존성을 주입해주는 방식으로 회원가입, 아이디찾기 같은 방법을 간단히 구현했다.

profile
WebDeveloper

0개의 댓글