일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 검색 기능 확장
- H-index
- kubenetes
- @EnableScheduling
- 완주하지 못한 선수
- 영속 자료구조
- @Getter
- 해시
- @Setter
- @configuration
- 가장 큰 수
- 기능개발
- 알고리즘
- 프로그래머스
- 쿠버네티스
- K번째수
- 롬복 어노테이션
- 코딩 테스트
- 커링
- 스택/큐
- Java
- 다리를 지나는 트럭
- 크론 표현식
- @Data
- 고차원 함수
- 모던 자바 인 액션
- 전화번호 목록
- 정렬
- 루씬 인 액션
- 스프링 스케쥴러
- Today
- Total
Today I Learned
04 컴포넌트 (2) 본문
14장. 컴포넌트 결합
지금부터 다룰 세 가지 원칙은 컴포넌트 사이의 관계를 설명한다. 이 장에서도 마찬가지로 개발 가능성과 논리적 설계 사이의 균형을 다룬다.
ADP : 의존성 비순환 원칙
컴포넌트 의존성 그래프에 순환(cycle)이 있어서는 안된다.
많은 개발자가 동일한 소스 파일을 수정하는 환경에선 누군가가 마지막으로 수정한 코드 때문에 망가진 부분을 다시 동작하도록 만들기 위해 코드를 수정하고 또 수정하는 작업이 빈번하게 발생할 수 있다. 다음은 이러한 문제에 대한 해결책이다.
주 단위 빌드(Weekly Build)
개발자가 모두 코드를 개인적으로 복사하여 작업하고, 매주 금요일마다 변경된 코드를 모두 통합하여 시스템을 빌드한다.
이 방식은 5일 중 4일동안 개발자를 고립된 세계에서 살 수 있게 보장해준다는 장점이 있다. 하지만 금요일에 통합과 관련된 막대한 업보를 치뤄야하며, 프로젝트가 커지면 통합 작업이 하루만에 끝나지 않을 수 있다.
개발보다 통합에 드는 시간이 늘어나면서 팀의 효율성도 서서히 나빠진다. 효율성을 유지하기 위해서는 빌드 일정을 계속 늘려야 하고, 빌드 주기가 늦어질수록 프로젝트 감수할 위험은 커진다.
통합과 테스트를 수행하기가 점점 어려워지고, 팀은 빠른 피드백이 주는 장점을 잃는다.
순환 의존성 제거하기
이 문제의 해결책은 개발 환경을 릴리스 가능한 컴포넌트 단위로 분리하는 것이다.
이를 통해 컴포넌트는 개별 개발자 또는 단일 개발팀이 책임질수 있는 작업 단위가 된다.
개발자는 컴포넌트가 동작하도록 만든 후, 해당 컴포넌트를 릴리즈하여 다른 개발자가 사용할 수 있도록 만든다.
컴포넌트가 새로 릴리스되어 사용할 수 있게 되면, 다른 팀에서는 새 릴리스에 대한 적용 시기를 검토하여 사용한다. 적용하지 않기로 했다면 과거 버전을 계속 사용한다.
따라서 특정 컴포넌트가 변경되더라도 다른 팀에 즉각 영향을 주지 않는다. 통합은 작고 점진적으로 이뤄지며, 특정 시점에 모든 작업들을 통합하는 일은 사라진다.
이 절차가 성공적으로 동작하려면 컴포넌트 사이의 의존성 구조가 반드시 관리되어야 한다.
순환이 컴포넌트 의존성에 미치는 영향
순환 의존성이 발생하면, 순환이 존재하는 컴포넌트들은 사실상 하나의 거대한 컴포넌트가 되어버린다.
해당 컴포넌트 개발자들은 모두 서로에게 얽매이게 되고, 항상 동일한 릴리스를 사용해야 한다.
Entities 컴포넌트를 테스트하기 위해서는 Authorizer와 Interactors 까지도 반드시 빌드하고 통합하여야 한다.
Database 컴포넌트를 릴리스하려면 Entities,Interactors 컴포넌트 뿐만 아니라 Authorizer 컴포넌트와도 호환되어야 한다.
순환이 생기면 컴포넌트를 분리하기 어려워질 뿐만 아니라, 단위 테스트를 하고 릴리스를 하는 일도 굉장히 어려워지고 에러도 쉽게 발생한다. 또한 컴포넌트를 어떤 순서로 빌드해야 올바를지 파악하기가 힘들어진다.
순환 끊기
컴포넌트 사이의 순환을 끊고 의존성을 다시 DAG로 원성복구하는 일은 언제라도 가능하다. 이를 위한 주요 메커니즘 두가지를 살펴보자.
- 의존성 역전 원칙(DIP) 적용
user가 필요로 하는 메서드를 제공하는 인터페이스를 생성한다. 이 인터페이스는 Entities에 위치시키고 Authorizer에서는 이 인터페이스를 상속받는다. 이렇게하면 Entities와 Authorizer 사이의 의존성을 역전시키고, 순환을 끊을 수 있다. - 신규 컴포넌트 생성
Entities와 Authorizer가 모두 의존하는 새로운 컴포넌트 만들고, 두 컴퍼넌트가 의존하는 클래스들을 새로운 컴포넌트로 이동시킨다.
흐트러짐(Jitters)
두 번째 해결책이 시사하는 바는 요구사항이 변경되면 컴포넌트 구조도 변경될 수 있다는 점이다.
실제로 애플리케이션이 성장함에 따라 컴포넌트 의존성 구조는 서서히 흐트러지며 또 성장한다.
순환이 발생하면 어떤 식으로든 끊어야하며, 이를 위해 새로운 컴포넌트를 생성하거나 의존성 구조가 더 커질 수도 있다.
하향식(top-down) 설계
지금까지의 살펴본 논의에 대한 결론은 컴포넌트 구조는 하향식으로 설계될 수 없다는 것이다.
컴포넌트는 시스템에서 가장 먼저 설계할 수 있는 대상이 아니며, 오히려 시스템이 성장하고 변경될 때 함께 진화한다.
컴포넌트 의존성 다이어그램은 애플리케이션의 기능을 기술하는 일과는 거의 관련이 없으며, 애플리케이션의 빌드 가능성과 유지 보수성을 보여주는 지도에 가깝다.
따라서 컴포넌트 구조는 프로젝트 초기에는 설계할 수 없다. 하지만 구현과 설계가 이뤄지는 프로젝트 초기에 모듈들이 점차 쌓이기 시작하면서 의존성 관리에 대한 요구도 늘어난다.
변경되는 범위가 가능한 작은 일부로 한정되기를 원하기때문에 단일 책임 원칙과 공통 폐쇄 원칙에 관심을 갖기 시작하며, 이를 적용해 함께 변경되는 클래스는 같은 위치에 배치되도록 만든다.
의존성 구조와 관련된 최우선 관심사는 변동성을 격리하는 일이다. 결국 컴포넌트 의존성 그래프는 자주 변경되는 컴포넌트로부터 안정적이며 가치가 높은 컴포넌트를 보호하려는 아키텍트가 만들고 가다듬게 된다.
아무런 클래스도 설게하지 않은 상태에서 컴포넌트 의존성 구조를 설계하려고 시도하면 상당히 큰 실패를 맛볼 수 있다. 컴포넌트 의존성 구조는 시스템의 논리적 설계에 발마춰 성장하며 진화해야 한다.
SDP: 안정된 의존성 원칙
안정성의 방향으로(더 안정된 쪽에) 의존하라
변경이 쉽지 않은 컴포넌트가 변동이 예상되는 컴포넌트에 의존하게 만들어서는 안된다. 한번 의존하게되면 변동성이 큰 컴포넌트도 변경이 어려워진다.
안정된 의존성 원칙을 준수하면 변경하기 어려운 모듈이 변경하기 쉽게 만들어진 모듈에 의존하지 않도록 만들 수 있다.
안정성
X는 안정된 컴포넌트다. 세 컴포넌트가 X에 의존하며, 따라서 X 컴포넌트는 변경하지 말아야할 이유가 세 가지나 되기 떄문이다. 이 경우 X는 세 컴포넌트를 책임진다라고 말할 수 있다.
반대로 X는 어디에도 의존하지 않으므로 X가 변경되도록 만들 수 있는 외적인 영향이 없다. 이 경우 X는 독립적이다라고 말한다.
안정성 지표
- Fan-in : 안으로 들어오는 의존성. 컴포넌트 내부의 클래스에 의존하는 외부 클래스의 개수
- Fan-out : 바깥으로 나가는 의존성. 컴포넌트 외부의 클래스에 의존하는 내부 클래스의 개수
- I(불안정성) : I = Fan-out / (Fan-in + Fan-out). I가 0이면 가장 안정된 상태, 1이면 가장 불안정안 상태
모든 컴포넌트가 안정적이어야 하는 것은 아니다
모든 컴포넌트가 최고로 안정적인 시스템이라면 변경이 불가능하다.
우리가 컴포넌트 구조를 설계할 때 기대하는 것은 불안정한 컴포넌트로 있고 안정된 컴포넌트도 존재하는 상황이다.
변경 가능한 컴포넌트는 안정된 컴포넌트에 의존하는 상태가 이상적이다.
DIP를 도입해서 변경하기 쉽도록 설계한 컴포넌트에 의존성이 걸리는 상황을 피하기 위할 수 있다.
추상 컴포넌트
오로지 인터페이스만 포함하는 컴포넌트(위 예제에서 UServer)를 생성하는 방식이 상하게 보일 수 있지만, 꼭 필요한 전략이다.
이러한 추상 컴포넌트는 상당히 안정적이며, 따라서 덜 안정적인 컴포넌트가 의존할 수 있는 이상적인 대상이다.
SAP: 안정된 추상화 전략
컴포넌트는 안정된 정도만큼만 추상화되어야 한다.
고수준 정책을 어디에 위치시켜야 하는가?
고수준 아키텍처나 정책 결정과 관련된 소프트웨어는 자주 변경해서는 안된다.
이러한 소프트웨어는 반드시 안정된 컴포넌트(I=0)에 위치해야 한다.
하지만 안정된 컴포넌트에 위치시키면, 그 코드는 수정하기 어려워진다. 이로 인해 시스템 전체 아키텍처가 유연성을 잃는다.
추상클래스를 사용하면 클래스를 수정하지 않고도 확장 가능하도록 유연하게 만들 수 있다.
안정된 추상화 원칙
안정된 추상화 원칙은 안정성과 추상화 정도 사이의 관계를 정의한다.
안정된 컴포넌트는 추상 컴포넌트로 만들어서 쉽게 확장할 수 있도록 해야한다.
불안정한 컴포넌트는 반드시 구체 컴포넌트로 만들어서 내부의 구체적인 코드를 쉽게 변경할 수 있도록 해야한다.
추상화 정도 측정하기
A 지표는 컴포넌트의 추상화 정도를 측정한 값이다. 이 값은 컴포넌트의 클래스 총 수 대비 인터페이스와 추상 클래스의 개수를 단순히 계산한 값이다.
- Nc : 컴포넌트의 클래스 개수
- Na : 컴포넌트의 추상 클래스와 인터페이스 개수
- A : 추상화 정도. A = Na / NC
A 지표는 0~1 사이의 값을 가지며, A가 0이면 컴포넌트에 추상 클래스가 하나도 없다는 뜻이다.
주계열
컴포넌트들은 다양한 추상화와 안정화 정도를 가진다. 추상 클래스로부터 파생된 추상클래스는 추상적이면서도 의존성을 가진다. 의존성으로 인해 안정성이 감소하므로 이 클래스는 최고로 추상적이지만, 최고로 안정적인 것은 아니다.
모든 컴포넌트가 A/I 그래프 상에서 (0,1) 또는 (1,0)에 위치할 순 없으므로 컴포넌트가 위치할 수 있는 합리적인 지점을 정의하는 궤적이 있으리라고 가정할 수 있다.
고통의 구역
(0,0) 주변 영역은 배제해야할 구역이다. 이 컴포넌트는 매우 안정적이며 구체적이다. 추상적이지 않으므로 확장할 수 없고, 안정적이므로 변경하기도 어렵다.
일부 소프트웨어 엔티티는 이 구역에 위치하곤 한다. 예를들어 데이터베이스 스키마는 변동성이 높기로 악명 높으며, 극단적으로 구체적이며, 많은 컴포넌트가 여기에 의존한다. 이러한 이유로 OO 애플리케이션과 데이터베이스 사이에 위치한 인터페이스는 관리하기가 굉장히 어려울 뿐만 아니라, 스키마가 변경되면 대체로 고통을 수반한다.
또다른 예로 유틸리티 라이브러리가 있다. 이러한 라이브러리는 I 지표가 1일지라도, 실제로는 변동성이 거의 없다. String 컴포넌트의 경우, 구체 클래스지만 굉장히 광범위하게 사용되기 때문에 변경 시 혼란을 초래한다. 따라서 String은 변동성이 없다.
변동성이 없는 컴포넌트는 (0,0) 구역에 위치했더라도 해롭지 않으며, 변동성이 큰 컴포넌트일수록 더 문제가 된다.
쓸모없는 구역
(1,1) 주변의 컴포넌트는 쓸모없는 구역이다. 이 컴포넌트는 최고로 추상적이지만, 누구도 그 컴포넌트를 의존하지 않기 때문이다. 이러한 엔티티는 누구도 구현하지 않은 채 남겨진 추상클래스인 경우가 많다.
배제 구역 벗어나기
따라서 변동성이 큰 컴포넌트 대부분은 두 배제 구역으로부터 가능한 멀리 떨어뜨려야 한다. 각 배제 구역에서 최대한 멀리 떨어진 점의 궤적이 바로 주계열이다.
주계열에 위치한 컴포넌트는 추상화된 수준에 어울릴 정도로만 다른 컴포넌트가 의존하며, 구체화된 수준에 어울릴 정도로만 다른 컴포넌트에 의존한다.
컴포넌트가 위치할 수 있는 가장 바람직한 지점은 주계열의 두 종점이다. 모든 컴포넌트가 완벽히 추상적이거나 완벽히 안정적일 수 없지만 주계열에 가깝게 위치할때 가장 이상적이다.
주계열과의 거리
컴포넌트가 주계열에 대체로 일치하도록 설계되었는지 분석하기 위해 컴포넌트-주계열 간의 거리를 측정해볼 수 있다.
- D⁴ : 거리. D = | A+I-1 |. D가 0이면 주계열 바로 위에 위치하고, 1이면 주계열로부터 가장 멀리 위치
D 값이 0에 가깝지 않으면 해당 컴포넌트를 검토해 재구성할 수 있다.
D 지표를 활용하는 또 다른 방법으로, 각 컴포넌트의 D 값을 시간에 따라 그려볼 수 있다.
개발이 진행될 수록 의존성이 어떻게 변화했는지 조사할 수 있다.
'클린 아키텍처' 카테고리의 다른 글
05 아키텍처 (2) (0) | 2021.08.12 |
---|---|
05 아키텍처 (1) (0) | 2021.08.06 |
04 컴포넌트 (1) (0) | 2021.07.23 |
03 설계 원칙 (2) (0) | 2021.07.16 |
03 설계 원칙 (1) (0) | 2021.07.09 |