Developer Guide
Bounded context: Building Management · Stack: Rust / Axum · Code-level walkthrough: Digital Twin Service
The twin-service realises the Building Management context. It is the spatial source of truth — the structural model of buildings and rooms that the live-data services decorate.
A flat, three-layer architecture — HTTP handlers, domain models, and infrastructure — wired directly through a shared AppState, with no interface layer between a handler and what it depends on. Handlers (api/buildings.rs) combine HTTP adaptation and business logic in one place, matching contracts-service’s established Rust convention: the language’s own module system and Result-based error handling already do the job a thin controller layer would.
Identity and authorization arrive as Axum extractors, not as calls to another service. GatewayClaims (infra/claims.rs) implements FromRequestParts, so any handler that needs the caller’s identity just declares it as an argument — decoding and validating the mesh-injected x-gateway-claims header happens before the handler body runs, with no network call to claims-gateway or tenancy-service. Authorization is the same shape: Cedar (infra/authz.rs) evaluates the request in-process against a policy bundle compiled into the binary via include_str!.
graph LR
subgraph Entry
R["lib.rs (Router)"]
CL["GatewayClaims extractor\ninfra/claims.rs"]
end
subgraph Handlers
H["api/buildings.rs"]
end
subgraph Infrastructure
DB["infra/db.rs\nMongoDB Collection (Building)"]
AZ["infra/authz.rs\nCedar, compiled-in policy"]
OUT["infra/outbound.rs\nsensor-service · contracts-service"]
MET["infra/metrics.rs"]
RL["infra/ratelimit.rs"]
end
R --> H
CL --> H
H --> DB
H --> AZ
H --> OUT
H --> MET
H --> RLThe integration test suite runs against a real MongoDB instance, exercised through the full Router via tower::ServiceExt::oneshot — the same code path a real request takes, not a swapped-in double. Metrics are recorded through an Axum Router::layer, which wraps the entire request/response cycle regardless of how the inner handler responds — so http_requests_total/http_request_duration_seconds increment for every route, labeled by {method, route, status_code} using the route template rather than the raw URL, so cardinality stays bounded even with real ids in the path. Cedar’s policy bundle (schema.cedarschema, policy.cedar) is compiled into the binary via Rust’s include_str! rather than read from disk at process startup — the production Docker image doesn’t need backend/auth-policy present at runtime, only the builder stage does.
400 on a malformed room rather than storing bad data or surfacing an opaque 500.twin-db; no other service reads it.For the registration flow, name-normalisation rules, synchronisation details, and the API, see the Digital Twin Service internals page.