/

Developer Guide

Digital Twin Service Architecture

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.


Architectural Style

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 --> RL

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


Key Architectural Decisions


Integration

For the registration flow, name-normalisation rules, synchronisation details, and the API, see the Digital Twin Service internals page.