움퍼스 사냥 게임을 통해 보는 아키텍처

- 게임을 여러 시장에서 다양한 언어로 발매할 수 있게 만들려고 할때 위의 그림과 같이 소스 코드 의존성을 관리한다면 UI 컴포넌트가 어떤 언어를 사용하더라도 게임 규칙을 재사용할 수 있다
- 마찬가지로 게임의 상태를 영속적인 저장소에 유지해야 할 때 게임 규칙이 이러한 세부사항을 알지못하도록 소스코드 의존성을 관리한다.
클린 아키텍처

- UI에서 언어가 유일한 변경의 축이 아니다. 이 밖에도 텍스트를 주고받는 메커니즘(Text Delivery)을 다양하게 만들고 싶을 수도 있다.
- 따라서 이 변경의 축에 의해 정의되는 아키텍처 경계가 잠재되어 있을 수도 있다. 해당 경계를 가로지르는 그래서 언어를 통신 메커니즘 으로부터 격리하는 API가 필요할 수 있다. (위 그림의
Text Delivery
와Language
추상 컴포넌트)
- API는 사용하는 쪽에 정의되고 소속됨(구현하는 쪽이 아님)
- GameRules는 GameRules가 정의하고(인터페이스) Language가 구현하는 API(구현)를 이용해 Language와 통신
- 마찬가지로, Language는 Language가 정의하고 Text Delivery가 구현하는 API를 이용해 TextDelivery와 통신
- 점선으로 된 테두리는 API를 정의하는 추상 컴포넌트. 해당 API를 추상 컴포넌트 위나 아래의 컴포넌트가 구현함

- 정보가 흐르는 방향을 생각해보면 모든 입력은 사용자로부터 전달받아 좌측 하단의
TextDelivery
컴포넌트로 전달됨. 이 정보는Language
컴포넌트를 거쳐서 위로 올라가며,GameRules
에 적합한 명령어로 번역됨.GameRules
는 사용자 입력을 처리하고, 우측 하단의DataStorage
로 적절한 데이터를 내려 보냄.
- 그런 후 GameRules 는 Language로 출력을 되돌려 보내고, Language는 API를 다시 적절한 언어로 번역한 후 번역된 언어를 TextDelivery를 통해 사용자에게 전달함
- 이 구성은 데이터 흐름을 두 개의 흐름으로 효과적으로 분리함
- 왼쪽의 흐름은 사용자와의 통신에 관여
- 오른쪽의 흐름은 데이터 영속성에 관여
흐름 횡단하기

- 위 예제처럼 데이터의 흐름이 항상 두가지 이지는 않음. 만약 움퍼스 사냥게임을 네트워크 상에서 여러 사람이 함께 플레이할 수 있게 만들어야 한다면 위와 같이 네트워크 컴포넌트를 추가해야 함
- 이 구성은 데이터 흐름을 세 개의 흐름으로 분리 하며, 이들 흐름은 모두
GameRules
가 제어함
- 시스템이 복잡해 질수록 컴포넌트 구조는 더 많은 흐름으로 분리됨
흐름 분리하기
이쯤 되면 모든 흐름이 결국에는
상단의 단일 컴포넌트에서 서로 만난다고 생각할 수도 있다
. 그러나 현실은 훨씬 복잡하다.- 예를 들어 GameRules 컴포넌트에서 게임 규칙 중 일부는 지도와 관련된 메커니즘을 처리(
Move Management
)(동굴이 서로 어떻게 연결될 지, 각 동굴에 어떤 물체가 위치할지, 플레이어가 동굴에서 동굴로 이동하는 방법, 등을 알고 있음)
- 이보다 더 높은 수준의 또 다른 정책집합이 존재하는데 이는 플레이어의 생명력, 그리고 특정 사건을 해결하는 비용과 얻게 될 소득 등을 알고 있는 정책(
Player Management
)
- 저수준 메커니즘(이동)과 관련된 정책에서는 이러한 고수준 정책에게 FoundFood(식량 발견)나 FellInFit(구덩이에 빠짐)과 같은 사건이 발생했음을 알리고 그러면 고수준 정책에서는 플레이어의 상태를 관리함

- 대규모의 플레이어가 동시에 플레이할 수 있는 버전의 움퍼스 사냥게임을 가정해보자
MoveManagement
는 플레이어의 로컬에서 직접 처리되지만PlayerManagement
는 서버에서 처리됨PlayerManagement
는 접속된 모든MoveManagement
컴포넌트에 마이크로서비스 API를 제공

MoveManagement
와PlayerManagement
사이에는 완벽한 형태의 아키텍처 경계가 존재함
결론
- 이 예제를 가져온 이유는 아키텍처 경계가 어디에나 존재한다는 사실을 보여주기 위함임
- 아키텍트로서 우리는 아키텍처 경계가 언제 필요한지를 신중하게 파악해내야 함.
- 또한 우리는 이러한 경계를 제대로 구현하려면 비용이 많이 든다는 사실도 인지하고 있어야 함
- 이와 동시에 이러한 경계가 무시되었다면 나중에 다시 추가하는 비용이 크다는 사실도 알아야 한다.(포괄적인 테스트 스위트와 리팩터링으로 단련되었더라도 마찬가지임)
- 추상화가 필요하리라고 미리 예측해서는 안된다(YAGNI. 오버엔지니어링이 언더엔지니어링 보다 나쁠 때가 훨씬 많다). 다른 한편으로 어떤 아키텍처 경계도 존재하지 않는 상황에서 경계가 정말로 필요하다는 사실을 발견 후 그때서야 경계 추가하려면 비용이 많이 들고 큰 위험을 감수해야 함
- 소프트웨어 아키텍트는 미래를 내다봐야만 한다. 현명하게 추측해야만 한다. 비용을 산정하고, 어디에 아키텍처 경계를 둬야 할지, 그리고 완벽하게 구현할 경계는 무엇인지와 부분적으로 구현할 경계와 무시할 경계는 무엇인지를 결정 해야만 한다.
- 하지만 이는 일회성 결정은 아니다. 프로젝트 초반에는 구현할 경계가 무엇인지와 무시할 경계가 무엇인지를 쉽게 결정할 수 없다. 대신 지켜봐야 한다. 시스템이 발전함에 따라 주의를 기울여야 한다.
- 경계가 필요할 수도 있는 부분에 주목하고, 경계가 존재하지 않아 생기는 마찰의 어렴풋한 첫 조짐을 신중하게 관찰해야 한다.
- 첫 조짐이 보이는 시점이 되면, 해당 경계를 구현하는 비용과 무시할 때 감수할 비용을 가늠해 본다. 그리고 결정된 사항을 자주 검토한다. 우리의 목표는 경계의 구현 비용이 그걸 무시해서 생기는 비용보다 적어지는 바로 그 변곡점에서 경계를 구현하는 것이다.
목표를 달성하려면 빈틈없이 지켜봐야 한다.