Context Map¶
Umbra 는 9개의 Bounded Context 로 구성됩니다. 각 컨텍스트는 명확한 책임과 경계를 가지며, 서로 간 통신은 직접 호출 또는 Outbox 이벤트로 이루어집니다. 이 문서는 컨텍스트의 분류, 관계, 통신 방식을 정리합니다.
Why this matters¶
Bounded Context 는 DDD 의 핵심 개념이며 Umbra 에서는 Go 패키지 경계 와 PostgreSQL schema 경계 로 동시에 구현됩니다. 컨텍스트 경계가 명확하지 않으면 도메인 로직이 뒤섞여 결제 변경이 복구 기능에 영향을 주거나, 권한 체크 로직이 여러 곳에 중복되는 문제가 발생합니다.
Umbra 의 컨텍스트 구분은 세 단계로 분류됩니다.
- Core — 비즈니스 차별화가 일어나는 핵심 도메인. 풀세트 Hexagonal 로 구현.
- Supporting — Core 를 떠받치는 도메인. 단순 구조로 실용적 적용.
- Generic — 일반 솔루션으로 충분한 도메인. 단순 구조.
분류 기준은 "이 도메인이 Umbra 의 경쟁 우위에 직접 기여하는가" 입니다.
Context inventory¶
Umbra 의 전체 컨텍스트 목록입니다.
| # | Context | Type | Location | Description |
|---|---|---|---|---|
| 1 | Recovery | Core | engine/recovery/ | 스냅샷, 복구, Live Sync, AntiNuke |
| 2 | Licensing | Core | engine/licensing/ | 길드 권한, Plan, Feature 게이팅 |
| 3 | Billing | Core | engine/billing/ | Toss 결제, 빌링키, 구독 사이클 |
| 4 | Identity | Supporting | engine/identity/ | Discord User 매핑, OAuth2, 세션 |
| 5 | Guild | Supporting | engine/guild/ | 길드 등록, 설정, owner 관리 |
| 6 | Member | Supporting | engine/member/ | 멤버 정보, Web Join 흐름 |
| 7 | Notification | Supporting | engine/notification/ | Discord DM/채널 알림 |
| 8 | Audit | Generic | engine/audit/ | 도메인 이벤트 감사 로그 |
| 9 | Webhook | Generic | engine/webhook/ | 외부 웹훅 수신(Toss) |
Recovery 는 내부에 4개의 sub-context 를 가집니다.
engine/recovery/subdomain/snapshot/— 스냅샷 생성 및 저장engine/recovery/subdomain/restore/— 복구 실행 (Temporal Workflow)engine/recovery/subdomain/sync/— Live Sync 배치 처리engine/recovery/subdomain/antinuke/— 이상 감지 및 자동 대응 (Temporal Workflow)
Context map¶
컨텍스트 간 관계를 DDD 표기법으로 표현합니다. 화살표 방향은 의존 방향입니다.
graph TB
subgraph Core
Recovery[Recovery]
Licensing[Licensing]
Billing[Billing]
end
subgraph Supporting
Identity[Identity]
Guild[Guild]
Member[Member]
Notification[Notification]
end
subgraph Generic
Audit[Audit]
Webhook[Webhook]
end
Billing -->|Outbox event| Licensing
Guild -->|Outbox event| Licensing
Licensing -->|Outbox event| Notification
Billing -->|Outbox event| Notification
Recovery -->|Outbox event| Notification
Recovery -->|reads| Guild
Recovery -->|reads| Member
Recovery -->|permission check| Licensing
Member -->|reads| Guild
Member -->|reads| Identity
Billing -->|reads| Identity
Billing -->|reads| Guild
Webhook -->|forwards| Billing
Audit -.->|subscribes all events| Billing
Audit -.->|subscribes all events| Licensing
Audit -.->|subscribes all events| Recovery
실선은 동기 호출 또는 이벤트 발행, 점선은 이벤트 구독입니다.
Communication patterns¶
컨텍스트 간 통신은 세 가지 방식으로 이루어집니다.
1. Direct call (synchronous)¶
같은 프로세스 안에서 도메인 서비스가 다른 도메인 서비스를 호출합니다. 주로 읽기 와 권한 체크 에 사용됩니다.
Examples
- Recovery 가 복구 실행 전 Licensing 에 권한 조회
- Member 가 새 멤버 등록 시 Guild 에서 길드 정보 조회
- Billing 이 구독 생성 시 Identity 에서 사용자 확인
Why direct — 읽기는 데이터 일관성이 즉시 필요하고 트랜잭션 경계 안에서 처리되어야 합니다. Outbox 는 비동기라 읽기에 부적합합니다.
2. Outbox event (asynchronous)¶
도메인 상태 변경이 다른 도메인의 상태 변경을 촉발 할 때 사용됩니다. Publisher 는 트랜잭션 안에 이벤트를 outbox 테이블에 기록하고, Poller 가 Subscriber 에 전달합니다.
Examples
- Billing 에서 결제 성공 → Licensing 이 License 기간 연장
- Guild 에서 봇 강퇴 감지 → Licensing 이 License suspend, Billing 이 Subscription suspend
- Licensing 에서 권한 downgrade → Notification 이 사용자 알림
Why outbox — 쓰기 트랜잭션의 일관성을 보장하면서 도메인 간 결합도를 낮춥니다. Publisher 는 Subscriber 를 모릅니다.
3. External input (webhook or gateway)¶
외부 시스템(Toss, Discord)에서 들어오는 이벤트는 adapter 를 통해 도메인으로 라우팅됩니다.
Examples
- Toss 웹훅 → Webhook 컨텍스트 → Billing 호출
- Discord Gateway 이벤트 → Bot 프로세스의 handler → Recovery / Member 등 도메인 호출
Why adapter — 외부 시스템 프로토콜을 도메인 코어에서 분리합니다. Toss 가 Stripe 로 바뀌어도 Webhook adapter 만 변경됩니다.
Context relationships¶
DDD 의 Context Mapping 패턴으로 각 관계를 분류합니다.
Upstream-Downstream (U ← D)¶
Downstream 이 Upstream 의 변경에 영향을 받습니다.
| Upstream | Downstream | Pattern |
|---|---|---|
| Billing | Licensing | Published Events (Outbox) |
| Guild | Licensing | Published Events (Outbox) |
| Licensing | Notification | Published Events (Outbox) |
| Billing | Notification | Published Events (Outbox) |
| Recovery | Notification | Published Events (Outbox) |
| Identity | Member | Open Host Service |
| Identity | Billing | Open Host Service |
| Guild | Member | Open Host Service |
| Guild | Recovery | Open Host Service |
| Member | Recovery | Open Host Service |
Partnership¶
서로가 서로에게 영향을 주는 관계는 없습니다. Umbra 의 컨텍스트는 의도적으로 단방향 의존만 허용합니다. 양방향 의존이 필요해 보이는 순간은 도메인 경계 재검토 신호입니다.
Conformist¶
Umbra 는 외부 시스템의 모델을 그대로 받아들이는 Conformist 관계를 피합니다. Discord API 의 Guild 구조는 engine/guild/ 의 Guild entity 로 변환되며, Toss 의 payment 응답은 engine/billing/ 의 PaymentAttempt 로 매핑됩니다.
Anti-Corruption Layer¶
외부 시스템과의 경계는 adapter 레이어가 Anti-Corruption Layer 역할을 합니다.
engine/billing/adapter/toss/— Toss API 모델 → Billing domain modelengine/recovery/subdomain/*/adapter/discord/— Discord API 모델 → Recovery domain modelapps/bot/internal/— Discord Gateway 이벤트 → 도메인 호출apps/api/internal/handler/— HTTP 요청/응답 → 도메인 호출
이 레이어에서 외부 모델과 내부 모델의 매핑이 일어나며, 도메인 코어는 외부 변경에 보호됩니다.
Dependency rules¶
컨텍스트 간 의존성에는 엄격한 규칙이 적용됩니다. 이 규칙은 guides/hexagonal-pattern.md 에 상세히 정리되어 있으며, 아키텍처 테스트로 CI 에서 강제됩니다.
Rule 1. Core cannot depend on Supporting or Generic¶
Core 컨텍스트(Recovery, Licensing, Billing)는 Supporting 또는 Generic 컨텍스트에 직접 의존하지 않습니다. 필요한 데이터는 Port 인터페이스로 정의되며, Adapter 가 실제 Supporting/Generic 을 호출합니다.
Example — Recovery 가 Member 데이터를 필요로 하면 recovery/port/MemberReader 인터페이스를 정의하고, recovery/adapter/member/ 가 engine/member/ 를 호출하는 구현을 제공합니다.
Rule 2. Supporting can depend on other Supporting¶
Supporting 컨텍스트는 서로 자유롭게 의존할 수 있습니다. Identity, Guild, Member 는 단순 구조이므로 Hexagonal 전환의 비용 대비 이점이 적습니다.
Rule 3. Generic can be depended on by all¶
Audit 과 Webhook 은 다른 컨텍스트에서 자유롭게 참조 가능합니다. Audit 은 모든 이벤트를 구독하고, Webhook 은 외부 진입점 역할을 합니다.
Rule 4. No circular dependency¶
컨텍스트 간 순환 의존은 금지됩니다. 순환이 필요해 보이면 도메인 경계 재검토 신호입니다.
Rule 5. Events flow only one direction¶
Outbox 이벤트는 단방향입니다. Billing → Licensing 은 허용되지만 Licensing → Billing 은 다른 이벤트 타입으로 명시되어야 합니다.
Sub-contexts in Recovery¶
Recovery 는 내부에 4개의 sub-context 를 가지며 이들 간에도 경계가 있습니다.
graph LR
Sync[sync
Live Sync 배치]
Snapshot[snapshot
스냅샷 생성/조회]
Restore[restore
복구 실행]
AntiNuke[antinuke
이상 감지]
Sync -->|writes| DB[(길드 상태 DB)]
Snapshot -->|reads| DB
Snapshot -->|writes| SnapshotDB[(스냅샷 DB)]
Restore -->|reads| SnapshotDB
Restore -->|sends signal| Sync
AntiNuke -->|triggers| Snapshot
AntiNuke -->|sends signal| Restore
각 sub-context 의 책임:
- sync — Discord Gateway 이벤트를 배치로 DB 에 반영
- snapshot — 현재 DB 상태를 JSONB 로 스냅샷
- restore — 스냅샷을 기준으로 Discord 에 복구 실행 (Temporal Workflow)
- antinuke — Discord 이벤트 패턴 분석, 이상 감지 시 snapshot + restore 트리거 (Temporal Workflow)
sub-context 간 통신도 컨텍스트 간 규칙을 따릅니다. 직접 호출 또는 Temporal Signal 로 조정되며, Outbox 는 사용하지 않습니다(같은 도메인 내부 통신).
Permission model flow¶
Licensing 은 모든 기능 접근의 단일 진입점입니다. 다른 컨텍스트가 "이 사용자가 이 기능을 쓸 수 있는가" 를 질문할 때 Licensing 이 답합니다.
sequenceDiagram
participant User
participant Handler
participant Domain as Core Domain
participant Licensing
participant Repo as License Repo
User->>Handler: Request action
Handler->>Licensing: Can(guild_id, feature_id)
Licensing->>Repo: Get active license
Repo-->>Licensing: License + Plan
Licensing-->>Handler: Permitted / Denied
alt Permitted
Handler->>Domain: Execute use case
Domain-->>Handler: Result
else Denied
Handler-->>User: 403 Forbidden
end
이 패턴은 bot, api, worker 모든 진입점에서 동일하게 적용됩니다.
Integration strategy¶
새 컨텍스트를 추가하거나 기존 컨텍스트를 변경할 때 따르는 전략입니다.
Adding a new context¶
- 분류 결정 — Core / Supporting / Generic 중 어디에 속하는가
- 경계 정의 — 다른 컨텍스트와 어떤 관계를 맺는가
- 폴더 생성 —
engine/{name}/(Core 는 Hexagonal 풀세트, Supporting/Generic 은 단순 구조) - Schema 분리 —
db/schema/{name}.sql,db/queries/{name}/ - 이벤트 정의 — Publish/Subscribe 할 이벤트를
platform/event/카탈로그에 등록 - ADR 작성 — 결정 근거 기록
Modifying context boundaries¶
경계 변경은 아키텍처 변경입니다. ADR 로 기록하고 팀 리뷰를 거쳐야 합니다.
Splitting a context¶
컨텍스트가 커져서 분리가 필요한 신호:
- 한 컨텍스트에서 여러 팀이 작업하며 merge conflict 빈번
- 특정 기능 변경이 다른 기능에 영향을 줌 (응집도 저하)
- 컨텍스트 내부에 명확히 다른 비즈니스 의미가 공존
분리 시 Recovery 처럼 sub-context 로 시작하고, 더 큰 분리가 필요하면 top-level 컨텍스트로 승격합니다.
Constraints¶
- 도메인 간 직접 DB 접근 금지 — Licensing 이 Billing 테이블을 직접 SELECT 하는 것 금지
- 순환 이벤트 금지 — A → B → A 이벤트 체인 금지
- 외부 시스템은 항상 adapter 경유 — 도메인 코어에서 Discord API 직접 호출 금지
- sqlc 코드는 해당 도메인에서만 사용 —
db/queries/billing/에서 생성된 코드는engine/billing/adapter/persistence/안에서만 import
See also¶
architecture/overview.md— 시스템 아키텍처 전체architecture/event-flow.md— Outbox 이벤트 카탈로그와 흐름architecture/process-communication.md— 프로세스 간 통신 패턴guides/hexagonal-pattern.md— Hexagonal 적용 가이드domain/— 각 컨텍스트 상세 문서adr/0016-outbox-pattern.md— Outbox 패턴 선택adr/0019-hexagonal-pragmatic.md— 실용적 Hexagonal 적용