Quality Attributes Series. A city digital twin is not one system. It is a system of systems: domain twins, shared platforms, field devices and third-party services, each owned by someone different and each changing on its own schedule. This series looks at the quality attributes that decide whether such a thing works in practice, one attribute per post, and at the architectural mechanisms that achieve each one. Chapter 1 is about maintainability.
A city digital twin rarely fails on launch day. Launch day is when it looks best: a 3D model, live traffic, a dashboard that makes the mayor’s office happy.
It fails in year three. The traffic control system vendor ships new firmware and the message format changes. The transit operator moves to a new GTFS version. The water utility wants its own twin connected. A tunnel concession is re-tendered and the new operator exposes a completely different API. None of these is a big change. Each one, in a badly built twin, touches twenty places.
That is a maintainability problem: the cost and risk of adding, modifying, testing and integrating parts of the system. And a city has more of it than almost any other domain.
Table of contents
Open Table of contents
- What quality attributes are, and why they shape the architecture
- Why a system of systems makes maintainability harder
- Mechanisms: tactics and patterns
- Family 1: managing dependencies
- Family 2: controlling and observing state
- Family 3: managing deployments
- What each tactic improves
- Patterns in the city
- Patterns are never pure
- Trade-offs: not every millisecond matters
- Governance: the mechanism that is not technical
- In summary
- References
What quality attributes are, and why they shape the architecture
Every system has two kinds of requirements. Functional requirements say what it does: show the state of the water network, predict congestion, open the emergency exits. Quality attributes say how well it does it: how fast, how reliably, how securely, how cheaply it can be changed. Availability, performance, security, interoperability, scalability and maintainability are all quality attributes.
The distinction matters because the two kinds of requirements put very different pressure on the architecture. Almost any structure can deliver a given set of functions: one big application, a dozen services, a federation of twins. What separates those structures is the quality attributes. A city twin that predicts congestion in a single process and one that does it through an event backbone and a set of domain twins have the same function. They have very different availability, very different latency and very different cost of change. The functions decide what the system does; the quality attributes decide what shape it takes.
Three things make quality attributes hard to get right:
- They are easy to state vaguely. “The twin must be maintainable” cannot be tested. “When a vendor changes a device’s message format, the change is absorbed by one adapter in under two days, without modifying any twin” can. Quality attributes are only useful when they are written as concrete scenarios: a stimulus, the part of the system it hits, and a measurable response.
- They conflict. Almost every mechanism that improves one attribute costs something in another. An intermediary improves maintainability and adds latency. Encryption improves security and costs performance. Architecture is largely the work of choosing those trade-offs on purpose.
- They are hard to add later. A missing function can be added in a sprint. A missing quality attribute usually means restructuring, because it lives in the relations between components, not inside any one of them.
In a system of systems like a city twin these three problems are amplified. The attributes have to hold across systems you do not own, and the trade-offs are negotiated between organisations, not just between components. That is why this series takes them one at a time.
Why a system of systems makes maintainability harder
In a single application, the team that owns the code also decides when it changes. In a city twin that is almost never true. The sources of change are many and they are independent:
- Devices and vendors. Sensors, controllers and the control systems behind them get replaced on their own schedules, usually through separate tenders.
- Data standards. GTFS, NGSI-LD, SAREF, CityGML, IEC CIM. Each one evolves, and not all of a city’s sources move at once.
- Models. A traffic model is recalibrated, a hydraulic model is replaced, a machine learning predictor is retrained every week.
- Organisations. Utilities, concessionaires, municipal departments, regional authorities. Each owns part of the data and none of them reports to the twin team.
- Regulation. Privacy, cybersecurity and open data obligations change what you can store and how you can expose it.
Each of these is someone else’s change landing on your architecture. Maintainability is about how far that change spreads.
This is the defining property of a system of systems: its constituent systems are operationally and managerially independent. You cannot stop them from changing. You can only decide how much of the rest of the city each change is allowed to reach.
Mechanisms: tactics and patterns
To achieve a quality attribute, an architect chooses mechanisms. Two kinds are useful here:
- Tactics are small building blocks. Each one acts on a single element or a single interaction. Encapsulate, use an intermediary, rollback.
- Patterns are built from tactics. Publish-subscribe, adapter, canary deployment.
Tactics are the atoms, patterns are the molecules. The distinction matters because no pattern is ever applied in a pure form. To know whether a pattern in your architecture really delivers maintainability, you break it back into its tactics and check whether each one survived the implementation.
The other thing to keep in mind from the start: tactics are scale-free. Encapsulation was first applied to modules in source code, later to containers, later to services. In a system of systems the same tactics apply to whole twins and whole platforms as units of deployment. The encapsulation that protects a class from its neighbours is the same encapsulation that protects the emergency twin from the next change in the mobility twin.
Maintainability tactics fall into three families: managing dependencies, controlling and observing state, and managing deployments.
Figure 1. Maintainability tactics grouped in three families, each with its reading in a city digital twin.
Figure 2 places those tactics in the architecture of a city twin: domain twins on top, shared common services underneath, and no direct dependency between twins.
Figure 2. The domain twins sit on top and draw on shared common services, with the ontology at the centre, instead of depending on each other. The panel on the right locates each family of maintainability tactics in the architecture, and marker 1 shows a vendor change absorbed by a single adapter without reaching any twin.
Family 1: managing dependencies
The first family limits how far a change can travel. Here is what each tactic means when the “module” is a whole domain twin.
Encapsulate. The foundation of all the others. Each domain twin — mobility, energy, water, emergencies — exposes an explicit interface and hides everything else: its internal model, its database, its simulation engine. The mobility twin can swap its traffic model without the emergency twin noticing, because the emergency twin only ever saw the API.
Use an intermediary. A context broker or an event backbone between producers and consumers, so neither needs to know the other exists. The tunnel sensors do not know which twins are listening, and the twins do not know which vendor made the sensors.
Restrict dependencies. Rules about who may talk to whom. No twin reads another twin’s database. No dashboard calls a field device directly. Everything goes through the backbone or the published API. In a city this is enforced through network segmentation and access policies, not just good intentions.
Abstract common services. Identity, geolocation, time series storage, the ontology, the asset register. If every twin builds its own, a change to the city’s coordinate reference system becomes five projects. Build them once, as shared platform services.
Split module. A twin that does too much becomes expensive to change. A “mobility twin” that covers traffic signals, parking, public transport and micromobility will probably need to be split along the lines where change actually happens.
Increase semantic coherence. A useful test: list the changes you expect and see which responsibilities each one affects. Responsibilities that are never affected by the same changes do not belong in the same twin. Twin boundaries should follow reasons for change, not organisational charts.
Defer binding. Decide as late as possible. Which sources feed a twin, which model version runs, which subscribers receive an event — all set by configuration or discovery at deploy time or runtime, not in code. A twin registry is defer binding at city scale.
Refactor. Not a one-off. In a twin expected to live for decades it is ordinary, scheduled work.
Family 2: controlling and observing state
The second family makes it easy to test the system after a change. In a city twin this is harder than it sounds, because the real input — the city — cannot be paused or repeated.
Specialized interfaces. Every twin should expose a way to set and read its state for testing: put the water network in a known pressure configuration, read the result. Without it, regression testing a twin means waiting for reality to produce the right conditions.
Record/playback. Log everything that crosses the backbone. When a storm night produces a fault, you can replay that exact event stream against the next version of the flood twin. In a city this is the cheapest and most valuable tactic on the list, because the hard cases — floods, blackouts, major events — are exactly the ones you cannot recreate.
Localize state storage. If a twin’s state is scattered across caches, databases and in-memory simulations, you cannot snapshot it and you cannot restart it from a known point. Keep it in one place per twin.
Abstract data sources. The twin should not care whether its input is the live feed from a control system, a replayed log or a synthetic generator. Same interface, different source. This is what makes the next tactic possible.
Sandbox. Run an instance isolated from the real world, with no consequences. A digital twin is often sold as a sandbox for the city, but the twin itself also needs one: a copy where a new signal-timing algorithm can be tested without any command reaching a real traffic light.
Executable assertions. Checks embedded in the system that fail loudly when the state is wrong. In a city twin: a flow sensor reporting negative values, a bus 40 km outside its route, an asset with no location. SHACL validation at ingest is exactly this tactic.
Family 3: managing deployments
The third family manages the moment of change itself.
Segment deployments. Never all at once. Roll out by district, by corridor, by a subset of intersections. A new adaptive signal algorithm goes to twelve intersections first, not to the whole city.
Rollback. In a system of systems, a release is usually a coordinated update across several services, and rollback has to reverse all of it. In a twin, that includes not only code but also models, calibrations and ontology versions. Rolling back the service but keeping the new calibration is not a rollback.
Feature toggle. A kill switch for new features at runtime, without redeploying. When the new flood predictor starts producing false alarms at three in the morning, the operator switches it off and the previous one takes over.
Command dispatcher. Deployments are scripted and the scripts are treated as code. With dozens of services across several twins, manual deployment is how you introduce the faults you then spend a week diagnosing. In practice, this is GitOps.
What each tactic improves
A tactic is only useful if you can say what it improves. Maintainability can be broken down into a set of properties you can actually measure in a city twin:
- Coupling. How many twins, services or adapters a single change touches.
- Cohesion. Whether the responsibilities inside a twin change for the same reasons.
- Observability of state. Whether you can see what state a twin is in at any moment.
- Control of state. Whether you can put a twin in a given state on purpose, for a test or a replay.
- Test efficiency. How much time and effort it takes to verify a twin after a change.
- Controllability. How precisely you can decide what gets deployed, where and when.
- Granularity. How small a unit you can deploy: the whole platform, one twin, one model, one district.
- Efficiency. How much time and effort a deployment, or undoing one, costs.
The matrix below relates each tactic to those measures.
| Tactic | Coupling | Cohesion | Observability of state | Control of state | Test efficiency | Controllability | Granularity | Efficiency |
|---|---|---|---|---|---|---|---|---|
| Encapsulate | + | + | * | * | + | |||
| Use an intermediary | + | * | * | |||||
| Restrict dependencies | + | + | ||||||
| Abstract common services | + | * | * | * | ||||
| Split module | * | + | + | |||||
| Refactor | + | + | * | * | * | |||
| Increase semantic coherence | + | |||||||
| Defer binding | + | − | ||||||
| Specialized interfaces | + | + | + | |||||
| Record / playback | + | + | ||||||
| Localize state storage | + | + | + | |||||
| Abstract data sources | + | + | ||||||
| Sandbox | + | |||||||
| Executable assertions | + | |||||||
| Segment deployments | + | + | ||||||
| Rollback | + | + | ||||||
| Feature toggle | + | + | ||||||
| Command dispatcher | + |
Table 1. Maintainability tactics and their relationship to measures of interest. A plus sign indicates that the tactic positively addresses maintainability properties and hence measures, a minus sign indicates that the tactic has a negative effect, and an asterisk indicates that the tactic might positively or negatively address the measure, depending on its realization. A blank cell means that the property has no consistent effect on the measure.
Two readings of the matrix matter in a city.
The asterisks are design decisions, not uncertainty. Encapsulating a twin can make its state easier or harder to observe. It is easier if the API exposes the state, and harder if the encapsulation hides it so well that nobody can check what the twin believes about the water network. The same goes for the event backbone and the shared services. Whether an asterisk becomes a plus depends on whether you designed for it.
Defer binding is the only minus, and it is the one a city feels most. When sources, models and subscribers are bound by configuration at runtime, the combination you tested is not necessarily the one running in production. A twin registry gives you a lot of flexibility and makes the twin harder to test exhaustively. The answer is to treat configuration as code: versioned, reviewed and deployed with the same tactics as everything else.
Patterns in the city
Here is where the common maintainability patterns appear in a city twin, which tactics they are made of, and what they cost.
| Pattern | Where it lives in a city twin | Main tactics | Trade-off to watch |
|---|---|---|---|
| Layers | Field → ingestion → backbone → twins → applications | Restrict dependencies, abstract common services | Extra hops; layer bridging for “urgent” cases |
| Pipe-and-filter | Ingestion: adapt → validate → anonymise → harmonise | Encapsulate, defer binding | A failed filter stops the pipeline without checkpoints |
| Publish-subscribe | Event backbone, MQTT, NGSI-LD subscriptions, UNS | Intermediary, restrict dependencies, defer binding | Harder testing, non-deterministic ordering, limited end-to-end crypto |
| Adapter | One per sensor vendor or external protocol | Encapsulate, intermediary, restrict dependencies | Latency per call |
| Façade | City API offered to third parties and other administrations | Encapsulate, restrict dependencies, semantic coherence | Simplified interface may not cover every use case |
| Memento | Scenario snapshots: “the network state before the change” | Localize state storage, rollback | Large states cost memory and restart time |
| Blue-green | Twin platform environments | Segment deployments, rollback, defer binding | Two identical environments can be expensive |
| Canary | New models on a district or a subset of assets | Segment deployments, sandbox, feature toggle | Routing rules get complex; metrics can mislead |
| Rolling | Twin services updated node by node | Segment deployments, rollback, feature toggle | Two versions coexist and confuse diagnosis |
| Circuit breaker | Calls to concessionaires, weather services, external endpoints | Intermediary, restrict dependencies | Up-front mapping of every external dependency |
| Strategy | Swappable traffic, hydraulic or demand models | Defer binding, specialized interfaces | Behaviour spread over more files |
| Intercepting filter | Authentication, anonymisation, validation around every twin API | Split module, defer binding, executable assertions | Long filter chains add latency |
Three of these deserve a closer look in a city.
Adapters exist for exactly the situation a city is full of: devices from different manufacturers that do the same job through different interfaces. When the vendor of a control system changes, you write one new adapter. If you find yourself changing the twin instead, the adapter was not doing its job.
Publish-subscribe is everywhere in smart city architectures — it is the core of a Unified Namespace. It has real costs: it makes testing harder, because a small change in which subscribers listen to which topics can change system behaviour widely, and it weakens determinism. The answer is not to avoid it. It is to pair it with record/playback, which the same pattern makes easy, since every event can be logged.
Blue-green and canary hit a limit every city architect learns quickly: you cannot blue-green the physical city. There is only one tunnel. You can run two versions of the twin, but only one of them can send commands to the jet fans. Which is why, in a city, canary releases usually start in read-only mode — the new version observes and predicts in parallel, and only takes control of a small segment once its predictions have been compared with reality.
Patterns are never pure
A pattern can be present in name and absent in practice. You find out by breaking it back into tactics and checking each one.
A common example in cities: a Unified Namespace where any twin can publish to any topic, with no schema and no ownership. It is publish-subscribe on paper. But restrict dependencies has gone — anything can depend on anything through the topic tree — and encapsulate has gone too, because consumers end up depending on the internal structure of messages nobody versioned. The pattern is there. The maintainability is not.
Another one: a “city API” façade that, for performance, lets a few applications call the twins’ databases directly. That kind of layer bridging is acceptable when it is isolated, small and well understood. The problem is when it is none of those things, and the façade becomes a decoration in front of a mesh of direct dependencies.
Trade-offs: not every millisecond matters
Almost every pattern in the table costs some latency. Intermediaries add hops; adapters add calls; filters add processing. Often the added latency is lost in the noise and the maintainability benefit is enormous. Measure, then decide.
In a city that measurement depends heavily on the use case. A strategic planning twin that recalculates overnight can absorb any number of intermediaries. A tunnel ventilation control loop or a traffic signal pre-emption for an ambulance cannot. The architecture does not have to choose one for the whole city: the same twin can have a short, direct path for control and a long, layered, maintainable path for everything else. What matters is that the short path is a deliberate decision, documented and isolated — not something that grew because it was faster to write.
This tension between maintainability and performance will come back in later posts of the series. Quality attributes are never optimised alone.
Governance: the mechanism that is not technical
Technical mechanisms are necessary but not sufficient. Without documentation, reviews, maintained test suites and people who understand the architecture, every change slowly erodes the structure the tactics were meant to protect.
A system of systems adds its own version of this, because the people making changes do not all work for you:
- Procurement. Every sensor, platform or model you buy should come with open interfaces, documentation and the right to write adapters for it. If the contract does not say so, no tactic will save you.
- Ownership of shared entities. Who decides when the definition of a “road segment” changes? If nobody does, every twin will have its own, and abstract common services is lost.
- Contracts with third parties. Concessionaires and utilities change their systems on their own schedules. Notice periods, version support and test environments belong in the agreement.
- People. The architects and developers who touch the twin are spread across departments and companies. The architecture has to be written down in a form they can all read, or each of them will undermine it with the best intentions.
In summary
A city digital twin is not maintained like a single application, because it is not one. It is a set of twins, platforms and external systems, each changing on its own schedule.
Three families of tactics — manage dependencies, control and observe state, manage deployments — and a set of patterns built from them give you the means to contain that change. What makes them work in a city is that tactics are scale-free: they apply to whole twins as much as to the code inside them.
Use the patterns, but check the tactics. Measure the trade-offs instead of assuming them. And put governance underneath, because the change that breaks a city twin almost never comes from inside it.
Next in the Quality Attributes Series: further attributes for the city twin as a system of systems, each with its own mechanisms and its own trade-offs against the ones already covered.
References
- Kazman, R., Bianco, P., Ivers, J. and Klein, J. (2020). Maintainability. Technical Report CMU/SEI-2020-TR-006, Software Engineering Institute, Carnegie Mellon University. Chapter 5, Mechanisms for Achieving Maintainability.
- Bass, L., Clements, P. and Kazman, R. (2012). Software Architecture in Practice, 3rd edition. Addison-Wesley.