Developer Guide
CrowdVision is modelled with a Domain-Driven Design (DDD) mindset. We approached the system not as one monolithic model but as a set of collaborating models, each owned by a context and each speaking its own dialect of a shared Ubiquitous Language. This page captures the strategic design decisions taken up front — the subdomains, the bounded contexts, and the relationships between them. The tactical model (aggregates, entities, value objects, domain services) is described per context on the following pages. The user stories that seeded these decisions — and the places where backlog and design deliberately part ways — are catalogued in User Stories.
We treat DDD as a design compass, not a rulebook. The strategic boundaries below are the part of the design we were most confident in. The tactical model is applied where it earns its keep, and some boundaries were left intentionally soft because the product ideas were still maturing when this design was written. The per-context pages flag where a decision is provisional or expected to evolve.
The overall domain is real-time monitoring of physical spaces through a digital twin. We classify the subdomains by how much competitive value they carry, which in turn drives how much modelling effort each is given.
| Subdomain | Type | Rationale |
|---|---|---|
| Spatial Digital Twin | Core | The 3D building model is the product’s differentiator and receives the richest model. |
| Telemetry Ingestion & Thresholds | Core | Turning raw sensor signals into meaningful, bounded readings is central business logic. |
| Telemetry Distribution | Core | Per-building stream shaping is what makes the live view feel tailored. |
| Identity & Access | Supporting | Multi-tenant access control is essential but not a differentiator. |
| Alerting | Supporting | Push alerting supports the core experience without defining it. |
| Knowledge & Assistance | Supporting | The assistant augments the product; it is explicitly experimental. |
| Real-Time Transport | Generic | A commodity relay with no domain model of its own. |
A guiding decision is that each core and supporting subdomain becomes exactly one bounded context, and each context is meant to stand on its own — its own model, its own storage, its own boundary. We chose this alignment so that a model can never quietly leak into another context through shared data: a context boundary is also intended to be a deployment and storage boundary.
graph TB
subgraph IA["Identity and Access Context"]
ACC["Account / Domain"]
end
subgraph BM["Building Management Context"]
BLD["Building / Room"]
end
subgraph TI["Telemetry Ingestion Context"]
SIG["Signals / Thresholds"]
end
subgraph TD["Telemetry Distribution Context"]
PRF["Building Preferences"]
end
subgraph AL["Alerting Context"]
SUB["Delivery / Preferences"]
end
subgraph KA["Knowledge and Assistance Context"]
DOC["Inquiries / Knowledge Sources"]
end
IA -.->|"identity claims"| BM
IA -.->|"identity claims"| AL
IA -.->|"identity claims"| KA
BM -->|"structural metrics"| TD
BM -->|"threshold baseline"| TI
BM -->|"domain resolution"| AL
TI -->|"readings"| TD
TI -->|"metric catalog"| TD
BM -->|"live building data"| KA
TI -->|"live signals and thresholds"| KAThe Real-Time Transport context is omitted from the map: it is meant to carry messages between contexts but to hold no model and enforce no rules.
We describe the relationships between contexts with the standard DDD integration patterns. They tell a contributor who depends on whom and which side dictates the contract. The interactions are stated conceptually; concrete endpoints belong to the implementation.
| Upstream → Downstream | Pattern | Integration |
|---|---|---|
| Identity → all other contexts | Published Language | The signed identity token (memberships, roles, domains) is the published language every context conforms to. Downstream contexts are Conformists: they read the token but never reshape it. |
| Building Management → Telemetry Ingestion | Customer / Supplier | When a Building is created, Building Management supplies a threshold baseline. It is the customer driving the contract. |
| Building Management → Telemetry Distribution | Customer / Supplier | Building Management supplies default display preferences and contributes the structural metrics it owns to the catalog. |
| Telemetry Ingestion → Telemetry Distribution | Open Host Service + Published Language | Telemetry Ingestion describes its metrics in a stable, self-describing form. Distribution gathers those descriptions, so new metrics appear downstream without a coordinated change. |
| Building Management → Alerting | Customer / Supplier (Anti-Corruption) | Alerting asks Building Management which domains a building belongs to, and translates the answer into its own terms. |
| Building Management → Knowledge & Assistance | Anti-Corruption Layer | The assistant reaches live building data only through a thin read-only layer, keeping the Building model out of the assistant’s domain. |
| Telemetry Ingestion → Knowledge & Assistance | Anti-Corruption Layer | The assistant reaches live signals and thresholds the same way — a thin read-only layer, never the Telemetry model itself. |
The Context Map above is organised by relationship pair. Here we restate the same decisions organised by pattern, so it is explicit which of the model-integrity patterns we relied on and where. We use all four.
| Pattern | Used? | Where, in our model |
|---|---|---|
| Shared Kernel | Yes, but deliberately minimal | The only thing shared across contexts is the Domain name identifier (see the next section). It is a tiny shared kernel on purpose — one stable, human-readable string, never shared code or a shared schema. |
| Customer / Supplier | Yes | Building Management is the upstream supplier; Telemetry Ingestion and Telemetry Distribution are the downstream customers that drive the threshold baseline and display preferences they need. Alerting is also a customer of Building Management, for domain resolution. |
| Conformist | Yes | Every context is a Conformist to the identity-token Published Language issued by Identity & Access: it reads the token and never reshapes it. Telemetry Distribution is likewise a Conformist to the metric descriptions published by each upstream context. |
| Anti-Corruption Layer | Yes | Knowledge & Assistance reaches live Building and Telemetry Ingestion data only through a thin read-only ACL, keeping those models out of the assistant. Alerting applies the same idea, translating Building Management’s domain answer into its own terms rather than adopting that model wholesale. |
Two further integration patterns appear in the Context Map and are worth naming: the identity token and the metric descriptions are both Published Language, and Telemetry Ingestion exposes its metrics as an Open Host Service so new metrics surface downstream without a coordinated change. We did not need a Separate Ways or Partnership relationship — no two contexts are either fully independent or jointly owned.
Across the platform, we decided that a Domain is referenced by its name, never by an internal database identifier. This is the closest thing the design has to a Shared Kernel, and it is deliberately minimal: a single, stable, human-readable string rather than shared code.
erDiagram
ACCOUNT ||--o{ MEMBERSHIP : holds
MEMBERSHIP }o--|| DOMAIN_NAME : "references by name"
BUILDING }o--o{ DOMAIN_NAME : "scoped by name"
PREFERENCE }o--|| DOMAIN_NAME : "scoped by name"Because the domain name travels inside the identity token and is recorded by each context, the intent is that multi-tenant isolation can be enforced symmetrically — each context answers “may this caller see this data?” from data it already holds, without consulting another context.
We build the tactical model from the seven DDD building blocks. The table states, for each, whether we reached for it, and where it sits in our model — at the model level, deliberately ahead of any implementation. The blocks recur throughout the per-context pages.
| Building Block | Used? | How it appears in our model |
|---|---|---|
| Entity | Yes | An object with an identity that lives inside an aggregate, e.g. Room within Building. |
| Value Object | Yes — heavily | An immutable, identity-less descriptor. Most of our small concepts are value objects: Coordinates, Dimensions, Bound, Role, Membership, RoomThreshold, the per-type alert preferences. |
| Aggregate Root | Yes | A top-level object that owns a consistency boundary: Account, Domain, Building, each *Signal, BuildingThreshold, BuildingPreference, DeliveryChannel, AlertPreference. |
| Domain Event | Yes | A noteworthy fact we expect a context to broadcast for others to react to — a new reading from Telemetry Ingestion, an alert from Alerting — crossing the context boundary as a published event. |
| Service (Domain Service) | Yes | Stateless behaviour we place outside any single entity: identity-token issuance and role invitation (Identity & Access), reading validation (Telemetry Ingestion), distribution (Telemetry Distribution). |
| Repository | Yes — but deliberately left thin here | In the model, persistence is a Repository responsibility we deliberately keep out of the domain: each aggregate is meant to be stored and retrieved as a whole through one repository per context, so that a consumer of a context never depends on how the aggregate happens to be stored. We state only that this responsibility exists; how it is realised is a decision left for later. |
| Factory | Lightly | We chose not to introduce dedicated Factory objects. Where creation is more than a constructor, we fold it into the relevant domain service — assembling and signing the identity token, or normalizing a raw reading into a canonical signal. |
On the per-context pages these blocks do not get headings of their own. Because an entity or a value object has no meaning outside the aggregate that owns it, each is documented inside its aggregate — tagged with its <<Entity>> / <<Value Object>> stereotype in the aggregate’s class diagram, and explained alongside the aggregate root. The aggregate is the unit of presentation, exactly as it is the unit of consistency.
Some aggregates are intentionally thin at this stage — they describe data and a few rules rather than rich behaviour. This was a conscious trade-off while the contexts were still settling, and the per-context pages note where we expect behaviour to grow onto the aggregate as the design firms up.