6. 세부사항
#
30장. 데이터베이스는 세부사항이다- 아키텍처 관점에서 볼 때 데이터베이스는 엔티티가 아닙니다.
- 즉, 데이터베이스는 세부사항이라 아키텍처의 구성요소 수준으로 끌어올릴 수 없습니다.
- 데이터베이스는 데이터 모델이 아닌 일개 소프트웨어일 뿐입니다.
#
관계형 데이터베이스- 관계형 데이터베이스의 원칙을 정의하고, 관계형 모델은 계속 성장합니다.
- 관리형 테이블은 특정한 형식의 데이터에 접근하는 경우에는 편리하지만, 데이터를 테이블에 핻 단위로 배치한다는 자체는 아키텍처적으로는 전혀 중요하지 않습니다.
#
데이터베이스 시스템은 왜 이렇게 널리 사용되는가?- 데이터베이스는 디스크를 통해 데이터 저장소의 중심이 되었습니다.
- 그러나, 디스크 때문에 피해갈 수 없는 시간 지연이 생겼고 이를 완화하기 위해 색인, 캐시, 쿼리 최적화가 필요했습니다.
- 이를 할려면 데이터가 어떤 데이터인지를 알아야했고, 이에 따라 데이터 접근 및 관리시스템이 필요했습니다.
- 이 시스템은 파일 시스템과 관계형 데이터베이스 관리 시스템(RDBMS)이 되었습니다.
- 데이터베이스 시스템은 내용 기반입니다. 데이터베이스 시스템은 내용을 기반으로 레코드를 자연스럽고 편리하게 찾는 방법을 제공합니다.- 이 두 시스템은 데이터를 디스크에 체계화해서, 각 시스템에 특화된 방식을로 접근해야 할 때 가능한 효율적으로 데이터를 저장하고 검색할 수 있게 합니다.
#
디스크가 없다면 어떻게 될까- 그러나 최근 추세는 디스는 RAM으로 대체되고 있습니다.
- 데이터를 저장할 때는 연결 리스트, 트리, 해시 테이블, 스택, 큐와 같은 무수히 많은 데이터구조로 체계화하여 저장하며, 이는 프로그래머가 하는 일입니다.
#
세부사항- 데이터베이스가 세부사항인 이유는 이러한 현실 때문입니다.
- 데이터베이스는 메커니즘에 불과하며, 디스크와 RAM 사이에서 데이터를 옮길 때 사용할 뿐입니다.
#
하지만 성능은?- 데이터 저장소의 측면에서 성능은 완전히 캡슐화하여 업무 규칙과는 분리할 수 있는 관심사입니다.
- 성능은 시스템의 번반적인 아키텍처와는 아무런 관련이 없습니다.
#
개인적인 일화- '엔터프라이즈'라는 단어와 '서비스-지향 아키텍처'라는 개념은 현실보다는 마케팅과 더 관련이 깊습니다.
#
결론- 체계화된 데이터 구조와 데이터 모델은 아키텍처적으로 중요합니다.
- 데이터를 테이블 구조로 만들고 SQL로만 접근하도록 하는 관계형 데이터베이스 시스템은 전자보다는 후자와 훨씬 관련이 깊습니다.
- 데이터는 중요하며, 데이터베이스는 세부사항입니다.
#
31장. 웹은 세부사항이다- 사실 웹이 바꾼 것은 없습니다.
#
끝없이 반복하는 추- 앱의 아키텍트와 UI와 업무 규칙을 서로 격리하면 좋습니다.
#
요약- 요약하면 GUI는 세부사항입니다.
- 웹을 다른 측면으로 보면, 입출력 장치입니다.
- 각 유즈케이스가 장치 독립적인 방식으로 UI라는 입출력 장치를 동작시킨다고 간주할 수 있습니다.
#
결론- 추상화는 어려우며 제대로 만들려면 수차례의 반복 과정이 필요합니다.
#
32장. 프레임워크는 세부사항이다프레임워크는 강력하며 유용하지만 아키텍처가 될 수 없습니다.
#
프레임워크 제작자- 프레임워크는 커뮤니티에 도움이 되기를 바라는 마음에 제공하지만, 이는 다른 사람의 관심사를 반영하지 않은 부분입니다.
- 다만, 겹치는 영역이 크면 클수록 프레임워크는 실제로 더 유용해집니다.
#
혼인 관계의 비대칭성- 프레임워크는 애플리케이션이 결합되게 만듭니다. 또한 이러한 결합은 관계를 깨기가 어렵습니다.
- 이러한 결합 관계는 일방적이며, 이를 진행하는 개발자가 취험과 부담을 가져야합니다.
#
위험 요인고려해야할 위험 요인은 다음과 같습니다.
- 프레임워크의 아키텍처는 그다지 깔끔하지 않은 경우가 많습니다.
- 프레임워크는 애플리케이션의 초기 기능을 만드는 데 도움이 됩니다.
- 프레임워크는 당신에게 도움되지 않는 방향으로 진화할 수도 있습니다.
- 새롭고 더 나은 프레임워크가 등장하여 바꾸고 싶습니다.
#
해결책- 해결책은
프레임워크와 결합해서는 안됩니다.
- 업무 객체를 만들 때 프레임워크가 자신의 기반 클래스로부터 파생하기를 요구한다면 거절합니다.
- 프레임워크가 핵심 코드 안으로 들어오지 못하게 합니다.
- 즉,
의존성 규칙을 준수해야합니다
.
- 즉,
- 대표적인 예시로 스프링의
@autowired
어노테이션은 업무 객체 도처에 산재해서는 안됩니다.- 이보다는 메인에서 의존성을 주입하는 것이 맞습니다.
#
이제 선언합니다.- 그러나 C++과 STL 관계는 뗄 수 없습니다. 즉, 표준 라이브러리와는 반드시 결합해야합니다.
- 이러한 관계는 정상적이나 항상
선택적
인 것을 알고 있어야합니다. 가볍게 선택하지 않는 것이 좋습니다.
#
결론- 프레임워크와 처음부터 결합하려 하지 않습니다.
- 가능한 오랫동안 아키텍처 경계 너머에 두는 것이 필요합니다.
#
33장. 사례 연구: 비디오 판매- 뛰어난 아키텍트가 일을 처리하는 과정과 결정은 다음과 같습니다.
#
제품웹 사이트에서 비디어를 판매하는 소프트웨어 예시입니다. 아래는 그 요구사항입니다.
- 판매하길 원하는 비디오들이 있고, 이를 개인과 기업에게 웹을 통해 판매하는 사이트입니다.
- 개인은 단품 가격을 지불해 스트리밍으로 보거나, 더 높은 가격으로 비디오를 다운로드해서 영구 소장이 가능하빈다.
- 기업용 라이선스는 스트리밍 전용이며, 대량 구매를 하면 할인을 받을 수 있습니다.
- 일반적으로 개인은 시청자인 동시에 구매자이며, 기업은 다른 사람들이 신청할 비디오를 구매하는 사람이 따로 있습니다.
- 비디오 제작자는 비디오 파일과 바디오에 대한 설명서, 부속 파일을 제공해야 합니다.
- 부속 파일에는 시험, 문제, 해법, 소스 코드 등이 포함됩니다.
- 관리자는 신규 비디오 시리즈물을 추가하거나 기존 시리즈물에 비디오를 추가/삭제하며 다양한 라이선스에 맞춰 가격을 측정합니다.
#
유스케이스 분석첫 단계는 액터와 유스케이스를 식별해야 합니다.
점선으로 표시된 추상 유스케이스는 점용적인 정책을 가지고 있으며, 다른 유스케이스에서 이를 더 구체화합니다.
#
컴포넌트 아키텍처이를 바탕으로 예비 단계의 컴포넌트 아키텍처를 만들 수 있습니다.
- 뷰(View), 프레젠터(Presenter), 인터랙터(Interactor), 컨트롤러(Controller) 로 분리된 전형적인 분할 방법을 확인할 수 있습니다.
- 이중선은 아키텍처 경계를 나타냅니다.
- 다만 이를 분할해서 여러개의 .jar 파일을 만들 지, 더 크게 혹은 더 작게 할 수도 있습니다. 이처럼 선택지를 열어두면 시스템이 변경되는 양상에 맞춰 시스템 배포 방식을 조정할 수 있습니다.
#
의존성 관리- 위의 그림에서 제어 흐름은 오른쪽에서 왼쪽으로 이동합니다.
- 그러나 모든 화살표가 오른쪽에서 왼쪽을 가리키지는 않습니다. 모든 의존성은 더 높은 수준의 정책을 포함하는 컴포넌트를 향합니다.
- 사용 관계(열린 화살표)는 제어흐름과 같은 방향을 가리키며, 상속 관계(닫힌 화살표)는 제어흐름과는 반대 방향을 가리킵니다. 이는 개방 폐쇄 원칙을 적용한 것입니다.
- 이를 통해 저수준의 세부사항 변경이 상위로 파급되어서 상위 수준의 정책에 영향을 미치지 않음을 보장할 수 있습니다.
#
결론- 아키텍처 다이어그램은 단일 책임 원칙에 기반한 액터의 분리 개념과 의존석 규칙의 개념을 가지고 있습니다.
- 서로 다른 이유라는 것은 액터와 관련이 있으며, 서로 다른 속도라는 것은 정책 수준과 관련이 있습니다.
- 코드를 한번 구조화하고 나면 시스템을 실제로 배포하는 방식은 다양하게 선택할 수 있게 됩니다.
#
34장. 빠져 있는 장- 앞의 조언들은 더 나은 소프트웨어를 설계하는 데 확실히 도움이 됩니다.
- 이를 통하면 소프트웨어는 올바르게 정의된 경계, 명확한 책임, 그리고 통제된 의존성을 자진 클래스와 컴포넌트로 구성됩니다.
#
계층 기반 패키지- 가장 단순한 첫 번째 설계 방식은 전통적인 수평 계층형 아키텍처입니다.
- 전형적인 계층형 아키텍처에는 웹, '업무 규칙', 영속성 코드를 위해 계층이 각각 하나씩 존재합니다.
- 처음 시작할 때는 계층형 아키텍처가 적합하다고 이야기됩니다. 그러나 계층형 아키텍처는 업무 도메인에 대해 아무것도 말해주지 않는다는 문제가 있습니다.
#
기능 기반 패키지- 코드를 조직화하는 또 다른 선택지로 '기능 기반 패키지' 구조도 있습니다.
- 서로 연관된 기능, 도메인 개념에 기반하여 수직의 얇은 조각으로 코드를 나누는 방식입니다.
- 인터페이스와 클래스는 이전과 같지만, 모두가 단 하나의 패키지에 속하게 됩니다.
- 코드의 상위 수준 구조가 업무 도메인에 대해 알려주게 됩니다.
- 또한 유스케이스가 변경될 경우, 변경해야 할 코드를 모두 찾는 작업이 더 쉬워질 수 있습니다.
그러나 수평적 계층화(계층 기반 패키지)와 수직적 계층화(기능 기반 패키지) 모두 차선책으로 보일 수도 있습니다.
#
포트와 어댑터일반적으로 '포트와 어댑터(Ports and Adapters', '육각형 아키텍처(Hexagonal Architecture)', 경계, 컨트롤러, 엔티티(BCE)' 등의 방식으로 접근하는 이유는 업무/도메인에 초점을 둔 코드가 프레임워크나 데이터베이스 같은 기술적인 세부 구현과 독립적이며 분리된 아키텍처를 만들기 위해서입니다.
- '내부' 영역은 도메인 개념을 모두 포함하는 반면, '외부' 영은 외부 세계(ex. UI, 데이터베이스, 서드파티 통합)와의 상호작용을 포함합니다.
- 이때 중요 규칙은 바로 '외부'가 '내부'에 의존하며, 절대 그 반대로는 안 된다는 점입니다.
- 해당 예제에서
com.mycompany.myapp.domain
패키지가 '내부'이며, 나머지 패키지는 모두 '외부' 입니다. - 의존성은 '내부'로 흐릅니다.
OrdersRepository
에서Orders
라는 간단한 이름으로 바뀌었으며, 이는 도메인 주도 설계적 개념입니다.- 도메인 주도 설계에서는 '내부'에 존재하는 모든 것의 이름은 반드시 '유비쿼터스 도메인 언어, ubiquitous domain language' 관점에서 기술해야 합니다.
- 위 그림을 통해 UML 클래스 다이어그램을 간소화할 때를 표현할 수 있습니다.
#
컴포넌트 기반 패키지- 기존의 서비스 대다수는 전통적인 계층형 아키텍처를 기반으로 했습니다.
- 계층형 아키텍처의 목적은 기능이 같은 코드끼리 서로 분리하는 것입니다.
- 엄격한 계층형 아키텍처에서는 의존성 화살표는 항상 아래를 향해야 하며, 각 계층은 반드시 바로 아래 계층에만 의존해야합니다.
- 또한, 코드 베이스의 요소들이 서로 의존할 때는 몇 가지 규칙을 반드시 지켜야합니다.
- ex) 웹 컨트롤러는 절대로 리포지터리에 직접 접근해서는 안됩니다.
- 그러나 이러한 규칙들은 강제하지 않는 팀이 많습니다.
- '컴포넌트 기반 패키지'가 필요한 이유는 다음과 같으며, 큰 단위의 단일 컴포넌트와 관련된 모든 책임을 하나의 자바 패키지로 묶는 데 주안점을 둡니다.
엉클 밥의 컴포넌트 정의 : 컴포넌트는 배포 단위다. 컴포넌트는 시스템의 구성 요소로 배포할 수 있는 가장 단위다. 자바의 jar 파일이 컴포넌트다.
- 그러나 이 책에서의 정의는 '컴포넌트는 멋지고 깔끔한 인터페이스로 감싸진 연관된 기능들의 묶음으로, 애플리케이션과 같은 실행 환경 내부에 존재한다' 입니다.
- 이는 시스템의 정적 구조를 컨테이너, 컴포넌트, 클래스의 측면에서 계층적으로 생각하는 간단한 방법입니다.
- 컴포넌트 기반 패키지 접근법의 주된 이점은 업무 로직은 데이터 영속성과 분리되어 있다는 점입니다.
- 이는 MSA나 서비스 지향 아키텍처를 적용했을 때 얻는 장점과 유사합니다.
- 컴포넌트를 잘 정의함으로서 마이크로 서비스 아키텍처로 가기 위한 발판으로 삼을 수 있습니다.
#
구현 세부사항엔 항상 문제가 있다- 많은 사람들이 자바에서
public
을 무분별하게 사용하며, 이는 프로그래밍 언어가 제공하는 캡슐화 관련 이점을 활용하지 않겠다는 의미입니다.
#
조직화 vs 캡슐화- 위의 경우처럼 자바 애플리케이션에서 모든 타입을 public으로 지정하면, 패키지는 단순히 조직화를 위한 메커니즘으로 전락하여 캡슐화를 위한 메커니즘이 될 수 없습니다.
- 즉, 앞에서 나온 네가지 아키텍처 접근법이 본질적으로 같아집니다.
- 자바에서 접근 지시자를 적절하게 사용한다면, 타입을 패키지로 배치하는 방식에 따라 각 타입에 접근할 수 있는 정도가 실제로 크게 달라질 수 있습니다.
- 각각의 접근법은 여러개의 장점과 단점을 가집니다.
- '계층 기반 패키지'는 구현체 클래스를 더 제한적으로 선언할 수 있습니다.
- '기능 기반 패키지' 접근법에서는 패키지가 들어올 수 있는 유일한 통로를 제공하므로 나머지는 모두 패키지 protected로 지정할 수 있습니다. 또한 외부에서는 컨트롤러로만 접속이 가능합니다.
- '포트와 어댑터'는 인터페이스는 외부로부터 들어오는 의존성을 가지므로 public을 지정해야하며, 이 경우 구현 클래스는 패키지 protected 로 지정하며, 런타임에 의존성을 주입할 수 있습니다.
- '컴포넌트 기반'은 인터페이스로 향하는 의존성을 가지며, 그 외의 모든 것은 패키지 protected 로 지정할 수 있습니다.
- 다만 앞에서 나온 부분은 모노리틱 애플리케이션에 대한 것이며, 아키텍처 원칙은 컴파일러에 의지해야 합니다.
#
다른 결합 분리 모드- 프로그래밍 언어가 제공하는 방법외에도 소스 코드 의존성을 분리하는 방법은 존재할 수 있습니다.
- 또한 소스 코드 수준에서 의존성을 분리하는 방법도 존재합니다. 즉, 서로 다른 소스 코드 트리로 분리하는 방법입니다.
- 그러나 이는 현실적으로 성능, 복잡성, 유지보수에서 걸리게 됩니다.
- 포트와 어댑터 접근법을 적용할 때는 이보다 간단한 방법을 사용하기도 하며 이는 도메인 코드(내부) 와 인프라 코드(외부) 로 표현하는 방법입니다.
#
결론: 빠져 있는 조언- 구현 전략에 얽힌 복잡함을 고려하지 않으면 설계가 순식간에 망가질 수도 있습니다.
- 설계를 어떻게 해야할지, 코드를 어떻게 조직화해야할지, 런타임과 컴파일타임에 어떤 결합 분리 모드를 적용할지를 고민해야합니다.
- 가능하면 선택사항을 열어두고, 실용주의적으로 행해야 합니다.
- 팀의 규모, 기술 수준, 해결책의 복잡성을 일정과 예산이라는 제약과 동시에 고려해야합니다.
- 선택한 아키텍처 스타일을 강제하는데 컴파일러의 도움을 받을 수 있을 지 고민하며, 데이터 모델과 같은 다른 영역에 결합되지 않도록 주의해야 합니다. 구현 세부사항에는 항상 문제가 있습니다.