
규모가 큰 프로젝트에는 다수의 모델이 공존하며, 많은 경우 다수의 모듈을 토대로 작업을 순조롭게 진행할 수 있다. 서로 다른 모델은 서로 다른 컨텍스트에 적용된다. 예를 들어, 새롭게 개발 중인 소프트웨어를 직접 통제할 수 없는 외부 시스템과 통합해야 한다고 해보자. 이 경우 개발 중인 모델을 적용할 수 없는 별개의 컨텍스트를 다루기 때문에 다른 모델을 적용해야 한다는 사실이 명확하지만 어떤 상황에서는 판단하기가 다소 모호하고 혼란스러울 수 있다. 본 장의 처음에 소개한 이야기에서 두 팀은 동일한 시스템에 포함될 예정인 서로 다른 기능을 개발하고 있었다. 두 팀은 동일한 모델을 기반으로 개발을 진행하고 있었는가? 적어도 함께 작업해야 하는 부분과 관련된 일부 모델이라도 공유하자는 것이 두 팀의 의도였지만 무엇을 공유하고 무엇을 공유하지 않을지를 명시하는 어떠한 경계도 마련돼 있지 않았고 공유 모델을 유지하거나 차이를 빠르게 감지할 수 있는 적절한 프로세스도 없었다. 두 팀은 갑자기 시스템이 예측 불가능한 상태에 빠지고 나서야 비로소 모델에 차이가 발생했다는 사실을 깨달았다.
심지어 한 팀 내에서도 다수의 모델이 공존할 수 있다. 이 경우 의사소통이 둔화되어 동일한 모델을 미묘하지만 상반되게 해석할 수도 있다. 종종 오래된 코드에서 현재의 모델이 내포하고 있는 개념과는 미묘한 차이를 보이는 초기 모델의 개념을 반영하고 있는 경우를 발견하곤 한다.
다른 시스템에서 관리하는 데이터의 형식이 자신의 시스템과 다른 탓에 데이터 변환 작업이 필요하다는 사실은 누구나 알고 있지만 이것은 단지 기계적인 차원의 문제일 뿐이다. 더 근본적인 문제는 두 시스템 내에 존재하는 암시적인 모델 간의 차이다. 모순이 외부 시스템 간에 발생하는 것이 아니라 동일한 코드 기반 내에서 발생할 경우 이를 인식하기가 더 어렵다. 그러나 이것은 모든 대규모 팀 프로젝트에서 발생하는 일이다.
규모가 큰 프로젝트에서는 다수의 모델이 사용되기 마련이다. 그러나 개별적인 모델을 기반으로 작성된 코드가 한데 섞이면 많은 버그가 발생하고 신뢰성이 떨어지며 이해하기 힘든 소프트웨어가 만들어진다. 아울러 팀 구성원 간의 의사소통이 혼란스러워진다. 종종 어떤 컨텍스트에서 어떤 모델을 사용해서는 안 되는지 불분명한 경우도 있다.
모델을 올바른 상태로 유지하는 데 실패했는가는 결국 실행 중인 코드가 정상적으로 동작하지 않을 때 드러나지만 문제의 원인은 팀이 조직되는 방식과 사람들이 상호작용하는 방식에 있다. 그러므로 모델의 컨텍스트를 명확하게 만들려면 프로젝트와 산출물(코드, 데이터베이스 스키마 등)을 모두 살펴봐야 한다.
모델은 컨텍스트에 적용된다. 컨텍스트는 코드의 특정 부분일 수도, 개별 팀이 수행하는 업무일 수도 있다. 브레인스토밍 회의를 거쳐 만들어진 모델의 경우, 회의에서 오간 대화로 컨텍스트를 국한시킬 수 있다. 이 책의 예제에 사용된 모델의 컨텍스트는 예제를 소개한 부분과 그에 대해 차후에 이어지는 토론에 해당한다. 모델 컨텍스트란 모델에서 사용된 용어를 특정한 의미로 의사소통하기 위한 조건의 집합이다.
다수의 모델 탓에 발생하는 문제를 해결하려면 하나의 모델이 적용되고 가능한 한 통일된 상태로 유지할 수 있는 소프트웨어 내의 제한된 부분으로 특정 모델의 범위를 명확하게 정의할 필요가 있다. 여기서 정의한 바는 팀의 구성과 조화를 이뤄야 한다.
그러므로
모델이 적용되는 컨텍스트를 명시적으로 정의하라. 컨텍스트의 경계를 팀 조직, 애플리케이션의 특정 부분에서의 사용법, 코드 기반이나 데이터베이스 스키마와 같은 물리적인 형태의 관점에서 명시적으로 설정하라. 이 경계 내에서는 모델을 엄격하게 일관된 상태로 유지하고 경계 바깥의 이슈 때문에 초점이 흐려지거나 혼란스러워져서는 안 된다.
BOUNDED CONTEXT는 팀 구성원이 어떤 부분에서 일관성을 지녀야 하고 다른 CONTEXT와 어떤 식으로 관련돼 있는가를 서로 명확하게 이해할 수 있게 특정 모델의 적용 범위를 제한한다. CONTEXT 내에서는 모델을 논리적으로 통일된 상태로 유지하되, 경계 외부에 대한 적절성에 대해서는 신경 쓰지 않아도 된다. 서로 다른 컨텍스트에 대해서는 용어, 개념과 규칙, UBIQUITOUS LANGUAGE에 포함된 독특한 표현 방식(dialect)에 차이를 보이는 서로 다른 모델을 적용한다. 명확한 경계를 정의함으로써 해당 모델을 적용할 수 있는 영역 내에서 모델을 순수하게(따라서 유용하게) 유지하고, 동시에 다른 CONTEXT로 초점을 옮길 때 혼란을 피할 수 있다. 여러 경계에 걸쳐 이뤄지는 통합에는 필수적으로 어느 정도의 번역(translation)이 수반될 것이며, 여러분은 이를 명시적으로 분석할 수 있을 것이다.
BOUNDED CONTEXT는 MODULE이 아니다
간혹 두 용어를 혼동하는 경우가 있는데 BOUNDED CONTEXT와 MODULE은 서로 동기가 다른 패턴이다. 사실 어떤 두 객체 집합이 각기 다른 모델을 구성한다고 여겨지면 두 객체 집합은 거의 항상 서로 다른 개별 MODULE 내에 위치한다. 이렇게 해서 서로 다른 네임스페이스(서로 다른 CONTEXT를 위한 필수 요소)와 일종의 경계를 제공할 수 있다.그러나 MODULE은 단일 모델 내에 포함된 요소를 구성하는 데도 사용되며, 꼭 개별 CONTEXT에 의도를 전하는 것은 아니다. 실제로는 BOUNDED CONTEXT 내에 MODULE이 만들어낸 개별 네임스페이스가 포함되면 우발적으로 발생하는 모델의 단편화를 파악하기가 더욱 어려워진다.
예제
예약 컨텍스트
어떤 해운 회사에서 화물운송 예약에 사용할 신규 애플리케이션을 개발하는 내부 프로젝트를 진행 중이다. 이 애플리케이션은 객체 모델로 진행될 예정이다. 이 모델이 적용되는 BOUNDED CONTEXT는 무엇인가? 이 질문에 답하려면 프로젝트에서 일어나고 있는 일을 살펴봐야 한다. 염두에 둘 것은 프로젝트를 있는 그대로 봐야지, 이상적인 프로젝트를 생각해서는 안 된다는 것이다.
한 프로젝트 팀에서는 예약 애플리케이션 자체를 작업 중이다. 그 프로젝트 팀에서는 모델 객체를 수정하지 않겠지만 해당 팀에서 구축하고 있는 애플리케이션에서는 그와 같은 객체를 보여주고 조작해야 한다. 이 팀은 모델의 소비자에 해당한다. 모델은 애플리케이션(모델의 주요 소비자) 내에서 유효하며, 따라서 예약 애플리케이션은 구획 내에 존재한다고 볼 수 있다.
예약이 완료되면 해당 예약 내역은 레거시 화물추적 시스템으로 전달돼야 한다. 새로운 모델은 레거시 모델과 구분하기로 사전에 결정했으므로 레거시 화물추적 시스템은 구획 밖에 존재한다. 새로운 모델과 레거시 사이에 필요한 번역은 레거시 유지보수팀의 몫이다. 번역 메커니즘은 모델에 의해 주도되지 않는다. 번역 메커니즘은 BOUNDED CONTEXT 내에 존재하지 않기 때문이다. (번역 메커니즘은 경계 자체의 일부인데, 이것에 관해서는 CONTEXT MAP에서 논의하겠다.) 번역은 CONTEXT(모델을 기반으로 하지 않는) 밖에 있는 것이 좋다. 그렇게 하면 레거시 팀의 주요 업무가 CONTEXT 밖에 있으므로 레거시 팀에서 실제로 모델을 사용하기 위해 요청하는 것이 불가능해질 것이다.
모델을 책임지는 팀에서는 영속화를 비롯한 각 객체의 전체 생명주기를 다룬다. 이 팀에서 데이터베이스 스키마를 통제하므로 그 팀에서 신중하게 객체 관계형 매핑을 명확하게 유지해 오고 있다. 다시 말해서, 스키마는 모델에 의해 주도되며, 따라서 구획 안에 존재한다.
또 다른 팀에서는 화물선 운항일정을 관리하는 모델과 애플리케이션을 개발 중이다. 일정관리 팀과 예약 팀은 함께 프로젝트에 착수했고, 두 팀의 목표는 하나의 단일화된 시스템을 만들어내는 것이었다. 두 팀은 비공식적으로 서로 조율하는 경우가 있었고 경우에 따라서는 객체를 공유하기도 하는데, 그와 같은 협업이 체계적으로 이뤄지는 것은 아니었다. 두 팀은 동일한 BOUNDED CONTEXT 내에서 일하고 있는 게 아니다. 이렇게 하는 것은 위험한데, 그 이유는 두 팀 스스로가 개별 모델을 이용하는 것으로 생각하지 않기 때문이다. 두 팀의 통합 정도에 따라 그와 같은 상황을 관리할 적절한 프로세스를 마련하지 않는다면 문제가 발생할 것이다. (본 장의 후반부에서 논의할 SHARED KERNEL이 좋은 대안일지도 모른다). 그렇지만 우선은 상황을 있는 그대로 인식해야 한다. 두 팀이 같은 CONTEXT 안에 있지 않다면 어느 정도의 변화가 생기기 전까지는 코드를 공유하려 해서는 안 된다.
이러한 BOUNDED CONTEXT는 모델 객체와 모델 객체를 영속화하는 데이터베이스 스키마, 예약 애플리케이션과 같은 특정 모델에 의해 주도되는 시스템의 그러한 모든 측면으로 구성된다. 모델링 팀과 애플리케이션 팀은 주로 이 CONTEXT 안에서 업무를 수행한다. 모델링 팀과 애플리케이션 팀은 레거시 관리 시스템과 정보를 주고받아야 하며, 레거시 팀의 주된 책임은 모델링 팀과의 협업을 토대로 이러한 경계에서 번역을 수행하는 것이다. 예약 모델과 운항일정 모델 간의 관계는 명확하게 정의된 바가 없으므로 이들 팀에서는 이와 같은 관계를 가장 먼저 정의해야 한다. 동시에 코드나 데이터를 공유하는 부분에서는 매우 신중을 기해야 한다.
그럼 이러한 BOUNDED CONTEXT를 정의해서 얻을 수 있는 건 뭘까? CONTEXT 안에서 업무를 진행하는 팀에서 얻게 되는 것은 바로 명확함이다. 두 팀은 자신들이 하나의 모델과 일관성을 유지해야 한다는 점을 알고 있다. 두 팀은 그와 같은 지식의 범위 내에서 설계 결정을 내리고 틈이 생기지는 않는지 살펴야 한다. 반면 BOUNDED CONTEXT 밖의 팀이 얻게 되는 것은 바로 자유로움이다. BOUNDED CONTEXT 안의 팀과 밖의 팀이 동일한 모델을 사용하지는 않는데, 어떤 면에서는 그래야 하지 않나 생각하면서 이도 저도 아닌 생각을 할 필요는 없다. 그러나 이처럼 특수한 경우에서 가장 실질적으로 얻을 수 있는 이득은 아마 예약 모델 팀과 운항일정 팀에서 비공식적인 정보 공유가 위험하다는 사실을 인지하는 것이리라. 문제가 일어나지 않게 하려면 두 팀은 정보를 공유했을 때 발생하는 비용과 이득의 타협점을 결정하고 정보 공유가 잘 될 수 있게 프로세스로 정립할 필요가 있다. 하지만 모든 이가 모델의 경계가 공존하는 곳을 알지 못한다면 이 같은 일은 일어나지 않을 것이다.
물론 경계는 특별한 곳이다. BOUNDED CONTEXT와 이웃하는 BOUNDED CONTEXT 간의 관계는 보살핌과 주의가 필요하다. 일부 패턴이 CONTEXT 간의 다양한 관계의 특성을 정의하는 반면 CONTEXT MAP은 여러 CONTEXT와 각 CONTEXT 사이의 연결이라는 큰 그림을 제시하면서 각 CONTEXT가 차지하는 영역을 보여준다. 또한 CONTINUOUS INTEGRATION 프로세스는 BOUNDED CONTEXT 내에 존재하는 모델의 단일성을 유지해준다.
그러나 이런 것들을 모두 살펴보기에 앞서 모델의 단일화가 깨진다면 어떤 모습을 띠게 될까? 어떻게 개념적 균열을 인식할 수 있을까?
BOUNDED CONTEXT 안의 균열 인식
여러 징후를 바탕으로 미처 인식하지 못한 모델의 차이점이 나타날 수도 있다. 일부 두드러진 징후 가운데 하나는 바로 코드로 작성된 인터페이스가 서로 맞지 않는 경우다. 좀더 미묘하게 예상치 못한 행위가 신호가 되는 경우도 있다. 자동화된 테스트를 이용한 CONTINUOUS INTEGRATION 프로세스가 이러한 문제점을 발견하는 데 도움될 수 있다. 그러나 초기 징후는 대개 언어를 혼동한 상태로 구사하는 데서 나타난다.
뚜렷이 구분되는 모델 요소를 결합할 경우 두 가지 종류의 문제가 일어나게 되는데, 바로 중복된 개념과 허위 동족 언어(false cognates)다. 중복된 개념이란 실제로 같은 개념을 나타내는 두 개의 모델 요소(그리고 그것에 따르는 구현)가 존재하는 것이다. 따라서 이 같은 정보가 변경될 때마다 두 군데를 갱신하고 변환해야 한다. 새로운 지식으로 여러 객체 중 한 객체가 바뀔 때마다 다른 하나도 다시 분석하고 바꿔야 한다. 실제로 객체를 다시 분석하지 않는다면 개념은 동일하지만 서로 다른 규칙을 따르고 심지어 데이터까지 다른 두 가지 버전의 객체가 존재하게 된다. 이러한 상황에서는 팀원들이 동기화를 위한 방법뿐 아니라 같은 일을 하는 데도 두 가지 방법을 배워야 한다.
허위 동족 언어는 그리 일반적으로 나타나지는 않지만 좀더 교묘한 방식으로 해를 끼친다. 허위 동족 언어는 같은 용어(혹은 구현 객체)를 사용하는 두 사람이 서로 같은 것을 이야기하고 있다고 생각하지만 실제로는 그렇지 않은 경우를 말한다. 본 장의 초반부에 나오는 예제(똑같이 Charge라고 부르는 두 가지 서로 다른 업무 활동)가 허위 동족 언어의 전형적인 예인데, 이러한 개념상의 충돌은 두 정의가 실제 도메인에서의 동일한 측면과 관련돼 있지만 약간 다른 방식으로 개념화됐을 때 훨씬 알아차리기 힘들 수 있다. 허위 동족 언어 탓에 개발 팀은 서로의 코드를 침범하게 되고, 데이터베이스에 이상한 불일치가 생기고, 팀 내 의사소통이 혼란스러워질 수 있다. 허위 동족 언어라는 용어는 주로 자연어에 적용되는 용어다. 이를테면, 스페인어를 배우는 영어 사용자는 주로 embarazada라는 단어를 잘못 사용하곤 하는데, 이 단어는 “당황한”을 의미하는 것이 아니라 “임신한”을 의미한다. 이런!
이러한 문제를 감지했다면 팀에서 결정을 내려야 한다. 여러분은 모델에서 한 걸음 물러나 프로세스를 정제해서 단편화를 막으려 할지도 모른다. 아니면 여러 집단이 모델을 합당한 이유에서 서로 다른 방향으로 이끈 결과로 단편화가 나타날 수도 있으므로 각 모델을 독립적으로 개발하기로 결정할 수도 있다. 이러한 문제를 다루는 것이 바로 본 장의 나머지 패턴이 다루는 주제에 해당한다.