Busflow Docs

Internal documentation portal

Skip to content
Reviewed 28 May 2026

Unified Domain Model

This document provides the bounded context map for the BusFlow platform — the architectural overview showing how contexts relate, not what they contain. For entity dictionaries within each context, follow the spoke links at the bottom of this page.

The platform uses a Modular Monolith architecture, grouping highly cohesive Bounded Contexts into five database schemas: Backoffice, Commerce, Operations, Communications (a Shared Core Domain), and Customer Intelligence [future — Phase 3].

Core Architectural Principles:

  1. Event-Driven Decoupling: To prevent database deadlocks and tight coupling, contexts communicate primarily via asynchronous Domain Events (e.g., BookingConfirmed, IssueReported, DutyViolation).
  2. Soft Cross-Schema References: The system strictly enforces hard foreign key constraints within a schema, but cross-schema relationships use soft ID referencing (e.g., the Commerce schema stores service_leg_id as an integer without a hard database-level constraint to the Operations schema).

For the DDD patterns governing context interaction (CQRS, read-only views, saga orchestration), see domain-driven-design.md.


Context Map Diagram

100%

For the full event registry with payloads, emitters, consumers, and trigger details, see event-catalog.md.


Context Map

The following map applies the DDD patterns defined in domain-driven-design.md §7 to the concrete bounded contexts. Each row specifies the upstream (data owner) and downstream (data consumer) context, the relationship type, and the primary synchronization mechanism.

UpstreamDownstreamRelationshipSync MechanismNotes
BackofficeCommerceCustomer–SupplierDomain events (PriceMatrixPublished, SeasonPricingFinalized, TourDeparturePublished) → Commerce projectionsBackoffice owns master data (TourTemplate, TourDeparture, PricingRule). Commerce holds local read models shaped for checkout.
BackofficeOperationsCustomer–SupplierDomain events → Operations projectionsOperations derives ServiceLeg from Backoffice TourDeparture data (via TourDeparturePublished). Crew/fleet status changes trigger dispatch recalculation.
CommerceOperationsConformistRead-only SQL viewsOperations consumes passenger/booking data as-is for boarding.
CommerceBackofficeEvent NotificationDomain events → Backoffice dashboardsBackoffice uses Commerce actuals for Soll/Ist reconciliation.
OperationsBackofficeEvent NotificationDomain events → Backoffice inspection schedulingField-reported maintenance escalation.
Communications(all contexts)Shared KernelGeneric SendMessageCommand APIProvider-agnostic. Domain contexts fire triggers; Communications owns delivery.
BackofficeCommunicationsCustomer–SupplierTemplates + Scheduled triggersCommunications resolves and dispatches notification templates.
(all contexts)Customer IntelligenceEvent Notification[future — Phase 3] Domain events → CI activity logCI consumes all customer-relevant events for behavioral aggregation and recommendations. See ADR-021.
Customer IntelligenceCommerce, Communications, BackofficeEvent Notification[future — Phase 3] Enrichment events → context projectionsCI produces personalized offers, channel preferences, and profile enrichment signals.

NOTE

No Anti-Corruption Layer (ACL) is required between Busflow's internal bounded contexts. They share a single TypeScript codebase (modular monolith) and a unified ubiquitous language. External legacy integrations are different: Kuschick/Satzart-style booking and ERP integrations require an ACL via dedicated connector/sidecar services so proprietary legacy data constructs do not leak into the Busflow domain model.

See adr-001-boarding-point-strategy.md for a concrete application of all three patterns.

Pricing: Authoring vs. Resolution

Busflow splits "pricing" across two contexts by design; no single context owns it. Teams usually trip over the two senses of the word.

SenseContextWhat it owns
Rate-card authoringBackofficeOperator-authored selling strategy: base price derived from CostingSheet, margin_config, and the demographic/season/early-bird structure. Cost-derived, planning-side, immutable-append versioning. Lives on PriceMatrix.
Price resolution at saleCommerceWhat a specific buyer, in a specific channel, at a specific time actually pays: the TourOfferingPrice read-model, the price_matrix_id frozen on the ticket at checkout, and realized revenue in FinancialLedger. Demand-side, transactional, accumulating.

So PriceMatrix stays in Backoffice, not Commerce. It is master-data authoring, couples tightly to CostingSheet (e.g. the recalculation→staleness cascade, ADR-006 §6), and serves as the Soll baseline for Soll/Ist reconciliation. Commerce does not own pricing; it consumes a validated read model (Customer–Supplier). The pricing input VOs (PricingRule, PricingConfig) already live on Backoffice TourTemplate, so keeping the output (PriceMatrix) there preserves cohesion.

NOTE

Open seam — [planned — Phase 2]. The static split above holds; demand-driven pricing stays open. Algorithmic yield (Level 3) and channel/commission economics (Level 2) follow signals that live in Commerce/Operations (occupancy, booking velocity, channel), so they pull genuinely toward Commerce. Pick one of two clean resolutions when you build Phase 2 yield:

  1. Single-authority — price authority stays in Backoffice; Commerce emits demand-signal events; a Backoffice yield service recomputes and republishes PriceMatrix versions.
  2. Resolution-layer — the base rate-card stays in Backoffice; a thin channel/yield modifier in Commerce layers on at resolution time (matches the "Commission on Retail" option).

See PD-1 (commission ↔ margin) and the Phase 2 Level 2/3 placeholder in ADR-006.


Cross-Boundary Soft FK Reference Map

The following table catalogs all soft foreign key references that cross schema boundaries. These are stored as plain ID columns (UUID or integer) without database-level FK constraints, as mandated by the database boundary rules.

Source ContextSource EntityFK ColumnTarget ContextTarget EntityNotes
CommerceTourOfferingtour_departure_idBackofficeTourDepartureSynced via TourDeparturePublished event
CommerceTourOfferingtour_template_idBackofficeTourTemplateDenormalized for display
CommerceTourOfferingPriceprice_matrix_version_idBackofficePriceMatrixSynced via PriceMatrixPublished event
CommercePassengerperson_profile_idBackofficePersonProfileCRM person link
CommercePassengerboarding_point_idBackofficeBoardingPointPickup location
CommerceBookingbooker_profile_idBackofficePersonProfileBilling authority (person who pays) [planned — Phase 1]. See ADR-037.
CommerceBookingreseller_idBackofficeResellerB2B channel
CommerceAncillarycatalog_item_idBackofficeAncillaryCatalogItemNullable; null for system-generated items
CommerceSeatReservationservice_leg_idOperationsServiceLegCapacity hold per leg
CommerceFinancialLedgercosting_sheet_idBackofficeCostingSheetSoll baseline for Nachkalkulation
OperationsServiceLegtour_departure_idBackofficeTourDepartureDeparture origin
OperationsLegAssignmentvehicle_idBackofficeVehicleFleet dispatching
OperationsLegAssignmentcrew_member_idBackofficeCrewMemberCrew dispatching
OperationsOnboardSaleticket_idCommerceTicketSeat billing
OperationsBoardingEventticket_idCommerceTicketQR validation
CommunicationsConversationbooking_idCommerceBookingExclusive nullable FK
CommunicationsConversationservice_leg_idOperationsServiceLegExclusive nullable FK
CommunicationsConversationinvoice_idCommerceInvoiceExclusive nullable FK
CommunicationsContactperson_profile_idBackofficePersonProfileCRM person link
CommunicationsMessagetemplate_idBackofficeNotificationTemplateAutomated messages

Multi-Tenancy Strategy

Every aggregate root carries a tenant_id (UUID) referencing the owning Operator. Hasura permission rules enforce row-level isolation by extracting x-hasura-tenant-id from the JWT and matching it against tenant_id. The system assigns users to specific tenants via a user_tenant_assignments table, preventing unauthorized cross-tenant data access. See domain-driven-design.md for enforcement rules.


Aggregate Model

Each bounded context defines aggregates — clusters of entities that the system modifies together in a single transaction. The aggregate root controls access to all children and enforces invariants.

Design heuristics:

  1. Transactional boundary — entities that must change together atomically belong in one aggregate.
  2. Lifecycle coupling — entities that share creation, state transitions, and deletion.
  3. Invariant protection — business rules that span multiple entities.
  4. Independent identity — entities with meaning outside their parent become separate aggregates.
  5. Collection size — unbounded child collections signal a separate aggregate.

FK policy: Hard FKs within aggregate boundaries; soft ID references for cross-aggregate lifecycle conflicts; hard FKs for stable context references. See ADR-036.

ContextAggregatesRoots
Backoffice15Operator, TourTemplate, TourDeparture, CostingSheet, PriceMatrix, CrewMember, Vehicle, Supplier, Allotment, BoardingPointLibrary, AncillaryCatalogItem, Reseller, PersonProfile, NotificationTemplate, CharterQuote
Commerce7TourOffering, Booking, CheckoutSession, SeatReservation, FinancialLedger, Invoice, LedgerPeriodLock
Operations6ServiceLeg, Incident, IssueReport, ExpenseReceipt, OnboardSale, BoardingEvent
Communications4Conversation, Message, ChannelAccount, Contact
Customer Intelligence3CustomerProfile, CustomerSegment, RecommendationModel

Each spoke file below contains a full ## Aggregates section with children, invariants, and domain events.


Entity Dictionaries (Spokes)

ContextFileSummary
Backofficedomain-backoffice.mdMaster data, planning, CRM, Kalkulations-Engine (15 aggregates)
Commerce & Financedomain-commerce.mdSales, payments, capacity, ledger, tax (7 aggregates)
Operationsdomain-operations.mdFleet execution, dispatching, IoT, offline sync (6 aggregates)
Communicationsdomain-communications.mdOmnichannel inbox, contacts, automation (4 aggregates)
Customer Intelligencedomain-customer-intelligence.md[TBD] — Phase 3 (3 conceptual aggregates)

Internal documentation — Busflow