-
Spring 기본1 - 회원 도메인 설계, SOLID 원칙FrameWork/Spring&Spring-boot 2023. 11. 7. 15:37
회원 도메인 설계
회원 도메인 요구사항
- 회원가입을 하고 조회할 수 있다.
- 회원은 일반과 VIP 두 가지 등급이 있다.
- 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)
회원 도메인 협력 관계(역할)
*기획자들도 볼수 있는 그림
회원 저장소라는 레이어를 별도로 만든다. 회원 DB를 자체 구축할 수도 있고 외부 시스템과 연동할 수도 있기 때문이다. 그래서 회원 데이터에 접근하는 계층을 따로 만드는 것이다. 회원 데이터를 어디서 끌어다 쓸지 결정이 되지 않았기때문 일단 메모리를 사용한 회원 저장소를 이용해 개발을 진행하기로 하였다. 메모리 회원 저장소는 test할때도 쓰이고 로컬에서 개발할 때도 쓰이고 할것이다. 이후에 어떤 회원 저장소를 쓸질 결정되면 그 부분만 빠르게 개발하면 된다.
회원 클래스 다이어그램(정적)
실제 구현체로 MemoryMemberRepository를 넣을지 DbMemberRepository를 넣을지는 동적으로 결정된다. 서버가 뜰때 new 연산자로 생성하면 되로 클래스 다이어그램만으로 판단하기 어렵다.
회원 객체 다이어그램(동적)
객체(인스턴스) 서버가 실제로 작동해서 사용하게 될 객체들의 관계를 표현한다.
실제 runtime에 동작을 할 경우 클라이언트는 실제 구현제인 MemberServiecImpl 클래스를 사용하게 되고, MemberServiceImpl은 실제 구현체인 MemoryMemberRepository를 사용하게 된다. 실제 인스턴스 참조 그림은 위와 같아진다.
다형성을 적용한 실제 MemberService와 MemberRepository 예제 코드
package com.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); } }
MemberServiceInple의 join()메서드를 save()를 호출하면 다형성에 의해 memberRepository는 MemoryMeberRepository에 작성되 save 메서드를 호출하게 된다.
회원 객체 다이어그램의 테스트
package com.hello.core.member; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; public class MemberServiceTest { MemberService memberService = new MemberServiceImpl(); @Test void join(){ //given Member memberA = new Member(1L, "memberA", Grade.VIP); //when memberService.join(memberA); Member findMember = memberService.findMember(1L); //then Assertions.assertEquals(memberA,findMember); } }
회원 도메인 설계의 문제점
- 이 코드의 설계상 문제점은 무엇일까?
- 다른 저장소로 변경할 때 OCP원칙을 잘 준수할까?
- DIP를 잘 지키고 있을까?
- 의존 관계가 인터페이스 뿐만 아니라 구현까지 모두 의존하는 문제점이 있다.
- → "주문까지 만들고 나서 문제점과 해결 방안 설명"
💡tip) SOLID 원칙
단일 책임의 원칙(SRP, Single Responsibility Principle) - 모듈이 변경되는 이유는 한가지여야 한다. 변경의 이유가 한가지라는 것은 해당 모듈이 여러 대상 또는 액터들에 대해 책임을 가져서은 안되고, 오직 하나의 액터에 대해서만 책임을 져야한다.는 것을 의미
개방 폐쇄 원칙 (OCP, Open-Close Principle) - 확장에 대해서는 열려있고 수정해 대해서는 닫혀있어야 한다는 원칙
- 확장에 열려 있다 : 요구사항이 변경될 때 새로운 동작을 추가하여 애플리케이션의 기능을 확장할 수 있다.
- 수정에 대해 닫혀있다 : 기존의 코드를 수정하지 않고 애플리케이션의 동작을 추가하거나 변경할 수 있다.
인터페이스 분리 원칙 (ISP, Interface Segregation Principel) - 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공 하는 것이다. 인터페이스 분리 원칙을 준수함으로써 모든 클라이언트가 자신의 관심에 맞는 퍼블릭 인터페이(외부에서 접근 가능함 메시지)만 접근하여 불필요한 간섭을 최소화 할 수 있다.
리스코프 치환 원칙 (LSP, Liskov Substitution Principle) - 하위 타입은 상위 타입을 대체할 수 있어야 한다는 것이다. 즉 해당 객체를 사용하는 클라이언트는 상위 타입이 하위 타입으로 변경되어도, 차이점을 인식하니 못한 채 상위 타입의 퍼블릭 인터페이스를 통해 서브 클래스를 사용할 수 있어야 한다는 것이다.
의존 역전의 원칙 (DIP, Dependency Inversion Principle) - 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되며, 저수준 모듈이 고수준 모듈에 의존해야 한다는 것
- 고수준 모듈 : 입력과 출력으로 부터 먼(비지니스와 관련된)추상화 모듈
- 저수준 모듈 : 입력과 출력으로 부터 가까운 (HTTP, 데이터 베이스, 캐시등과 관련되)구현 모듈생성된 인스턴스가 구체 클래스(구현체)에 의존하고 있는 문제점
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에 선언된 변수는 memberRepository는 MemberRepository type을 가지고 있어 인테페이스에 의존하고 있는 반면 실제 생성된 인터페이스는 구현클래스인 MemoryMemberRepository type으로 생성되어 구체 클래스에 의존하고 있는 모습을 확인할 수 있다.
결과 적으로 MemberServiceImpl은 추상에도 의존하고 구체에도 의존하는 모습을 띄게된다. DIP 원칙을 지키지 못한 모습이다.
[출처 - 스프링 핵심 원리 - 기본편]
'FrameWork > Spring&Spring-boot' 카테고리의 다른 글
Spring 기본3 - 객체 지향 원리 적용, OCP,DIP 위반의 문제 (1) 2023.11.09 Spring 기본2 - 주문 할인 도메인 설계 (1) 2023.11.07 @Configuration과 싱글톤 (0) 2022.01.11 싱글톤 방식의 주의점 (0) 2022.01.11 싱글톤 컨테이너 (0) 2022.01.10