시그마 삽질==six 시그마

Clean Architecture(로버트C.마틴, 엉클밥) 본문

프로그래밍/Programming stuff

Clean Architecture(로버트C.마틴, 엉클밥)

Ethan Matthew Hunt 2020. 10. 1. 10:48

 

Clean Architecture(저자: 로버트C.마틴, 엉클밥 ) 구입하시길 강력 추천드립니다.

설계에 대한 숲을  볼 수 있는 좋은 책입니다.

책 구입을 원하시는분은 요기를 클릭하시면 됩니다.

하단의 내용은 제가 예전에 읽었던 내용을 제 나름대로 요약한 것으로 저자의 의도와는 다를 수 있습니다

 

아키텍처의 궁극적인 목표는 시스템의 수명과 관련된 비용은 최소화하고,프로그래머의 생산성은 최대화하는데 있다.

 

The ultimate Goal of Architecture

The goal of software architecture is to minimize the lifetime cost of the software

(development cost+maintenance cost afterwards)

 

-keep software soft

-keep frameworks at arm’s length

-keep options open

 

소프트웨어 아키텍처에서 경계선을 그리면서 먼저 시스템을 컴포넌트 단위로 분할 해야한다. 

아키텍처는 최소한의 업무 규칙을 위한 계층 하나와 나머지는 사용자와 시스템 인터페이스를 위한 다른 계층인 플러그인이된다. 컴포넌트 사이의 화살표가 특정방향, 핵심업무를 향하도록 이들 컴포넌트 소스를 배치한다.(관심사의 분리) 의존성 화살표는 저수준 세부사항에서 안쪽으로 고수준의 추상화를 향하도록 배치한다.

 

각각의 동심원은 소프트웨어에서 서로 다른 영역을 표시한다. 보통 안으로 들어갈수록 고수준의 소프트웨어가 된다.

이러한 아키텍처가 동작하도록 하는 가장 중요한 규칙은 의존성 규칙이다.

내부의 원에 속한 요소는 외부의 원에 속한 어떤 것도 알지 못한다. 같은 이유로 외부의 원에 선언된 데이터 형식도 내부의 원에서 절대로 사용해서는 안된다

 

좋은 소프트웨어 아키텍처는 프레임워크, 데이터 베이스, 웹서버, 그리고 여타 개발 환경 문제나 도구에 대해서는 결정을 미룰 있도록 만든다.

 

좋은 아키텍처는 유스케이스를 중심에 두기 때문에 프레임워크나 도구,환경에 전혀 구애받지 않고 유스케이스를 지원하는 구조를 아무런 문제 없이 기술 있다.

 

사실 소프트웨어 개발 기술의 역사는 플러그인을 손쉽게 생성하여 확장 가능하며 유지보수가 쉬운 시스템 아키텍처를 확립할 있게 만드는 방법에 대한 이야기이다. 

 

아키텍처 특징:프레임워크 독립성, 테스트 용이성, UI독립성, 데이터베이스 독립성, 모든 외부 에이전시에 대한 독립성

 

Only a Domain-Centric Architecture allows Domain-Driven Design

Chapter 1 소개

 

 

 

1장 설계와 아키텍처란

 

소프트웨어의 아키텍처의 목표는 필요한 시스템을 만들고 유지보수하는 데 투입되는 인력을 최소화하는데 있다.

빨리가는 유일한 방법은 제대로 가는 것이다.

 

 

2장  두 가지 가치에 대한 이야기

 

소프트웨어가 가진 본연의 목적을 추구하려면 소프트웨어는 반드시 부드러워야 한다. 다시말해 변경하기 쉬워야 한다.

소프트웨어는 두 종류의 가치, 즉 행위적 가치와 구조적 가치를 지닌다. 이중에서 두번째 가치가 더 중요한데, 소프트웨어를 부드럽게 만드는것이 바로 이 구조적 가치이기 때문이다.

아키텍처를 위해 투장하라

 

 

 

 

 

 

Chapter 2 프로그래밍 패러다임

 

 

4장 구조적 프로그래밍

 

구조적 프로그래밍은 제어흐름의 직접적인 전환에 대한 규칙을 부과한다. goto -> 순차,분기,반복

 

5장 객체지향 프로그래밍

 

객체지향 프로그래밍 제어흐름의 간접적인 전환에 대해 규칙을 부과한다. 캡슐화x , 상속x, only 다형성

 

다형성 전에는 소스코드의 의존성은 제어흐름에 따라 결정됐다.  다형성이 들어가면서 소스코드의 의존성이 제어흐름의 방향과 일치되도록 제한되지 않는다.

 

소스코드는 인터페이스를 통해 구현 함수를 호출하나  런타임시에는 인터페이스는 존재하지 않는다. 제어흐름은 단순히 구현 함수를 호출할 뿐이다

 

이 힘으로 컴포넌트를 개별적이며 독립적으로 배포 가능하게 할 수 있다.  특정 컴포넌트의 소스 코드가 변경되면 해당 코드가 포함된 컴포넌트만 다시 배포한면된다. 이것이 바로 배포 독립성이다.(independent deployability) -->플러그인 아키텍처 구성가능

 

6장 함수형 프로그래밍

 

함수형 프로그래밍은 할당문에 대한 규칙을 부과한다. 리스프->클로저 (변수 불변)

경합조건,교착상태조건, 동시 업데이트 문제가 모두 가변 변수로 인해 발생한다. 만약 어떠한 변수도 갱신되지 않는다면 경합조건이나 동시업데이트 문제가 일어나지 않는다. 락이 가변적이지 않다면 교착상태도 일어나지 않는다.  즉 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.

 

애플리케이션을 제대로 구조화 하려면 가변 컴포넌트와 불변 컴포넌트로 분리해야한다.

 

이벤트 소싱은 상태가 아닌 트랜잭션을 저장하자는 전략이다. 상태가 필요해지면 단순히 상태의 시작점부터 모든 트랜잭션을 처리한다.  이때는 데이터 저장소에서 삭제되거나 변경되는것이 하나도 없다는 사실이다. 결과적으로 애플리케이션은 crud가 아니라 그저 cr만 수행한다. 또한 데이터 저장소에 변경과 삭제가 전혀 발생하지 ㅇ낳으므로 동시 업데이트 문제 또한 일어나지 않는다. 버전 관리 시스템이 정확히 이 방식으로 동작한다.

 

우리는 아키텍처 경계를 넘나들기 위한 메커니즘으로 다형성을 이용한다. 우리는 함수형 프로그래밍을 이용하여 데이터의 위치와 접근방법에대해 규칙을 부과한다. 우리는 모듈 기반 알고리즘으로 구조적 프로그래밍을 사용한다

 

 

테이크스트라는 테스트는 버그가 있음을 보여줄뿐, 버그가 없음을 보여줄수는 없다라고 말한적있다.

 

 

 

 

 

 

Chapter 3 설계 원칙

 

 

 

SOLID 원칙

 

7장 SRP(The Single Responsiblity Principle)

 

클래스나 모듈을 변경할 이유가 하나, 단 하나뿐이어야한다는 원칙이다.

책임의 분리를 강조!!!

응집도는 높이고, 결합도는 낮은게 좋다

 

하나의 일만 해야한다는 원칙은 사실 따로 있다. 그것은 바로 함수는 반드시 하나의 일만 해야 한다는 원칙이다. 이 원칙은 커다란 함수를 작은 함수들로 리팩터링 하는 더 저수준에서 사용된다.하지만 이 원칙은 SOLID 원칙이 아니며 SRP도 아니다.

 

하나의 모듈은 하나의 ,오직 하나의 액터에(사용자,이해관계자) 대해서만 책임져야한다

 

Single Responsibility Principle

A module* should have only one reason to change

*read: class,package,component,architecture element,software entity…..

 

Business Code is isolated from changes.. business code is more stable

 

 

작은 클래스가 많은 시스템이든 큰 클래스가 몇 개뿐인 시스템이든 돌아가는 부품은 그 수가 비슷하다. 그럼 클래스가 여러개 많이생기는거에 걱정하는데 도구상자를 작은 서랍을 많이 두고 기능과 이름이 명확한 컴포넌트로 나눠넣고 싶나 아니면 큰 서랍 몇개를 두고 모두를 던져 넣고 싶은가. 

 

ex) TextManipulator(텍스트 조작)라는 class안에 printText() 메소드가 있다면 이건 단일 책임 위배.

책임이 2개가됨 텍스트 조작 & 인쇄

프린트 메소드는 TextPrinter class를 만들어서 옮겨준다.

https://www.baeldung.com/java-single-responsibility-principle

 

Person 클래스에 String email 이 있고 validateEmail()메소드가 있다면 Email 클래스를 만들고 Person 클래스의 validateEmail()메소드를 Email 클래스로 가져오고 Person 클래스의 String email은 Email email로 변경.

https://stackoverflow.com/questions/10620022/what-is-an-example-of-the-single-responsibility-principle

 

 

 

8장 OCP(The Open Close Principle)

 

클래스는 확장에는 열려있어야 하며 변경에 닫혀 있어야 한다.

 

화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려진다.

A 컴포넌트에서 발생한 변경으로부터 B 컴포넌트를 보호하려면 반드시 A컴포넌트가 B컴포넌트에 의존해야한다.

 

클래스 A가 클래스 B에 의존하고 다시 클래스 B가 클래스 C에 의존한다면 클래스 A는 클래스 C에 의존하게 된다. 이를 추이 종속성이라고 하며(transitive dependency) 클래스 이외의 소프트웨어의 모든 엔티티(패키지,컴포넌트 등)에도 동일하게 적용된다. 만약 클래스 의존성이 순환적이라면(cyclic) 모든 클래스가 서로 의존하게 되는 문제가 있다. 컴포넌트 수준에서의 순환성에 대해서는 의존성 비순환 원칙에서 다룬다.(Acyclic Dependency Principle)

 

 

ex) 전략 패턴, 탬플릿 메소드 패턴

 

 

9장 LSP(The Liskov Substitution Principle)

 

서브타입은 그것의 기반 타입(부모타입)에 대해 대체 가능해야한다.

 

 

ex) 

https://blog.itcode.dev/posts/2021/08/15/liskov-subsitution-principle

http://stg-tud.github.io/sedc/Lecture/ws13-14/3.3-LSP.html#mode=document

https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle

https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle

 

10장 ISP(The Interface Segregation Principle)

인터페이스의 단일 책임

클라이언트에 밀접하게 작게 쪼개진 인터페이스를 유지한다.

클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안된다.

즉 하나의 거대한 인터페이스 보다는 책임에 맞게 여러개로 쪼개진 인터페이스가 낫다는 것을 의미한다.

 

ex) Vehicle 인터페이스 가격,색,시동,정지,난다 기능있는데 그걸 상속한 Car 클래스에 가격,색,시동,정지만 있고 난다는 비어있음. 

 

Vehicle 인터페이스 (가격, 색) ,Movable 인터페이스(시동,정지) ,Flyable 인터페이스(난다)로 분리해서 Car 클래스에 Vehicle,Movable 만 적용해줌.

https://levelup.gitconnected.com/interface-segregation-principle-in-java-44f1c1a4eacd

 

 

정적 타입 언어는 사용자가 import, use 또는 include와 같은 타입 선언문을 사용하도록 강제한다. 이처럼 소스코드에 포함된 included 선언문으로 인해 소스코드의 존성이 발생하고 ,이로 인해 재컴파일 또는 재배포가 강제되는 상황이 무조건 초래된다.

 

 

 

 

 

 

 

11장 DIP(The Dependency Inversion Principle)

 

추상화에 의존해야 하며, 구체화에 의존하면 안된다.

 

유연성이 극대화된 시스템이란 소스 코드 의존성이 추상(abstraction)에 의존하며 구체(concretion)에는 의존하지 않는 시스템이다.

 

자바와 같은 정적 타입 언어에서 이 말은 use, import,include 구문은 오직 인터페이스나 추상 클래스 같은 추상적인 선언만을 참조해야 한다는 뜻이다.  

자바에서 String은 구체 클래스인데 이건 매우 안정적이고 절대 변경안될 이건 DIP 논의 대상 아님. 

우리가 의존하지 않도록 피하고자 하는 것은 바로 변동성이 큰(volatile) 구제척인 요소다.

 

 

안정된 소프트웨어 아키텍처란 변동성이 큰 구현체에 의존하는 일은 지양하고, 안정된 추상 인터페이스를 선호하는 아키텍처라는 뜻이다.

변동성이 큰 구체 클래스를 참조하지 마라. 변동성이 큰 구체 클래스로부터 파생하지 마라. 구체 함수를 오버라이드 하지 마라

 

소스코드의 의존성은 제어흐름과는 반대 방향으로 역전된다. 이러한 이유로 이 원칙을 의존성 역전이라고 부른다.

 

Dependency Inversion Principle

We can choose the direction of any code dependency

(as long as we have control over the code)

 

 

Chapter 4 컴포넌트 원칙

 

 

13장 컴포넌트 응집도

 

REP:재사용/ 릴르스 등가 원칙 Reuse/Release Equivalence Principle   재사용성을 위한 그룹

CCP: 공통 폐쇄원칙 Common Closure Principle==SRP, OCP 유지보수성을 위한 그룹

CRP: 공통 재사용 원칙 Common Reuse Principle ==ISP 불필요한 리리스를 피하기 위해 분리하기

 

 

 

 

 

 

14장 컴포넌트 결합

 

1.ADP:의존성 비순환 원칙(Acyclic Dependency Principle)

 

컴포넌트 의존성 그래프에 순환이 있어서는 안된다

 

1)주단위 빌드 weekly build

 

2)순환의존성 제거하기

 

개발 환경을 릴리스 가능한 컴포넌트 단위로 분리한다.

어떤 컴포넌트가 새로 릴리스되어 사용할 수 있게 되면(A 컴포턴트라하자) 다른 팀에서는 A 컴포넌트의 새 릴리스를 당장 적용할지를 결정해야한다. 적용하지 않기로 했다면 그냥 과거 버전의 릴리스를 계속 사용한다.  이 A 컴포넌트를 사용하기로 결정하면 의존성 화살표를 거꾸로 따라가면 영향 받는 컴포넌트가 보인다.(B,C라 하자) 비순환 뱡향 그래프라면  딱 그 영향받는 컴포넌트들만 A의 새로운 컴포넌트와 통합할지말지, 그리고 언제 통합할지를 결정하면된다.(선택권이 있다) 그리고 앞과는  별개로 시스템 전체를 릴리스해야 할때가 오면 릴리스 절차는 상향식으로 진행하면된다. 의존 당하는 컴포넌트먼저 그후 의존하는 컴포넌트 순으로...

 

3)순환이 컴포넌트 의존성 그래프에 미치는영향

 

만약 어떤 클래스 하나가 다른 클래스 하나를 사용하도록 변경할수밖에 없었고 그로인해 비순환 그래프에서 사이클이 돌아버리면 그  사이클은 사실상 하나의 거대한 컴포넌트가 되어 버린다.  

(1)그렇게 되면 해당 컴포넌트의 개발자들은 모두 서로에게 얽매이게 되는데 모두 항상 정확하게 동일한 릴리스를 사용해야 하기 때문이다. (아까 비순환일때는 변경된 A 컴포넌트를 반영할지 말지 선택이 가능했음) 

(2)한 컴포넌트 테스트할때 다른 컴포넌트들도 빌드하고 통합해야한다. 

(3)의존성 그래프에 순환이 생기면 컴포넌트를 어떤 순서로 빌드해야 올바를지 파악하기가 힘들어진다

 

4)순환 끊기

 

(1)DIP 의존성 역전 원칙을 적용한다

A <-->B 순환참조시
 A --> C  <---B  (AB가 모두 의존하는 새로운 컴포넌트 C를 만들고  C를 호출)

 

 

2.SDP: 안정된 의존성 원칙 (Stable Dependencies Principle)

 

안정성의 방향으로(더 안정된 쪽에) 의존하라

 

안정성(stability)는 쉽게 움직이지 않는

 

A,B,C 컴포넌트가--> D  컴포넌트를 의존한다면  D는 안정된 컴포넌트다. 세 컴포넌트가 D에 의존하며 따라서 D 컴포넌트는 변경하지 말아야할 이유가 세가지나 되기 때문이다. 이경우 D는 세 컴포넌트를 책임진다라고 말한다. 반대로 D는 어디에도 의존하지 않으므로 D가 변경되도록 만들수 있는 외적인 영향이 전혀 없다 .이경우 D는 독립적이다라고 말한다.

 

A 컴포넌트가 -->B,C,D 컴포넌트에 의존시 A는 상당히 불안정한 컴포넌트다. 어떤 컴포넌트도 A에 의존하지 않으므로 A는 책임성이 없다고 말할 수 있다 또한A는 세개의 컴포넌트에 의존하므로 변경이 발생할 수 있는 외부요인이 세가지나 있다. 이경우 A는 의존적이라고 말한다.

 

 

안정성 지표 == 의존하는 정도==변경될 가능성

 

개별 컴포넌트의 안정성을 측정하는 방법

 

Fan-in(의존당하는,good): 개별 컴포넌트 안으로 들어오는 의존성.  컴포넌트 내부의 클래스에 의존하는 컴포넌트 외부의 클래스 개수

Fan-out(의존하는,bad ):바깥으로 나가는 의존성. 컴포넌트 외부의 클래스에 의존하는 컴포넌트 내부의 클래스 개수

 

I(불안정성): I= Fan-out /(Fan-in+Fan-out)  이 지표는 [0,1] 범위의 값을 갖는다 I=0이면 최고로 안정된 컴포넌트, I=1이면 최고로 불안정한 컴포넌트(Fan-in이 많은면 좋긴한데 항상 그런건 아님)

i=0이면 최고로 안정된 상태. 자신에게 의존하는 컴포넌트가 있으므로 해당 컴포넌트는 변경하기 어렵지만 해당 컴포넌트를 변경하도록 강제하는 의존성은 없음 -->이경우 변경이 불가능함. 바람직한 상황아님

 

 

3.SAP : 안정된 추상화 원칙(Stable Abstractions principle)

 

안정된 추상화 원칙은 안정성과 추상화 정도 사이의 관계를 정의한다.

 

 

Nc: 컴포넌트의 클래스 개수

Na: 컴포넌트의 추상클래스와 인터페이스의 개수

A: 추상화 정도 A= Na/Nc

 

A지표는 [0~1]  A가 0이면 컴포넌트에는 추상 클래스가 하나도 없다는 뜻이다. A가 1이면 컴포넌트는 오로지 추상 클래스만을 포함한다는 뜻이다.

 

 

X축을 I(안정성) 으로 하고 Y축을 A(추상화 정도)로 할때 (X,Y) 는 (I,A)가 된다.

 

고통의 구역 (0,0) 

이 컴포넌트는 매우 안정적이며 구체적이다. 뻣뻣한 상태로 추상적이지 않으므로 확장될 수 없고, 안정적이므로 변경하기도 상당히 어렵다. 배제해야할 구역이며 고통의 구역이다.

 

쓸모없는 구역(1,1)

최고로 추상적이지만 누구도 그 해당 컴포넌트에 의존하지 않는다. 이렇게 사용되지 않는 컴포넌트는 쓸모없다.

 

 

배제 구역 벗어나기

따라서 변동성이 큰 컴포넌트 대부분은 두 배제 구역으로부터 가능한 멀리 떨어뜨려야한다.  각 배제 구역으부터 최대한 멀리 떨어진 점의 궤적은 (1,0), (0,1)을 잇는 선분이다. 이 선분을 주계열(main sequence)라고 부른다.

컴포넌트가 위치할 수 있는 가장 바람직한 지점은 주계열의 두 종점이다. 뛰어난 아키텍트라면 대다수의 컴포넌트가 두 종점에 위치하도록 만들기위해 매진해야한다.

하지만 경험상 대부분의 시스템에서 소수의 일부 컴포넌트는 완전히 추상적이거나 완전하게 안정적일수 없다. 이들 컴포넌트는 주계열 바로 위에 도는 가깝게 위치할때 가장 이상적이다.

 

 

Chapter 5 아키텍처

 

15장 아키텍처

 

아키텍처의 주된 목적은 시스템의 생명주기를 지원하는 것이다. 좋은 아키텍처는 시스템을 쉽게 이해하고 ,쉽게 개발하며,쉽게 유지보수하고, 또 쉽게 배포해준다. 아키텍처의 궁극적인 목표는 시스템의 수명과 관련된 비용은 최소화하고,프로그래머의 생산성은 최대화하는데 있다.

 

 

 

 

 

17장 경계: 선긋기

 

소프트웨어 아키텍처는 선을 긋는 기술이며, 나는 이런 선을 경계라고 부른다. 경계는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막는다.

관련성이 낮은 컴포넌트가 관련성이 높은 컴포넌트에 의존한다. 

 

플로그인 아키텍처

사실 소프트웨어 개발 기술의 역사는 플러그인을 손쉽게 생성하여 확장 가능하며 유지보수가 쉬운 시스템 아키텍처를 확립할 수 있게 만드는 방법에 대한 이야기이다.  GUI  |  Business Rules  | DB

 

소프트웨어 아키텍처에서 경계선을 그리려면 먼저 시스템을 컴포넌트 단위로 분할 해야한다. 일부는 핵심업무 규칙에 해당되고 나머지는 플러그인이된다. 컴포넌트 사이의 화살표가 특정방향, 즉 핵심업무를 향하도록 이들 컴포넌트 소스를 배치한다.

이는 DIP와 SAP를 응용한것이다. 의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배치한다.

 

 

 

 

 

 

20장 업무 규칙

 

애플리케이션을 업무규칙과 플러그인으로 구현해야한다. 업무규칙은 사업적으로 수익을 얻거나 비용을 줄일수 있는 규칙 또는 절차다. 이러한 규칙을 핵심 업무 규칙이라하고 보통 이런 규칙은 데이터를 요구하는데 핵심 업무 데이타  부른다.

핵심 규칙과 핵심 데이터는 본질적으로 결합되어 있기 때문에 객체로 만들기 좋은 후보가 된다. 이러한 유형의 객체를 엔티티라고 하겠다.

 

엔티티는 컴퓨터 시스템 내부의 객체로서, 핵심 업무 데이터를 기반으로 동작하는 일련의 조그만 핵심 업무 규칙을 구체화한다.

 

 

 

유스케이스

유스케이스는 사용자가 제공해야 하는 입력, 사용자에게 보여줄 출력, 그리고 해당 출력을 생성하기 위한 처리단계를 기술한다. 유스케이스만 봐서는 이 애플리케이션이 웹을 통해 전달되는지, 리치 클라이언트인지, 콘솔 기반이지 아니면 순수한 서비스인지를 구분하기란 불가능하다.

유스케이스는 시스템이 사용자에게 어떻게 보이는지를 설명하지 않는다. 이보다는 애플리케이션에 특화된 규칙을 설명하며, 이를 통해 사용자와 엔티티 사이의 상호작용을 규정한다.

 

엔티티와 같은 고수준 개념은 유스케이스와 같은 저수준 개념에 대해 아무것도 알지 못한다. 왜 앤티티는 고수준이며 유스케이스는 저수준일까? 왜내하면 유스케이스는 단일 애플리케이션에 특화되 있으며, 따라서 해당 시스템의 입력과 출력에 보다 가깝게 위치하기 때문이다. 엔티티는 수많은 다양한 애플리케이션에서 사용될 수 있도록 일반화 된것이므로 각 시스템의 입력이나 출력에서 더 멀리 떨어져 있다. 유스케이스는 엔티티에 의존한다. 반면 엔티티는 유스케이스에 의존하지 않는다.

 

 

 

업무규칙은 소프트웨어 시스템이 존재하는 이유다. 업무규칙은 핵심적인 기능이다. 업무 규칙은 수익을 내고 비용을 줄이는 코드를 수반한다. 

업무 규칙은 사용자 인터페이스나 데이터베이스와 같은 저수준의 관심사로 인해 오엽되어서는 안되며 원래 그대로의 모습으로 남아 있어야한다. 

이상적으로 업무 규칙을 표현하는 코드는 반드시 시스템의 심장부에 위치해야하며, 덜 중요한 코드는 이 심장부에 플러그인되어야 한다.업무 규칙은 시스템에서 가장 독립적이며 가장 많이 재사용될 수 있는 코드여야한다

 

 

 

 

 

21장 소리치는 아키텍처

 

좋은 아키텍처는 유스케이스를 그 중심에 두기 때문에 프레임워크나 도구,환경에 전혀 구애받지 않고 유스케이스를 지원하는 구조를 아무런 문제 없이 기술 할 수 있다.

좋은 소프트웨어 아키텍처는 프레임워크, 데이터 베이스, 웹서버, 그리고 여타 개발 환경 문제나 도구에 대해서는 결정을 미룰 수 있도록 만든다.

 

 

 

 

22장 클린아키텍쳐

 

육각형 아텍처:포트와 어댑터

 

목표는 모두 같은데, 관심사의 분리다. 각 아키텍처는 최소한의 업무 규칙을 위한 계층 하나와 , 사용자와 시스템 인터페이스를 위한 또 다른 계층 하나를 반드시 포함한다.

아키텍처는 다음과 같은 특징이 있다: 프레임워크 독립성, 테스트 용이성, UI독립성, 데이터베이스 독립성, 모든 외부 에이전시에 대한 독립성

 

각각의 동심원은 소프트웨어에서 서로 다른 영역을 표시한다. 보통 안으로 들어갈수록 고수준의 소프트웨어가 된다. 이러한 아키텍처가 동작하도록 하는 가장 중요한 규칙은 의존성 규칙이다.

소스코드의 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야한다.

내부의 원에 속한 요소는 외부의 원에 속한 어떤 것도 알지 못한다. 같은 이유로 외부의 원에 선언된 데이터 형식도 내부의 원에서 절대로 사용해서는 안된다

 

 

 

 

23장 부분적 경계

 

일차원 경계 

전략패턴 service는 클라이언트가 사용 ->serviceImpl 클래스가 구현한다

 

퍼사드

Facade 클래스에는 모든 서비스 클래스를 메서드 형태로 정의하고, 서비스 호출이 발생하면 해당 서비스 클래스로 호출을 전달한다. 클라이언트는 이들 서비스 클래스에 직접 접근할 수 없다.

 

 

Chapter 6 세부 사항

 

34징 빠져있는 장

 

 

 

 

1.전통적인 수평 계층형 아키텍처

 

기술적인 관점에서 해당 코드가 하는 일에 기반해 그 코드를 분할한다. 이 방식을 계층 기반 패키지라 부른다.

전형적인 계층형 아키텍처에는 웹,업무규칙, 영속성 코드를 위해 계층이 각각  하나씩 존재한다. 다시말해 코드는 계층이라는 얇은 수평 조각으로 나뉘며,

각 계층은 유사항 종류의 것들을 묶는 도구로 사용된다. 업격한 계층형 아키텍처의 경우 계층은 반드시 바로 아래 계층에만 의존해야 한다.

자바의 경우 계층은 주로 패키지로 구현된다.

 

OrdersController   --->  |||  <I>OrderService ◁-- OrderServiceImpl   |||    --->    <I>OrderRepository   ◁---   OrderRepositoryImpl

 

 

 

 

2.기능 기반 패키지(수직적 계층화) -도메인별로 하나의 패키지로 묶음

 

서로 연관된 기능, 도메인 개념, 또는 Aggregate Root에 기반하여 수직의 얇은 조각으로 코드를 나누는 방식이다.

전형적인 구현은 모든 타입이 하나의 자바 패키지에 속하며, 패키지 이름은 그 안에 담긴 개념을 반영해 짓는다.

인터페이스와 클래스는 이전과 같지만 모두가 단 하나의 패키지에 속하게 된다.

드디어 이 코드베이스가 웹,서비스,리포지토리가 아니라 주문과 관련된 무언가를 한다는걸 볼수 있다.

주문조회하기 유스케이스 변경시 변경해야할 코드를 모두 찾는 작업이 더 쉬워질 수 있다.변경해야할 코드가 여러군데 퍼져있지 않고 모두 한 패키지에 담겨 있기 때문이다.

 

 

OrdersController --->   <I>OrderService ◁-- OrderServiceImpl   --->    <I>OrderRepository   ◁---   OrderRepositoryImpl

 

 

위 두개의 계층형 아키텍처의 단점은  OrdersController에 OrderRepository를 주입해서 service를 우회해서 사용될수 있다. 웹 컨트롤러는 절대로 리포지터리에 직접 접근해서는 안된다는 원칙이 무시될 수 있다.

 

 

3.포트와 어댑터

 

엉클 밥에 따르면 포트와 어댑터, 육각형 아키텍처, 경계와컨틀롤러 엔티 등의 방식으로 접근하는 이유는 업무/도메인에 초점을 둔 코드가 프레임워크나 데이터베이스 같은 기술적인 세부 구현과 독립적이며 분리된 아키텍처를 만들기 위해서다. 요약하자면

코드 베이스는 내부(도메인)과 외부(인프라)로 구성됨을 볼 수 있다.

내부 영역은 도메인 개념을 모두 포함하는 반면 외부 영역은 외부세계(예를들면 UI,데이터베이스,서드파티 통합)와의 상호작용을 포함한다. 여기서 주요 규칙은 바로 외부가 내부에 의존하며 절대 그 반대로는 안된다는 것이다.

 

 

OrdersController ---> |||   내부 시작 <I>OrderService ◁-- OrderServiceImpl   --->    <I>Orders   내부끝  |||  ◁---   OrderRepositoryImpl

 

 

4.컴포넌트 기반 패키지

 

단일 컴포넌트와 관련된 모든 책임을 하나의 자바 패키지로 묶는데 주안점을 둔다. 이 접근법은 서비스 중심적인 시작그올 소프트웨어 시스템을 바라보며, 마이크로서비스 아키첵처가 가진 시각과도 동일하다. 

포트와 어댑터에서 웹을 그저 또 다른 전달 메커니즘으로 취급하는 것과 마찬가지로 ,컴포넌트 기반 패키지에서도 사용자 인터페이스를  큰 단위의 컴포넌트로부터 분리해서 유지한다.

 

 

컴포넌트는 멋지고 깔끔한 인터페이스로 감싸진 연관된 기능들의 묶음으로 ,애플리케이션과 같은 실행 환경 내부에 존재한다.

소프트웨어 시스템은 하나 이상의 컨테이너(예를들어 웹 애플리케이션, 모바일 앱, 독립된 애클리케이션 ,데이터베이스,파일 시스템등) 로 구성되며 각 컨테이너는 하나 이상의 컴포넌트를 포함한다. 또한 각 컴포넌트는 하나 이상의 클래스(또는 코드)로 구현된다.

이때 각 컴포넌트가 개별 jar 파일로 분리될지 여부는 직교적인(독립적인) 관심사다.

 

모노리틱 애플리케이션에서 컴포넌트를 잘 정의하면 마이크로 서비스 아키텍처로 가기위한 발판으로 삼을 수 있다.

 

 

OrdersController   --->  |||  <I>OrdersComponent ◁-- OrdersComponentImpl     --->    <I>OrderRepository   ◁---   OrderRepositoryImpl

 

위에서 1,2,3 경우 모든 타입에서 public 지시자를 사용하면 패키지는 단순히 조직화를 위한 매커니즘(폴더와 같이 무언가를 묶는 방식)으로 전락하여 캡슐화를 위한 메커니즘이 될 수 없다. 그럼 패키지를 사용하는데 따른 이점이 없다. 따라서 사실상 패키지를 사용하지 않는것과 같다.

 

 

 

변경 후

 

 

1.전통적인 수평 계층형 아키텍처

 

OrdersController   --->  |||  <I> public OrderService ◁--protected OrderServiceImpl   |||    --->    <I>public  OrderRepository   ◁---protected   OrderRepositoryImpl

 

OrderService와OrderRepository는 외부 패키지의 클래스로부터 자신이 속한 패키지 내부로 들어오는 의존성이 존재하므로 public으로 선언되고 그 구현체 클래스들은 누구도 알  필요가 없기에 더 제한적으로 선언할 수 있다. 

 

 

<====java에서는 정확히는 protected로 된부분을 생략해서 default 클래스로 만들어서 사용가능함

 

 

 

 

2.기능 기반 패키지(수직적 계층화) -도메인별로 하나의 패키지로 묶음

 

OrdersController --->   <I>protected OrderService ◁-- protected OrderServiceImpl   --->    <I>protected OrderRepository   ◁--- protected  OrderRepositoryImpl

 

 

OrdersController가 패키지로 들어올 수 있는 유일한 통로를 제공하므로 나머지는 모두 패키지 protected로 지정할 수 있다. 이 패키지 밖의 코드에서는 컨트롤러를 통하지 않으면 주문 관련 정보에 접근할 수 없다는 사실이다. 이는 바람직할때도 있고 아닐때도 있다.

 

 

<====java에서는 불가능. 인터페이스는 public이어야함.  1번처럼만 가능(비슷한 효과 발생)

 

 

 

 

 

3.포트와 어댑터

 

 

OrdersController ---> |||   내부 시작 <I>public  OrderService ◁-- protected OrderServiceImpl   --->    <I>public  Orders   내부끝  |||  ◁---protected   OrderRepositoryImpl

 

OrderService와OrderRepository는 외부 패키지의 클래스로부터 자신이 속한 패키지 내부로 들어오는 의존성이 존재하므로 public으로 선언되고 그 구현체 클래스들은 누구도 알  필요가 없기에 더 제한적으로 선언할 수 있다. 

 

 

<====java에서는 정확히는 protected로 된부분을 생략해서 default 클래스로 만들어서 사용가능함

 

 

 

4.컴포넌트 기반 패키지

 

OrdersController   --->  |||  <I>public OrdersComponent ◁--protected  OrdersComponentImpl     --->    <I>protected OrderRepository   ◁---protected   OrderRepositoryImpl

 

컨트롤러에서 OrdersComponent 인터페이스로 향하는 의존성을 가지며, 그 외의 모든 것은 패키지 protected로 지정할 수 있다. public 타입이 적을 수록 필요한 의존성의 수도 적어진다. 이제 이 패키지 외부의 코드에서는 OrderRepository 인터페이스나 구현체를 직접 사용할수 있는 방법이 전혀 없다. 따러서 컴파일러의 도움을 받아서 컴포넌트 기반 패키지 아키텍처 접근법을 강제할 수 있다.

 

 

<====java에서는 불가능. 인터페이스는 public이어야함.  1번처럼만 가능(비슷한 효과 발생)

 

 

 

 

 

Comments