시스템은 주로 UI, 비즈니스 로직, 데이터베이스 세 가지 컴포넌트로 구성된다고 생각할 수 있지만, 대개 더 많은 컴포넌트로 구성될 수 있다. 마지막에 말하지만 쪼개려면 얼마든지 더 작은 컴포넌트들로 분리할 수 있다.
움퍼스 사냥 게임
움퍼스 사냥 게임은 간단히 GO EAST 와 SHOOT WEST 같은 명령어를 사용한다. 플레이어는 명령어를 입력하고, 컴퓨터는 명령어 기반으로 동작을 수행한다.
만약 영어로 되어 있는 현재 상태에서 한글 서비스를 추가하고 싶을 경우 어떻게 만들어야 할지 상상해보자. 명령을 내리는 부분과 게임을 구분하여 영어 UI, 한글 UI 에서 게임을 사용하면 된다.
추가로 기존에 메모리에 저장했던 것을 클라우드에 저장하고 싶은 요건이 생겼다면 클라우드 데이터가 게임을 호출하도록 하면 된다.
모든 부분적인 것들이 게임을 의존하고, 게임은 아무것도 의존하지 않게 만들면 게임의 비즈니스 로직은 다른 것들의 변경으로 인해 변경해야 하는 상황이 발생하지 않을 것이다.
클린 아키텍처?
UI 와 게임 그리고 데이터베이스는 구분했는데 여기서 더 경계를 나눌 수 있을까? UI 에서 언어가 유일한 변경의 축이 아니다. 만약 콘솔로 사용하고 싶고, 채팅이나 메시지도 사용하고 싶은 경우는 어떻게 될까?
이 책에서는 콘솔과 메시지를 추상화한 Text Delivery
컴포넌트를 만들고, English 와 한글을 추상화한 Language
컴포넌트를 만들어 Text Delivery
가 Language
컴포넌트를 사용하게 구현했다. 전체 로직은 SMS
Console
-> Text Delivery
-> Language
-> Game Rules
가 된다.
이제 데이터 흐름은 Text Delivery
-> Language
-> Game Rules
로 올라가고, Game Rules
-> Data Storage
로 내려간다. 비즈니스 로직이 데이터와 UI 의 구분이 된다.
흐름 횡단하기
흐름이 사용자 -> 게임, 게임 -> 데이터 이렇게 항상 두 가지일까? 전혀 아니다. 네트워크를 사용해 여러 사용자가 함께 게임할 수 있다면 네트워크 컴포넌트도 추가해야 한다.
흐름 분리하기
모든 흐름이 Game Rules
에서 서로 만나지만, 항상 이렇게 단순하지 않다. Game Ruels
가 더 많은 컴포넌트로 나뉠 때는 어떻게 할까?
움퍼스 게임에 이동하는 정책과 더 상위 정책인 플레이어의 생명과 관련된 정책이 있다고 할 때, 하위 정책인 Move Management
가 Player Management
를 사용하게 된다. 당장은 컴포넌트를 분리할 필요는 없지만 만약 사용자 이동은 각 사용자의 PC에서, 그리고 결과는 게임 서버에서 처리된다고 했을 때, 두 컴포넌트는 분리되고, Player Management
는 Move Management
에 마이크로서비스 API 를 제공해야 한다.
결론
처음에 말했듯이 컴포넌트는 언제든 더 많은 계층과 경계로 나뉠 수 있다. 움퍼스 게임만 봐도 처음 시작은 단순히 200 라인의 코드였지만, 마지막 아키텍처의 구조는 200 라인으로는 턱도 없다. 그럼 항상 이렇게 많은 컴포넌트로 분리해야 하나?
여기서 얻은 결론은 오버 엔지니어링이 언더 엔지니어링보다 나쁠 때가 훨신 많다는 것이다. 소프트웨어 아키텍트는 미래를 내다봐야 한다. 비용을 산정하고 아키텍처 경계를 두어야 할지 고민해야 한다. 다행인 것은 처음 내린 결정을 절대 수정하지 못한다는 것은 아니라는 것이다.
프로젝트 초반에는 구현할 경계가 무엇인지와 무시할 경계가 무엇인지를 쉽게 결정할 수 없다. 대신 지켜봐야 한다.
...
경계가 필요할 수도 있는 부분에 주목하고, 경계가 존재하지 않아 생기는 마찰의 어렴풋한 첫 조짐을 신중하게 관찰해야 한다. 첫 조짐이 보이는 시점이 되면, 해당 경계를 구현하는 비용과 무시할 때 감수할 비용을 가늠해 본다. 그리고 결정된 사항을 자주 검토한다. 우리의 목표는 경계의 구현 비용이 그걸 무시해서 생기는 비용보다 적어지는 바로 그 변곡점에서 경계를 구현하는 것이다.