-
도메인 주도 설계의 적용-4.ORM과 투명한 영속성 1부[옛날 글들] 도메인 주도 설계 2024. 4. 23. 07:59728x90
이전글 : 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 5부
이 글은 제가 2008년 6월부터 10월까지 5개월간 마이크로소프트웨어에 연재했던 "도메인 주도 설계의 적용"이라는 원고의 원글입니다. 잡지에 맞추어 편집을 하는 과정에서 지면 상의 제약으로 인해 수정되거나 삭제된 부분이 있어 제 블로그에 원글을 올립니다. 도메인 주도 설계(Domain-Driven Design)에 관심 있는 분들에게 도움이 되었으면 합니다.
코드와 모델을 밀접하게 연관시키는 것은 코드에 의미를 부여하고 모델을 적절하게 한다.
-Eric Evans뒤돌아보기
“우리는 주문 시스템이 필요해요.”
모든 것은 이 한마디로부터 시작됐다. 고객은 주문 시스템을 원한다. 이 시스템이 무엇을 해야 하는지는 아직 잘 모르지만 주문이라는 개념이 도메인의 핵심을 이루는 것 같다. 팀을 구성하고 계획을 세우면서 지속적으로 고객과의 대화를 통해 시스템의 전체적인 윤곽을 그려나갔다. 처음에는 고객이 사용하는 용어가 낯설고 이해하기 힘들었지만 도메인에 대한 이해가 깊어질수록 고객들과의 의사소통도 크게 문제가 되지 않았다. 고객들 역시 처음에는 UML, REPOSITORY, XP, JUnit과 같은 용어를 사용하는 개발자들을 무슨 화성에서 온 외계인인 양 쳐다보았지만 지금은 교육과 의사소통을 통해 개발자들의 용어와 적용 기술을 대략적으로나마 이해하게 되었다.
고객들과의 지속적인 협력을 통해 도메인을 구성하는 중요 개념들을 식별하고 이를 화이트보드 상에 간단한 UML로 스케치하여 짧은 검토회의를 거쳤다. 고객들은 적은 시간의 교육만으로도 UML의 클래스 다이어그램과 오브젝트 다이어그램을 이해할 수 있었다. 개발자들은 UML을 구현 명세가 아닌 설계 스케치 용도로 사용함으로써 회의 참가자들이 시스템에 반영될 도메인 개념과 개념들 간의 관계에 집중할 수 있도록 유도했다. 클래스 다이어그램의 클래스와 속성, 책임에 사용되는 용어들은 고객들이 업무에서 실제로 쓰고 있는 용어를 사용했다. 이 용어들은 고객과 개발자들이 공통으로 사용하기로 한 것들이다. 물론 적절한 용어가 존재하지 않을 경우 고객들과의 협의를 통해 새로운 용어를 만들기도 했다. 그 용어가 무엇이든 현재는 고객과 개발자들이 주문 도메인을 동일한 언어와 동일한 의미로 바라볼 수 있도록 했다는 점에 그 의의를 두어야겠다..
짧은 검토 회의를 마친 후에 화이트보드에 그려진 UML 다이어그램들을 디지털카메라로 찍은 후 자리로 돌아와 짝 프로그래머와 함께 식별된 도메인 개념들과 이들의 관계를 코드로 옮기기 시작했다. 물론 실패하는 테스트부터 작성하고 테스트를 통과하도록 코드를 작성하는 규칙에 따라 시스템을 구현해 나갔다. 도메인에는 고객, 주문, 주문 항목, 상품, 금액이라는 개념이 필요하다. 우리는 이 개념들을 고객이 사용하는 용어 그대로 Customer, Order, OrderLineItem, Product, Money라고 명명하기로 했다. 이 클래스들은 고객들이 보더라도 무엇을 의미하는지 한눈에 알 수 있을 것이다. 각 클래스의 속성과 메서드를 할당할 때에도 원칙은 동일하다. 고객의 용어를 사용한다. 그런 용어가 존재하지 않는다면 고객과의 토의를 통해 선정된 적절한 용어를 사용한다.
잘 운영되던 시스템이 다운되자 그동안 묻어 두었던 문제점이 노출되었다. 시스템이 깨끗하게 초기화된 것이다. 고객 정보도 주문 정보도, 그 어느 것 하나 남아있지 않고 공중으로 증발해 버리고 말았다. 그동안 아무 일도 없었다는 듯 멍하니 돌아가고 있는 시스템을 보자니 울화통이 치민다. 도메인 정보의 영속성을 보장하기 위해 관계형 데이터베이스를 도입하기로 결정하고 데이터베이스 접근 코드에 의한 파급 효과를 제한하고 테스트를 용이하기 위해 전체 시스템의 결합도를 낮춰야 한다는 주장이 제기됐다. 우리는 의존성 주입을 지원하는 Spring 프레임워크를 도입하기로 결정했으며 데이터베이스 로직을 캡슐화할 수 있도록 하기 위해 리포지토리를 리팩터링 하여 인터페이스와 구현 클래스로 분리했다.
비록 몇 가지 기술적인 이슈로 인해 시스템에 몇 가지 변경이 있었지만 도메인 모델 자체의 변화는 거의 없었다. 고객의 개념과 용어를 사용하여 도메인을 모델링하고 모델링 된 도메인을 별다른 수정 없이 분석, 설계, 구현 전과정에 걸쳐 사용했다. Spring이라는 새로운 프레임워크를 도입했지만 도메인 모델 자체를 하위 인프라 스트럭처로부터 고립시켰기 때문에 이로 인한 도메인 모델의 변경은 미미한 수준이었다.
우리의 도메인 모델은 고객의 용어로 말하고 고객의 의도대로 행동한다. 도메인 모델은 도메인에 관해서 이야기하지만 도메인과 무관한 기술적인 부분에 관해서는 이야기하지 않는다. 분석, 설계, 구현에 이르기까지 도메인 모델의 기본적인 형태는 변하지 않고 유지된다. 이는 도메인에 관한 정신적 모델과 소프트웨어 모델 간의 차이를 의미하는 “표현적 차이(representation gap)“ 또는 “의미적 차이(semantic gap)”라는 논점과 관련이 있다. 소프트웨어 모델, 즉 코드가 도메인 모델을 떠오르게 하고 도메인 모델을 바탕으로 예측가능한 방식으로 작동할 경우 두 모델 간의 “표현적 차이가 적다”라고 한다.
도메인 주도 설계는 도메인 모델과 소프트웨어 모델, 즉 코드 간의 표현적 차이를 최소화하기 위한 접근 방법이다. 도메인 주도 설계 EJB와 같은 구현 기술이 소프트웨어 개발을 주도함으로써 발생되는 여러 가지 문제점을 해결하기 위해 기술 주도적인 방식이 아닌 도메인 주도적인 방식으로 소프트웨어를 개발할 것을 주장한다.
도메인 주도 설계를 성공적으로 적용하기 위해서는 기본적인 두 가지 요소가 갖추어져야 한다. 하나는 유비쿼터스 언어(UBIQUITOUS LANGUAGE)로 고객과 개발자들 사이에 공통된 용어를 사용하도록 함으로써 의사소통의 단절 및 오해로 인해 잘못된 소프트웨어가 개발되는 것을 방지할 수 있도록 한다. 앞의 예에서 고객과 개발자들이 용어를 협의하고 소프트웨어에 고객의 용어를 반영하도록 노력하는 과정이 바로 유비쿼터스 언어를 구축해 가는 과정이라고 할 수 있다. 또 다른 하나는 모델 주도 설계(MODEL-DRIVEN DEISGN)다. 이 개념은 분석, 설계, 구현의 모든 단계를 관통하는 하나의 모델을 만들자는 개념이다. 즉, 표현적 차이를 줄임으로써 소프트웨어가 도메인의 모습을 투영하도록 만들자는 것이다. 앞의 예에서 하위의 인프라 스트럭처를 변경할 경우에도 기본적인 도메인 모델은 변경되지 않았음에 주목하자. 일반적으로 모델 주도 설계를 적용하기 위해서는 표현적 차이가 적은 객체-지향 언어를 사용하고 EJB와 같은 침투적인 인프라 스트럭처 대신 비침투적인 POJO(Plain Old Java Object) 기반의 경량 프레임워크를 적용하는 것이 적합하다.
우리는 가능한 한 주문 시스템에 고객의 용어를 반영하고 표현적 차이를 줄일 수 있도록 노력했다. 그리고 인프라 스트럭처의 변경이 핵심적인 도메인 모델에 영향을 미치지 않도록 여러 가지 원칙들을 적용해 왔다. 그렇다면 관계형 데이터베이스를 도입했을 경우에도 별다른 변경 없이 도메인 모델의 핵심 구조를 유지할 수 있을까? 답은 그렇다이다. 그러나 관계형 데이터베이스를 도입하기 위해서는 주문 시스템이라는 요리에 몇 가지 재료를 더 얹어야 한다. 엔티티에서 시작하자.
참조 객체의 별칭(aliasing) - 엔티티
참조 객체는 시스템 내에 유일하게 존재하고 상태를 추적할 수 있는 도메인 객체를 의미한다. 참조 객체는 유일성 및 추적성을 만족시키기 위해 식별자(identity)를 가지며 동일한 객체를 다른 이름으로 참조할 수 있도록 별칭(aliasing)을 허용한다. 별칭은 여러 가지 골치 아픈 문제를 야기하지만 그와 동시에 시스템의 모든 부분이 참조 객체의 상태를 공유할 수 있도록 함으로써 도메인의 무결성을 유지하도록 한다.
참조 객체의 동일함(identical)을 테스트하기 위해 ‘==’ 연산자를 사용하여 식별자를 비교할 수 있다. ‘==’ 연산자는 객체 생성 시에 자동으로 부여되는 식별자를 비교하며 대개의 경우 객체가 위치하고 있는 메모리 주소를 사용한다. 두 객체의 메모리 주소가 다르면 두 객체는 다른 것으로 판단된다.
참조 객체는 도메인 객체의 서로 다른 두 가지 측면을 설명하기 위해 사용된다. 첫번째는 참조 객체의 의미론적인 측면이다. 도메인 개념의 유일성과 추적성은 참조 객체의 필요조건이다. 참조 객체의 의미론적인 특성은 리포지토리, 애그리게이트와 같은 다른 도메인 요소에 영향을 미친다. 의미론적인 측면 배후에는 참조 객체의 기술론적인 측면이 존재한다. 참조 객체는 생성 시 메모리 주소를 기반으로 한 식별자를 할당받으며 “==” 연산자의 경우 식별자를 사용해서 객체의 동일성을 판단한다는 것을 가정한다.
만약 어떤 자료에서 참조 객체라는 용어를 발견했다면 의미론과 기술론 중 어떤 측면을 다루고 있는 지를 먼저 파악해야 한다. 대부분의 경우 이런 구분이 명확하지만 어떤 경우에는 오해의 소지가 있을 수 있으며 때때로 이런 오해가 참조 객체의 의미론적 측면을 이해하는데 방해 요소로 작용하기도 한다. 문제는 참조 객체의 의미론과 기술론이 충돌하는 영역이 존재한다는 점이다. 의미론은 도메인의 특성이다. 기술론은 언어 자체의 특성이다.
의미론이 도메인의 본질적인(essential) 특성이라면 기술론은 비본질적인(accidential) 특성이다. 본질적인 특성은 변하지 않는 반면 비본질적인 특성은 언어, 환경, 기술에 따라 영향을 받을 수 있다. Fredy Brooks의 말처럼 소프트웨어 개발의 복잡성은 본질적인 특성에 기인한 것이지 비본질적인 특성에 기인한 것이 아니다. 그러므로 소프트웨어 문제의 본질을 다루는 도전, 즉 복잡한 개념적 구조를 체계화하는 것을 우선적으로 고려해야 한다.
따라서 도메인의 본질적인 특성을 강조하고 기술에 종속된 비본질적인 개념을 감추기 위해 참조 객체를 대체할 수 있는 용어가 필요하다. 우리는 참조 객체의 기술적인 측면을 캡슐화하고 도메인 측면만을 추상화하기 위해 엔티티(ENTITY)라는 용어를 사용한다. 일반적으로 참조 객체라는 용어가 도메인의 구현을 객체 지향 언어라는 틀로 한정 짓는데 비해 엔티티라는 용어는 광범위한 구현 기술을 수용할 수 있도록 한다.
엔티티의 개념은 참조 객체와 동일하다. 엔티티는 식별자(identity)를 가지고 추적가능하며 연속성을 가지는 도메인 개념이다. 엔티티는 속성이 아닌 식별자로 구분된다. 반면 값 객체는 식별자가 아닌 속성으로 구별되고 일반적으로 값 객체의 생명주기는 엔티티에 종속된다.
엔티티는 도메인 내에 존재하는 개념의 연속성을 강조한다. 따라서 엔티티라는 용어는 단순히 시스템 내에 존재하는 객체만을 지칭하지 않는다. 엔티티는 표현 매체의 특성이나 기술에 독립적이다.
고객의 신규 가입을 처리하기 위해 시스템은 고객 정보를 상태로 가지는 객체를 생성한다. 생성된 고객 객체는 비즈니스 로직을 처리하기 위해 사용된 후 영구 저장소에 보관된다. 대부분의 경우 영구 저장소로 관계형 데이터베이스를 사용할 것이며 고객 객체는 데이터베이스 테이블의 한 레코드로 저장될 것이다. 시스템은 다시 고객 정보가 필요한 경우 데이터베이스로부터 레코드를 읽어 고객 객체의 상태를 복원하고 비즈니스 로직을 처리한 후 변경된 상태를 다시 데이터베이스에 저장한다. 시스템은 주기적으로 고객 정보를 공유하는 제3의 시스템에 해당 고객 정보를 전송해야 할 수도 있다. 시스템은 고객 정보를 바이트 스트림으로 변경한 후 네트워크를 통해 다른 시스템에 전송한다. 고객 정보를 수신한 시스템은 다시 바이트 스트림을 고객 객체로 복원하고 비즈니스 로직을 처리한 후 적합한 데이터베이스에 저장한다.
위 예에서 신규 가입 고객의 정보는 객체, 데이터베이스 레코드, 네트워크 전송을 위한 바이트 스트림, 타 시스템 내의 객체, 타 시스템 내의 데이터베이스 레코드와 같이 다양한 형태로 변경된다. 그러나 이들 형태와 무관하게 이들은 한 가지 동일한 속성을 공유한다. 즉, 도메인 내에 존재하는 동일한 고객의 상태를 표현한다는 것이다. 이것이 엔티티의 본질이다.
엔티티는 도메인 객체의 표현 형태를 초월해서 동일한 도메인 개념의 추적성과 유일성을 강조한다. 엔티티라는 용어를 사용함으로써 메모리 상의 고객 객체와 데이터베이스에 저장된 고객 테이블의 레코드를 동일한 대상으로 바라볼 수 있다. 고객 객체는 언젠가는 가비지 켈렉터에 의해 소멸되겠지만 고객 그 자체는 데이터베이스를 통해 연속적인 추적성을 가진다. 엔티티라는 용어는 도메인 개념의 연속성과 생명 주기를 구현 기술에 독립적인 문맥 상에서 이해할 수 있도록 한다. 이것이 자칫 도메인 객체의 생명주기를 객체 자체의 생명주기와 혼동하도록 할 여지가 있는 참조 객체라는 용어를 사용하지 않음으로써 얻게 되는 궁극적인 효과라고 생각한다.
엔티티가 참조 객체를 표현 형태와 기술에 독립적인 개념으로 확장한다면 참조 객체의 생명 주기와 식별자의 의미는 어떻게 될 것인가? 이것 역시 미묘하지만 적지 않은 변화를 수반한다.
728x90'[옛날 글들] 도메인 주도 설계' 카테고리의 다른 글
도메인 주도 설계의 적용-4.ORM과 투명한 영속성 3부 (0) 2024.05.01 도메인 주도 설계의 적용-4.ORM과 투명한 영속성 2부 (5) 2024.04.24 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 5부 (1) 2024.04.20 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 4부 (0) 2024.04.18 도메인 주도 설계의 적용 - 3. 의존성 주입과 관점 지향 프로그래밍 3부 (0) 2024.04.17