DDD · Architecture

Evans가 쓴 적 없는
규칙

요즘 거의 모든 DDD 코드베이스가 같은 규칙을 지킨다: 다른 Aggregate는 오직 ID로만 참조하고, 객체 참조를 직접 갖고 있으면 안 된다. Aggregate라는 용어를 만든 2003년 원저는 정반대로 말한다. 그리고 실제로 "ID로만 참조"라는 규칙을 쓴 사람은 같은 논문 안에서 그 사실을 숨기지 않고 스스로 밝힌다.

이 규칙은 오늘날 DDD 코드베이스에서 거의 보편적인 관습으로 등장한다. 보통 단서 없이 단정적으로: 다른 Aggregate는 ID로 참조하고, 객체 참조는 설계 실수로 취급한다. 원전에서 곧바로 나온 것처럼 들린다. 아니다.

실제로 지켜지는 규칙

한 코드베이스의 버전: "다른 Aggregate는 반드시 ID로만 참조한다(객체 참조는 절대 금지)" — 그리고 자기 자신의 요약 표에서 다시: "객체 참조는 결합도를 만든다 — ID만 유지할 것." 최근 아무 DDD 튜토리얼이나 발표, 레퍼런스 아키텍처를 검색해봐도 거의 곧바로 같은 규칙의 어떤 버전이 등장하고, 대개 확정된 근본 원칙처럼 제시된다.

2003년 원저 자신의 규칙 목록이 말하는 것

Eric Evans의 Domain-Driven Design은 Aggregate 개념을 구현 제약으로 번역하면서, 그 규칙들을 번호 매긴 목록으로 제시한다. 일곱 항목 중 하나는 이렇게, 전체가, 아무 단서 없이 적혀 있다:

"Objects within the AGGREGATE can hold references to other AGGREGATE roots." (Aggregate 안의 객체는 다른 Aggregate root에 대한 참조를 가질 수 있다) (p. 92)

얼버무림도, 곁다리 언급도 아니다 — "Aggregate root만 데이터베이스 쿼리로 직접 얻을 수 있다"와 "delete 연산은 Aggregate 경계 안의 모든 것을 한 번에 제거해야 한다" 사이에 나란히 놓인, 목록에 오른 규칙이다. Aggregate가 무엇인지 정의한 바로 그 책이, 오늘날의 관습이 금지하는 바로 그것을 명시적으로 허용하고 있다.

확대 해석이 아니다

모호한 문장 하나를 억지로 늘려서 만든 주장이 아니다. 이 책은 구현자가 실제로 무엇이 요구되는지 알 수 있도록 정확히 그 목적으로 Aggregate 규칙을 열거된 목록으로 제시한다. Aggregate root 간 직접 객체 참조는 그 목록에, 허용되는 것으로 올라 있다.

"ID로만"은 실제로 어디서 왔는가

오늘날 모두가 따르는 규칙은 Vaughn Vernon의 2011년 논문 Effective Aggregate Design, Part II로 거슬러 올라간다 — 그리고 Vernon 스스로도 이걸 Evans의 규칙이라고 주장하지 않는다. 그는 해당 절을 Evans를 직접, 정확하게 인용하며 연다: "[DDD] states that one aggregate may hold references to the root of other aggregates."(DDD는 하나의 aggregate가 다른 aggregate의 root에 대한 참조를 가질 수 있다고 말한다) 그런 다음 그 위에 자기 자신의, 별개인 규칙을 얹는다:

"Rule: Reference Other Aggregates By Identity — Prefer references to external aggregates only by their globally unique identity, not by holding a direct object reference." (규칙: 다른 Aggregate는 식별자로 참조하라 — 외부 aggregate에 대한 참조는 직접적인 객체 참조가 아니라 전역 고유 식별자로만 하는 걸 선호하라)

Vernon은 2003년 원저에 대한 오독을 바로잡고 있는 게 아니다. 그는 그 텍스트를 정확히 읽고, 그것이 객체 참조를 허용한다는 데 동의한 뒤, 그럼에도 그 허용을 실제로 행사해서는 안 된다고 주장한다 — 원저가 고려할 계기가 없었던 이유들 때문에.

왜 더 엄격한 규칙이 이겼는가

같은 논문에 나오는 Vernon 자신의 근거는 직접 참조가 부과하는, ID는 부과하지 않는 구체적인 비용 두 가지로 요약된다:

  • 트랜잭션 경계의 안전성. "참조하는 aggregate와 참조되는 aggregate 둘 다 같은 트랜잭션 안에서 수정되어서는 안 된다. 둘 중 하나만 하나의 트랜잭션 안에서 수정될 수 있다." 살아있는 객체 참조는 이 실수를 우연히 저지르기 쉽게 만든다 — 다른 Aggregate가 메서드 호출 한 번 거리에 바로 있으니까. ID는 그것에 닿기 위해 명시적인 Repository 조회를 강제하고, 바로 그 순간이 개발자가 자신이 두 번째 Aggregate를 건드리려 한다는 걸 알아채고 재고하게 되는 지점이다.
  • 무엇이 로드되는지의 비용. "추론된 객체 참조를 가진 aggregate는... 참조가 결코 즉시 로드되지 않기 때문에 자동으로 더 작아진다. 인스턴스를 로드하는 데 시간이 덜 걸리고 메모리를 덜 쓰기 때문에 모델이 더 나은 성능을 낼 수 있다." 객체 참조는 필드 타입 하나를 만족시키려고 영속성 프레임워크가 두 번째 Aggregate 그래프 전체를 즉시 또는 지연 로딩하도록 유혹한다. ID는 그냥 문자열이다.

두 규칙, 어느 쪽도 반박하지 않는 하나의 원칙

두 규칙이 각자 보호하고 있는 더 깊은 제약은 애초에 논쟁거리가 아니었다 — 그건 Evans 자신의 것이고, 논쟁이 된 그 규칙 바로 몇 줄 위에 적혀 있다: "Aggregate 안에 적용되는 invariant는 각 트랜잭션의 완료와 함께 강제된다." 이는 트랜잭션의 역할이 정확히 하나의 Aggregate를 일관되게 유지하는 것이라는 걸 암시한다 — 명시적으로 법제화하지는 않지만. Vernon의 식별자 전용 규칙은 그 원칙을 수정하지 않는다. 그건 특정한 허점 하나 — 직접 참조 — 를 닫는 것이고, 그 허점이 알아채지 못한 채 그 원칙을 어기는 걸 너무 쉽게 만들었을 뿐이다. 겉보기에 자신이 따른다고 주장하는 책과 어긋나는 코드베이스처럼 보이는 것이, 실은 같은 논증의 역사에서 서로 다른 두 시점의 책을 각각 따르고 있는 것이다: 제약 자체는 Evans의 것이고, 그 제약이 우연히 깨지기 어렵게 만드는 메커니즘은 Vernon의 것이며, Evans가 열어둔 허용 위에 의식적으로 지어졌다.

더 읽을거리

Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley, 2003), 6장 · Vaughn Vernon, "Effective Aggregate Design, Part II: Making Aggregates Work Together" (2011) · backend-service-playbook/docs/architecture/tactical-ddd.md