본문 바로가기
행위 돌아보기

[동영상 정리]의존성을 이용해 설계 진화시키기 - 조영호 님

by simplify-len 2020. 8. 21.

의존성을 어떻게 관리하는가? 그것이 핵심☢️

변경에 의한 영향

의존성의 정의란 무엇일까?

A가 B에 의존할수록 위 그림과 같이 점선으로 그린다. 이 그림의 의미는 B가 변경될 때 A도 같이 변경 될 수 있다. 라는 것을 의미한다. 그러므로, B가 뭐가 됐던 A가 같이 변경 될 수 있음을 의미합니다.

B클래스의 변경에 따라서 A에 영향을 줄수 또는 안줄 수도 있다. 

클래스 의존성의 종류

A에서 B로 이동할 수 있어요

 

일시적으로 협력관계에 있는 것.
B가 변경될 때 A도 변경된다.
Operation에서 시그니처가 변경될 경우 영향을 미치는 것

패키지 의존성

패키지에 포함된 클래스 사이의 의존성

import에 다른 패키지의 이름이 있다면 디팬던시가 있다고 생각하면 된다.

설계 할 때 좋은 규칙이 몇가지 있다.

양방향일 경우, 싱크를 맞추는데- 비용이 많이 든다.

다중성이 적은 방향으로 선택하라
의존성이 필요없다면 제거하라.

패키지 세계에 사이클이 돈다라는 것은 하나의 패키지라고 봐야 한다.

패키지 사이의 의존성 사이클을 제거하라.

 

의존성을 가장 쉽게 이해할 수 있는 방법은 역시 코드로 설명하기.

배달의 민족 주문 플로우

가게선택 > 메뉴선택 > 장바구니 담기 > 주문완료

도메인 컨셉 - 가게&메뉴
도메인오브젝트 - 가게&메뉴

주문이 발생할 것인데,

 

메뉴와 주문을 붙이면 아래 그림과 같이 될 것입니다.

물건을 주문하고, 장바구니에 담습니다.

그러나, 

1인 세트 메뉴가 있다가 0.5인세트로 바꾸고, 가격도 바꿀 경우.

핸드폰에 담긴 장바구니와 불일치가 일어날 것이다. 그럼 이 문제를 어떻게 해결해야 될까?

메뉴의 이름과 주문항목의 이름 비교 하고, 옵션 그룹의 이름과 주문옵션의 이름 비교해야 합니다.

 그 이후로, 

많은 Validation이 존재한다. 최소 주문 요청한게 넘어섰는지 확인해야 한다.

Validation을 하는 협력을 설계해봅시다.

어떤 원칙에 의해서 설계되었는가는 이전 포스팅을 통해 이해할 수 있습니다.

주문하기() 라는 메세지가 시작포인트 아래는 Validation Flow이다.

다음 가게가 영업중인지 확인해야 합니다.

이제 위 flow를 갖고 설계를 따라가봅시다.

움직이지 않는 것으로 만들어야 합니다. 정적인 무엇가를 찾아야 합니다.

어떤 메소드에 파라미터를 넣거나 등의 행위는 각 타입간에 협력을 하는 것을 말합니다.

 그 중에서도 의존성에 대한 것에 초점을 맞쳐봅시다.

연관관계와 의존관계를 제외한 관계는 명확하나 이 두개는 명확하지 않다.

데이터 흐름을 따라간다.

연관관계는 협력을 위해 필요한 영구적인 탐색 구조를 의미합니다. 의존관계의 경우, 일시적인 것으로, 그 객체가 항상 갈 경로가 필요없습니다. 

일시적으로 파라미터로 받았다가, new 와 같은 것으로 받는 것을 의미합니다. 

연관관계

 

중요한 것은 방향성입니다. 내가 여기로 연관관계가 가는 이유, 여기로 의존관계가 가는 이유가 중요합니다.

런타임에 어떻게 객체들이 협력하냐가 중요합니다.

연관관계를 잠깐 살펴보면, 정의는 탐색 가능성(navigability)

연관관계

내가 A라는 것을 알면 B를 알 수 있습니다.

그리고 이를 코드로 구현하면 단순합니다.

연관관계를 통해 협력

연관관계라는 개념이 있고, 객체 참조라는 구현방법이 있다. 이 둘은 다른 것이다.

예를들어, 어떤 개념을 구현하는 방법이 다양할 수 있다. 라는 말이다.

연관관계를 구현하는 일반적인 방법이 객체 참조라는 것이지, 다른 방법도 있을 수 있다.

객체를 통해서 어떻게 탐색할 거야. 라는 것이다.

메서드가 필요한 이유는 메서드를 받기 때문이다.

메세지를 결정하고 메소드를 만드는 것이 옳은 순서이다.

주문이 올바른지 검증하는 로직 하나, 주문의 상태를 바꾸는 메소드 하나.

그러므로 현재의 상태는 place()라는 주문을 받았습니다.

주문이라는 관계가 항상 있을 것이기 때문에, 위와같은 물리적인 경로를 만들었다.

 

 

이제 개선하기 위한 작업을 수행하자.

아키텍처를 개선할 수 있는 방법이 무엇이냐는 질문에 늘 답하는 대답 중 하나가 바로 의존성을 다시한번 살펴보라고 합니다.

최종 결과물은 이쁠 수 있지만, 과정은 지저분할 수 있습니다.

그러면, 일단 짜고 나서 디펜던시를 보면서 다시 코드를 살펴보면 답이 보입니다.

일단 디펜던시를 한번 그려보며, 설계의 개선점을 찾을 수 있습니다.

 

 그 중에 딱 두가지를 살펴볼 예정입니다.

- 객체 참조로 인한 결합도 상승

- 패키지 의존성 사이클

두가지문제

이걸 다시 표현해보니 Shop과 Order사이에 양방향 관계를 맺고있는 문제가 발생함을 발견할 수 있었다. 즉 양방향

어디서 양방향 문제가 발생했는가?

주문이 shop이 영업중인지 아닌지 여부를 확인하는 메세지를 보냈고, OptionGroupSpecification이 OrderOptionGroup을 가져오고, Option Specification이 OrderOption을 가져와. 즉, 양방향 관계를 지닌다.

Order는 Shop에 의존성, Shop은 OrderOptionGroup, OrderOption에 의존성을 가질 것입니다.

이 문제를 해결하는 방법으로 3가지정도 있는데, 첫번째로

 

실질적으로는 아래와같이 수정합니다.

 

의존성 역전 원리라는 것이 있는데, 클래스들이 구체적인것에 의존하지 않고, 추상적인 부분에 의존하게 해주세요. 라는 것이다.

사람들이 가지고 있는 선입견 중, 추상화는 Interface, Abstract class 여야 한다는 선입견을 가진다. 그러나, 사실 추상화는 변화지 않는 것을 말합니다. 

그러므로, 위 그림과 같이 OptionGroup, Option이 두가지 방향으로 관계를 갖는 것이 의존성 역전 원리의 다른 방법이라고 말할 수 있습니다.

디펜던시를 끊임으로써 문제 해결~

디펜던시를 보면서 연결성을 끊는것을 고민하는 것.

두번째

 

연관 관계라고 하는 것은 어떤 객체에서 다른 객체를 탐색할 수 있는 것을 말합니다.

그리고 결합도가 높아지므로

이러한 경계가 불분명해지면,

그렇다면 어떻게 해야될까? 

 

로그 트랜잭션으로 물려있다.

주문이 들어오면, 결재를 하고 나서 배달 완료라는 flow 를 간단히 살펴보면,

모두가 하나의 Transactional 으로 묶여 있다.

long transaction 으로 물려있는 이것이 경합을 일으킬 수 있다.

 

그럼 객체 참조를 꼭해야만 하는걸까?

해결하는 한가지 방법으로 Repository를 사용.

Repository를 통한 탐색

리파지토리를 중구난방하게 만들수 있는데, 그렇게 만드는 것이 아니라, 이 파라미터로 찾을수 있다는 오퍼레이션.

 

- 함께 생성되고 함께 삭제되는 객체들을 함께 묶어라
- 도메인 제약사항을 공유하는 객체들을 함께 묶어라
- 가능하면 분리하라

도메인 관점에서 묶는데, 위 규칙을 활용하는게 좋다.

어떤 객체를 어떻게 묶겠다 라는 것은 어떤 규칙이 없다. 비지니스 룰에 의해서 결졍된다.





새 객체 OrderValidator 를 생성하고
Validation Logic을 이동시킨 후에 

Validation Logic 모으기

이렇게 하는 것이 좋은 걸까? 나쁜걸까요?

도메인의 제약사항에 의해서 문제가 발생했다. 이걸 같이 바꾸자. 라는 의사결정을 가지고 변경됨.

이게 첫번째 해결방법이고 의존성을 살펴보면

여기서 의존성 사이클 문제가 생긴다.

 

느슨하게 끊고 싶다. 잘게 찢어서 느슨하게 만들고 싶다. 

도메인 이벤트는 '로직간에 로직의 순서를 느슨하게 만들고 싶다' 라는 것이다. 이건 메모리상에서 던지는것이다.

 

Order에 delivered() 라는 이벤트가 발생한다면, 

Order가 Shop을 직접 호출하던 로직을 

 



---

의존성을 컨트롤할 수 있게 된다.

 

댓글