Business Domain — Product A Analysis
Applying DDD to the specific case study. Domain.md covers theory — this document is the PRACTICAL APPLICATION.
1. Domain Overview
Domain: Enterprise Travel, Event & Workforce Management Platform
Users: ~40,000 globally
Current: Legacy .NET monolith, shared SQL database
Target: .NET 8 microservices, per-service databases
2. Subdomain Classification
| Subdomain |
Type |
Business Value |
Build vs Buy |
Investment |
| Travel Booking |
Core |
★★★★★ |
Build (custom logic, competitive) |
Highest |
| Event Management |
Core |
★★★★☆ |
Build (custom workflows) |
High |
| Payment Processing |
Core |
★★★★★ |
Build + integrate gateway |
Frozen Phase 1 |
| Workforce Management |
Supporting |
★★★☆☆ |
Build (domain-specific) |
Medium |
| Allocation Algorithms |
Supporting |
★★★☆☆ |
Build (merge with WFM) |
Medium |
| Communications |
Generic |
★★☆☆☆ |
Build thin + use platform (SendGrid, Twilio) |
Low |
| Reporting |
Supporting |
★★★☆☆ |
Build (cross-domain aggregation) |
Medium |
Decision: Merge Allocation into Workforce (same domain: people + resource allocation). Add Reporting (not explicitly stated in the brief, but "Operational Reporting" exists in the monolith diagram).
3. Bounded Context Deep-Dive
3.1 Travel Booking Context
┌─────────────────────────────────────────────────────────────┐
│ TRAVEL BOOKING BOUNDED CONTEXT │
│ ════════════════════════════════ │
│ │
│ Ubiquitous Language: │
│ Booking, Itinerary, Traveler, Supplier, Segment, │
│ Quote, Confirmation, Cancellation, Amendment │
│ │
│ Aggregates: │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Booking (Root) │ │ Supplier (Root) │ │
│ │ ├─ BookingItems │ │ ├─ Contracts │ │
│ │ ├─ Travelers │ │ ├─ RateCards │ │
│ │ ├─ Segments │ │ └─ Availability │ │
│ │ └─ PaymentRef │ └──────────────────┘ │
│ └──────────────────┘ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Itinerary (Root) │ │ Quote (Root) │ │
│ │ ├─ Legs │ │ ├─ PriceBreakdown│ │
│ │ ├─ Accommodations│ │ └─ Expiry │ │
│ │ └─ Transfers │ └──────────────────┘ │
│ └──────────────────┘ │
│ │
│ Domain Events: │
│ BookingRequested → BookingConfirmed → BookingAmended │
│ BookingCancelled, QuoteExpired, SupplierBooked │
│ │
│ Commands: │
│ CreateBooking, ConfirmBooking, AmendBooking, │
│ CancelBooking, SearchAvailability, RequestQuote │
│ │
│ Queries: │
│ GetBookingById, SearchBookings, GetItinerary, │
│ GetBookingHistory, GetSupplierAvailability │
│ │
│ External Dependencies: │
│ → Payment (via ACL): charge, refund │
│ → Communications: send confirmation/cancellation email │
│ → Reporting: publish booking events for dashboards │
│ → Supplier APIs (GDS/external): search, book, cancel │
│ │
│ Complexity: HIGH │
│ Reason: "Expedia-like" = search, multi-leg, pricing rules, │
│ supplier integration, amendment workflows │
│ Team: D2 (Senior) lead + D1 (Tech Lead) support │
│ Phase: Phase 1 (M2-4), go-live M3 (canary) → M4 (100%) │
└─────────────────────────────────────────────────────────────┘
3.2 Event Management Context
┌─────────────────────────────────────────────────────────────┐
│ EVENT MANAGEMENT BOUNDED CONTEXT │
│ ════════════════════════════════ │
│ │
│ Ubiquitous Language: │
│ Event, Venue, Session, Attendee, Registration, │
│ Agenda, Speaker, Capacity, RSVP │
│ │
│ ⚠️ "Event" here = business event/conference, │
│ NOT a domain event (DDD). Easy to confuse! │
│ │
│ Aggregates: │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Event (Root) │ │ Registration │ │
│ │ ├─ Sessions │ │ (Root) │ │
│ │ ├─ Agenda │ │ ├─ Attendee │ │
│ │ ├─ Venue │ │ ├─ Tickets │ │
│ │ └─ Capacity │ │ └─ DietaryReqs │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ Domain Events: │
│ EventCreated, EventPublished, RegistrationOpened, │
│ AttendeeRegistered, EventCancelled, CapacityReached │
│ │
│ Commands: │
│ CreateEvent, PublishEvent, RegisterAttendee, │
│ CancelEvent, UpdateAgenda, AssignVenue │
│ │
│ Queries: │
│ GetEventById, ListUpcomingEvents, GetAttendeeList, │
│ GetEventCapacity, SearchEvents │
│ │
│ External Dependencies: │
│ → Payment (via ACL): registration fee │
│ → Communications: event reminders, updates │
│ → Workforce: staff assignment for events │
│ → Reporting: event metrics │
│ │
│ Complexity: HIGH (but less than Travel) │
│ Reason: Calendar logic, capacity management, attendee flows │
│ Team: D3 (Mid) lead + D2 (Senior) support │
│ Phase: Phase 1 (M3-4), go-live M4 │
└─────────────────────────────────────────────────────────────┘
3.3 Payment Processing Context (FROZEN)
┌─────────────────────────────────────────────────────────────┐
│ PAYMENT PROCESSING BOUNDED CONTEXT │
│ ══════════════════════════════════ │
│ ⚠️ STATUS: FROZEN Phase 1. Stays in monolith. │
│ │
│ Ubiquitous Language: │
│ Transaction, Payment, Refund, Invoice, Receipt, │
│ PaymentMethod, Reconciliation, Settlement │
│ │
│ Aggregates (in monolith, NOT migrating): │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Transaction(Root)│ │ Invoice (Root) │ │
│ │ ├─ LineItems │ │ ├─ LineItems │ │
│ │ ├─ PaymentMethod │ │ └─ TaxBreakdown │ │
│ │ └─ Status │ └──────────────────┘ │
│ └──────────────────┘ │
│ │
│ Integration via ACL: │
│ ┌──────────────────────────────────────────────┐ │
│ │ New Services → PaymentACL → Legacy Payment │ │
│ │ │ │
│ │ ACL exposes: │ │
│ │ ProcessPayment(bookingId, amount, method) │ │
│ │ RequestRefund(transactionId, amount) │ │
│ │ GetPaymentStatus(transactionId) │ │
│ │ │ │
│ │ ACL translates: │ │
│ │ New service format → legacy API format │ │
│ │ Legacy errors → standard error codes │ │
│ │ Adds: logging, retry, circuit breaker │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ Why frozen? │
│ 1. PCI DSS compliance risk │
│ 2. Financial transaction = zero error tolerance │
│ 3. Payment gateway integrations (complex) │
│ 4. Regulatory requirements │
│ 5. 5 engineers — insufficient bandwidth │
│ │
│ Future (post-Phase 1, possibly Phase 3 or post-9-months): │
│ Extract → Payment Service (.NET 8) │
│ Own database, Stripe/gateway integration │
│ PCI compliance validation │
│ │
│ Team: N/A (ACL built by D1 Tech Lead + D2 Senior, ~2 weeks) │
│ Phase: ACL Phase 1 (M2). Migration: deferred beyond M9 │
└─────────────────────────────────────────────────────────────┘
3.4 Workforce Management Context (includes Allocation)
┌─────────────────────────────────────────────────────────────┐
│ WORKFORCE MANAGEMENT BOUNDED CONTEXT │
│ ════════════════════════════════════ │
│ (Merged: Workforce Management + Allocation Algorithms) │
│ │
│ Ubiquitous Language: │
│ Staff, Shift, Schedule, Skill, Availability, │
│ Allocation, Assignment, Capacity, Roster │
│ │
│ Why merge Allocation? │
│ • Allocation = staff/resource assignment → same WFM domain │
│ • Same data: staff skills, availability, constraints │
│ • Splitting = 2 services too small for the same domain │
│ • 1 service with 2 internal modules is sufficient │
│ │
│ Aggregates: │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Staff (Root) │ │ Schedule (Root) │ │
│ │ ├─ Skills │ │ ├─ Shifts │ │
│ │ ├─ Availability │ │ ├─ Assignments │ │
│ │ └─ Preferences │ │ └─ Constraints │ │
│ └──────────────────┘ └──────────────────┘ │
│ ┌──────────────────┐ │
│ │ Allocation (Root)│ ← Allocation algorithm │
│ │ ├─ Request │ │
│ │ ├─ Candidates │ │
│ │ └─ Result │ │
│ └──────────────────┘ │
│ │
│ Domain Events: │
│ StaffCreated, ShiftAssigned, AllocationCompleted, │
│ SchedulePublished, StaffUnavailable, SkillUpdated │
│ │
│ Commands: │
│ CreateStaff, AssignShift, RunAllocation, │
│ PublishSchedule, UpdateAvailability │
│ │
│ Queries: │
│ GetStaffById, GetScheduleByWeek, GetAvailableStaff, │
│ GetAllocationResult, SearchStaffBySkill │
│ │
│ External Dependencies: │
│ → Event Management: staff allocated for events │
│ → Travel Booking: staff assigned for travel support │
│ → Communications: notify shift changes │
│ → Reporting: workforce utilization metrics │
│ │
│ Complexity: MEDIUM │
│ Reason: Scheduling logic, constraint-based allocation │
│ Note: Allocation algorithms = CPU-intensive, │
│ may require async processing (queue + worker) │
│ Team: D4 (Mid) lead │
│ Phase: Phase 2 (M5-6), go-live M6 │
└─────────────────────────────────────────────────────────────┘
3.5 Communications Context
┌─────────────────────────────────────────────────────────────┐
│ COMMUNICATIONS BOUNDED CONTEXT │
│ ══════════════════════════════ │
│ │
│ Ubiquitous Language: │
│ Notification, Template, Channel, Recipient, │
│ DeliveryStatus, Preference, Queue │
│ │
│ Aggregates: │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Notification │ │ Template (Root) │ │
│ │ (Root) │ │ ├─ Placeholders │ │
│ │ ├─ Recipients │ │ ├─ Channel │ │
│ │ ├─ Channel │ │ └─ Version │ │
│ │ └─ DeliveryLog │ └──────────────────┘ │
│ └──────────────────┘ │
│ ┌──────────────────┐ │
│ │ Preference(Root) │ ← User notification settings │
│ │ ├─ Channels │ │
│ │ └─ Frequency │ │
│ └──────────────────┘ │
│ │
│ Domain Events: │
│ NotificationQueued, NotificationSent, │
│ NotificationFailed, TemplateUpdated │
│ │
│ Commands: │
│ SendNotification, CreateTemplate, UpdatePreference, │
│ RetryFailedNotification │
│ │
│ Queries: │
│ GetDeliveryStatus, GetNotificationHistory, │
│ GetTemplates, GetUserPreferences │
│ │
│ Channels: │
│ Email (SendGrid/SES), SMS (Twilio), Push, In-App │
│ │
│ External Dependencies: │
│ ← ALL other services: everyone sends notifications │
│ → External providers: SendGrid, Twilio, Firebase │
│ │
│ Complexity: LOW │
│ Reason: Well-defined, stateless processing, no complex logic │
│ Why pilot? Simplest domain, cross-cutting, thin integration │
│ Team: D4 (Mid) primary + D3 secondary │
│ Phase: Phase 0 (M1) pilot (D3+D4) → Phase 2 (M7) complete │
└─────────────────────────────────────────────────────────────┘
3.6 Reporting Context
┌─────────────────────────────────────────────────────────────┐
│ REPORTING BOUNDED CONTEXT │
│ ═════════════════════════ │
│ (Not explicitly stated in the brief, but the monolith has │
│ "Operational Reporting" → needs extraction) │
│ │
│ Ubiquitous Language: │
│ Report, Dashboard, Metric, KPI, DataSource, │
│ Aggregation, Schedule, Export │
│ │
│ Aggregates: │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Report (Root) │ │ Dashboard (Root) │ │
│ │ ├─ DataSources │ │ ├─ Widgets │ │
│ │ ├─ Filters │ │ ├─ Layout │ │
│ │ └─ Schedule │ │ └─ Permissions │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ Notable — READ-ONLY service: │
│ • No complex business commands │
│ • Subscribes to events from ALL services │
│ • Materializes into read-optimized tables │
│ • CQRS pattern: this IS the read side │
│ │
│ Data Sources (via events): │
│ ← Travel: BookingCreated, BookingCancelled → booking stats │
│ ← Event: AttendeeRegistered, EventCreated → event metrics │
│ ← Workforce: ShiftAssigned, AllocationDone → utilization │
│ ← Payment (via ACL): TransactionCompleted → revenue data │
│ ← Communications: NotificationSent → delivery metrics │
│ │
│ Queries: │
│ GetDashboard, RunReport, ExportCSV, GetKPIs, │
│ GetTrendData, GetBookingStats, GetRevenueReport │
│ │
│ Complexity: MEDIUM │
│ Reason: Many data sources, aggregation logic, but no write │
│ business logic. Main challenge = data consistency │
│ Team: D5 (Mid) lead │
│ Phase: Phase 2 (M6-7), go-live M7 │
└─────────────────────────────────────────────────────────────┘
4. Context Map — Inter-Service Relationships
┌──────────────────┐
│ API Gateway │
│ (YARP) │
└────────┬─────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ TRAVEL │ │ EVENT │ │ WORKFORCE │
│ BOOKING │ │ MANAGEMENT │ │ + ALLOCATION│
│ │ │ │ │ │
│ [Core] │ │ [Core] │ │ [Supporting]│
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ Events (Azure Service Bus) │
├──────────────────┼──────────────────┤
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ╔══════════════╗
│ COMMS │ │ REPORTING │ ║ PAYMENT ║
│ │ │ (read-only) │ ║ (MONOLITH) ║
│ [Generic] │ │ [Supporting]│ ║ [Frozen] ║
└──────────────┘ └──────────────┘ ╚══════╤═══════╝
│
┌──────┴───────┐
│ ACL │
│ (Adapter) │
└──────────────┘
Relationship Types:
─────────────────────────────────────────────────────
Travel ──[Customer-Supplier]──→ Payment (via ACL)
Travel requests payment processing, Payment provides service
Event ──[Customer-Supplier]──→ Payment (via ACL)
Event registration fees processed by Payment
Travel ──[Publisher]──→ Comms (via events)
BookingConfirmed → send confirmation email
Event ──[Publisher]──→ Comms (via events)
AttendeeRegistered → send welcome email
Event ──[Partner]──→ Workforce
EventCreated → trigger staff allocation request
Travel ──[Publisher]──→ Reporting (via events)
Event ──[Publisher]──→ Reporting (via events)
Workforce ──[Publisher]──→ Reporting (via events)
All services → publish events → Reporting materializes
All New Services ──[ACL]──→ Legacy Monolith
Anti-Corruption Layer protects new services from legacy internals
4.1 Event Flow Matrix
| Publisher |
Event |
Subscribers |
Action |
| Travel |
BookingCreated |
Comms, Reporting |
Send email, update dashboard |
| Travel |
BookingConfirmed |
Payment (ACL), Comms, Reporting |
Charge card, send confirmation, record |
| Travel |
BookingCancelled |
Payment (ACL), Comms, Reporting |
Refund, send cancellation, record |
| Event Mgmt |
EventCreated |
Reporting |
Record new event |
| Event Mgmt |
AttendeeRegistered |
Payment (ACL), Comms, Reporting |
Charge fee, send welcome, record |
| Event Mgmt |
CapacityReached |
Comms |
Notify waitlist |
| Workforce |
ShiftAssigned |
Comms, Reporting |
Notify staff, record |
| Workforce |
AllocationCompleted |
Event Mgmt, Reporting |
Confirm staff for event |
| Payment (legacy) |
PaymentProcessed |
Travel/Event (via ACL), Reporting |
Confirm booking, record revenue |
5. Shared Kernel — Shared Concepts (Minimal)
⚠️ Shared Kernel must be EXTREMELY SMALL. Only share what is MANDATORY.
Shared package: PhoenixDX.SharedKernel (NuGet internal)
Contains:
• UserId (value object) — identifier across services
• Money (value object) — Amount + Currency
• DateRange (value object) — Start + End date
• Address (value object) — Street, City, Country
• IntegrationEvent (base class) — EventId, Timestamp, CorrelationId
• Result<T> (generic) — Success/Failure pattern
Must NOT contain:
✗ Business entities (Booking, Event, Staff)
✗ Business logic
✗ Database entities/migrations
✗ Service-specific DTOs
Rule: Any SharedKernel change → requires updating ALL services
→ Keep it as small as possible
6. Migration Order — Domain-Driven Justification
| Order |
Service |
Justification |
| 1 |
Communications |
Generic subdomain, simplest, AI pilot. Establishes pattern template for the team |
| 2 |
Travel Booking |
Core domain, highest value, hardest. Start early = more time for complexity |
| 3 |
Event Management |
Core domain, runs in parallel with Travel tail. Reuses patterns from Travel |
| 4 |
Workforce + Allocation |
Supporting, depends on Event (staff for events). Extract after Event is stable |
| 5 |
Reporting |
Read-only, depends on events from ALL services. Extract last = more event sources are available |
| 6 |
Payment |
Frozen. ACL only. Migration deferred beyond 9 months |
Why NOT start with the core domain (Travel) immediately?
Phase 0 pilot = Communications because:
1. Simple → team learns patterns without being overwhelmed
2. Cross-cutting → every service will call Comms → build API contracts early
3. Low risk → if email is delayed 5 min = acceptable, booking delay = NOT OK
4. AI pilot → test agentic migration workflow on a simple domain first
5. Pattern template → Travel team copies proven patterns from Comms pilot
After pilot → Travel begins Phase 1 with confidence + proven patterns
7. Database Decomposition Strategy
LEGACY (current state):
┌──────────────────────────────────────────────────┐
│ Shared SQL Server │
│ │
│ dbo.Bookings dbo.Events │
│ dbo.Travelers dbo.Attendees │
│ dbo.Payments dbo.Invoices │
│ dbo.Staff dbo.Shifts │
│ dbo.Notifications dbo.Templates │
│ dbo.Reports dbo.Users │
│ │
│ ← Shared tables: dbo.Users, dbo.AuditLog │
│ ← Cross-domain FKs: Bookings.PaymentId → Payments│
│ ← Cross-domain views: vw_BookingRevenue (JOIN 5) │
└──────────────────────────────────────────────────┘
MIGRATION STEPS:
─────────────────
Step 1: Identify table ownership
Travel owns: Bookings, Travelers, Itineraries, Suppliers
Event owns: Events, Attendees, Sessions, Venues
Workforce owns: Staff, Shifts, Skills, Allocations
Comms owns: Notifications, Templates, Preferences
Reporting owns: Reports, Dashboards (+ materialized views)
Payment owns: Payments, Invoices, Transactions (STAYS in monolith)
Shared: Users → extract to identity service or keep in gateway
Step 2: Break cross-domain FKs
Bookings.PaymentId → replace with PaymentReference (string, not FK)
Events.VenueId if Venue is shared → duplicate into Event DB
Step 3: CDC for transition
New service DB ← CDC sync ← Legacy DB (during dual-run period)
When service goes live → stop CDC → service owns data solely
Step 4: Handle cross-domain queries
vw_BookingRevenue → Reporting service materialized view
Built from events: BookingConfirmed + PaymentProcessed
8. Summary — Service Registry
| Service |
BC |
Type |
DB |
Phase |
Go-Live |
Owner |
Complexity |
| Travel Booking |
Travel |
Core |
Azure SQL |
Phase 1 |
M3-M4 |
D2 (+D1) |
High |
| Event Management |
Event |
Core |
Azure SQL |
Phase 1 |
M4 |
D3 (+D2) |
High |
| Workforce + Allocation |
WFM |
Supporting |
Azure SQL |
Phase 2 |
M6 |
D4 |
Medium |
| Communications |
Comms |
Generic |
Azure SQL |
Phase 0→2 |
M1(pilot)→M7 |
D4 (+D3) |
Low |
| Reporting |
Reporting |
Supporting |
Azure SQL (read) |
Phase 2 |
M7 |
D5 (+D3) |
Medium |
| Payment (ACL only) |
Payment |
Core (frozen) |
Legacy DB |
Phase 1 |
M2 (ACL) |
D1 (+D2) |
ACL only |
| API Gateway |
Infra |
Platform |
N/A |
Phase 0 |
M1 |
D1 |
Medium |
| Shared Libraries |
Infra |
Platform |
N/A |
Phase 0 |
M1 |
D1+D5 |
Low |