/

Developer Guide

Strategic Design

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.

A pragmatic stance

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.


Domain and Subdomains

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.

SubdomainTypeRationale
Spatial Digital TwinCoreThe 3D building model is the product’s differentiator and receives the richest model.
Telemetry Ingestion & ThresholdsCoreTurning raw sensor signals into meaningful, bounded readings is central business logic.
Telemetry DistributionCorePer-building stream shaping is what makes the live view feel tailored.
Identity & AccessSupportingMulti-tenant access control is essential but not a differentiator.
AlertingSupportingPush alerting supports the core experience without defining it.
Knowledge & AssistanceSupportingThe assistant augments the product; it is explicitly experimental.
Real-Time TransportGenericA commodity relay with no domain model of its own.

Bounded Contexts

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"| KA

The 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.


Context Map

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 → DownstreamPatternIntegration
Identity → all other contextsPublished LanguageThe 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 IngestionCustomer / SupplierWhen a Building is created, Building Management supplies a threshold baseline. It is the customer driving the contract.
Building Management → Telemetry DistributionCustomer / SupplierBuilding Management supplies default display preferences and contributes the structural metrics it owns to the catalog.
Telemetry Ingestion → Telemetry DistributionOpen Host Service + Published LanguageTelemetry 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 → AlertingCustomer / Supplier (Anti-Corruption)Alerting asks Building Management which domains a building belongs to, and translates the answer into its own terms.
Building Management → Knowledge & AssistanceAnti-Corruption LayerThe 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 & AssistanceAnti-Corruption LayerThe assistant reaches live signals and thresholds the same way — a thin read-only layer, never the Telemetry model itself.

Model Integrity Patterns

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.

PatternUsed?Where, in our model
Shared KernelYes, but deliberately minimalThe 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 / SupplierYesBuilding 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.
ConformistYesEvery 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 LayerYesKnowledge & 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.


The Shared Identifier Convention

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.


Tactical Building Blocks

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 BlockUsed?How it appears in our model
EntityYesAn object with an identity that lives inside an aggregate, e.g. Room within Building.
Value ObjectYes — heavilyAn immutable, identity-less descriptor. Most of our small concepts are value objects: Coordinates, Dimensions, Bound, Role, Membership, RoomThreshold, the per-type alert preferences.
Aggregate RootYesA top-level object that owns a consistency boundary: Account, Domain, Building, each *Signal, BuildingThreshold, BuildingPreference, DeliveryChannel, AlertPreference.
Domain EventYesA 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)YesStateless behaviour we place outside any single entity: identity-token issuance and role invitation (Identity & Access), reading validation (Telemetry Ingestion), distribution (Telemetry Distribution).
RepositoryYes — but deliberately left thin hereIn 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.
FactoryLightlyWe 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.

Where the model is deliberately light

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.