Evolutionary Architecture Structure: Connascence, Architectural Quanta, and Contract-Driven Design

This article explores how software topology determines evolvability through connascence-based coupling analysis, architectural quanta as independently deployable units, contract spectra from strict RPC to loose key-value, microservices as evolutionary templates, data mesh for analytical data, and evolutionary database practices including Expand/Contract migrations and data governance.

Code Archaeology
Code Archaeology
Code Archaeology
Evolutionary Architecture Structure: Connascence, Architectural Quanta, and Contract-Driven Design

1. Evolvability Depends on Coupling

Architecture discussions ultimately center on coupling — how components connect and depend on each other. Evolutionary architecture doesn't seek to eliminate coupling but to achieve appropriate coupling : which dimensions to couple, and to what degree, to maximize benefit with minimal cost.

A counter-intuitive conclusion: architectural style itself does not determine evolvability . What matters is the coupling characteristics the architecture supports.

Connascence: A Finer-Grained Coupling Language

In 1996, Meilir Page-Jones introduced connascence in What Every Programmer Should Know About Object-Oriented Design :

Two components are connascent if a change in one would require the other to be modified in order to maintain the overall correctness of the system.

Page-Jones categorizes connascence into two types:

Static Connascence (source-code level)

CoN (Name) : Components must agree on an entity's name . Method names are the most common and most ideal form — modern refactoring tools make global renaming trivial.

CoT (Type) : Components must agree on a type . Common in statically typed languages; dynamic languages can provide optional typing.

CoM / CoC (Meaning / Convention) : Components must agree on the meaning of values . Classic example: hard-coded numbers instead of constants, e.g., int TRUE = 1; int FALSE = 0 — flipping values breaks correctness.

CoP (Position) : Components must agree on value order . Even statically typed languages cannot avoid this: updateSeat(String name, String seatLocation) called as updateSeat("14D", "Ford, N") — types match but semantics are wrong.

CoA (Algorithm) : Components must agree on an algorithm . Typical example: encryption hash algorithms running on both client and server must produce identical results — a strong coupling where any detail change breaks the handshake.

Dynamic Connascence (runtime calls)

CoE (Execution) : Execution order of multiple components matters. Example: must call setRecipient, setSender, then send, finally setSubject — wrong order fails.

CoT (Timing) : Timing of execution matters. Classic race condition: two threads executing simultaneously affect joint operation result.

CoV (Value) : Multiple values are interrelated and must change together . Example: four points defining a rectangle — cannot change one point without adjusting the other three. Harder in distributed systems: cross-database updates where a value must change in multiple databases atomically.

CoI (Identity) : Multiple components must reference the same entity . Example: two independent components sharing and updating the same data structure, such as a distributed queue.

Dynamic connascence is harder to analyze because we lack effective runtime analysis tools comparable to static call-graph analysis.

Connascence Properties

Strength : How easy it is to refactor the coupling. Architects should prefer static over dynamic connascence because static coupling is detectable via simple source analysis and modern tools can easily improve it — e.g., refactor CoM to CoN by replacing magic values with named constants.

Locality : Code distance between coupled parties. Nearby code naturally has more and stronger connascence; distant code should not.

Degree : Scope of impact — how many classes/modules are affected. Smaller degree means less breakage. High dynamic connascence limited to few modules may be tolerable, but codebase growth turns small problems into large ones.

Page-Jones offers three modularity improvement rules:

Minimize overall connascence.

Minimize connascence across encapsulation boundaries.

Maximize connascence within encapsulation boundaries.

Jim Weirich later popularized connascence with two practical guidelines:

Degree Rule : Convert strong connascence forms into weaker ones.

Locality Rule : The farther apart software elements are, the weaker the connascence form they should use.

Bounded Contexts and Connascence Locality

Eric Evans' Domain-Driven Design defines bounded context : everything related to a domain is visible internally, opaque to other bounded contexts. Instead of a single organization-wide Customer class, each problem domain owns its own, reconciling differences at integration points. This isolation applies to implementation details like database schemas — microservice data isolation draws directly from bounded contexts.

Goal when designing with DDD: prevent implementation details from leaking out of bounded contexts . Communication between contexts must be mediated by contracts .

Comparing 1993 connascence locality advice with 2003 bounded context guidance reveals they address the same principle: letting coupling spread to a larger scope makes architecture brittle . Brittle architecture means a small change in one place triggers unpredictable, non-local breakage elsewhere.

Extreme example: exposing an application's database schema as an architectural integration point. Database schema belongs to the bounded context as an implementation detail; exposing it to other applications means changing one app's database may unpredictably break others. Exposing implementation details to a wider scope damages overall architectural integrity.

From 1993 to present, architectural consensus remains: constrain implementation coupling to the smallest possible scope . We've just lacked good terminology — whether called bounded context or connascence locality, architects have wrestled with coupling for decades.

However, bounded context originates from DDD at the abstract design level . We need a concept reflecting bounded context but expressed in technical architecture language that aligns with architectural concerns.

2. Architectural Quantum

Software systems bind together in various ways. Component-level coupling isn't the only binding: many business concepts also semantically bind system parts into functional cohesion. To evolve software successfully, we must consider all potential coupling break points.

In physics, a quantum is the smallest unit of a physical entity participating in interaction. An architectural quantum is an independently deployable, highly functionally cohesive component containing all structural elements required for the system to operate. In a monolith, the quantum is the entire application — highly coupled, must deploy as a whole.

An architectural quantum measures two aspects of software architecture — topology and behavior — regarding how parts connect and communicate:

Static Coupling : How static dependencies resolve via contracts . Includes OS, frameworks/libraries via transitive dependencies, and other operational requirements for the quantum to run.

Dynamic Coupling : How quanta communicate at runtime — synchronous or asynchronous. Therefore this characteristic's fitness function must be continuous , typically via monitoring.

These directly correspond to connascence's two concepts. Simply put: static coupling describes how services are wired; dynamic coupling describes how services call each other at runtime. In microservices, a service must bring its database — that's static coupling (won't run without data); it may call other services in a workflow — that's dynamic coupling. So static coupling analyzes runtime dependencies; dynamic coupling analyzes communication dependencies.

Architectural Quantum = independently deployable artifact, highly functionally cohesive, high static coupling, synchronous dynamic coupling.

Architectural quantum is partly a measure of static coupling , and for most topologies it's simple:

Any monolithic style has quantum = 1.

Distributed architectures merely may have multiple quanta, not guaranteed. Example: brokered event-driven architecture always counts as single quantum.

Static analysis asks: Does this service depend on something required for the architecture to start? Even services not directly accessing a database get pulled into the same quantum's static coupling if they depend on a service that does.

What about distributed architectures without shared coupling points? Imagine an event-driven system with two data stores and two service groups with no static dependencies between them — each quantum can run in a production-grade ecosystem. That yields two quanta .

Microservices are highly decoupled, including data dependencies. Architects deliberately avoid creating coupling points between services , making each service its own quantum. But if the system is tightly bound to a single user interface , the entire architecture degrades to a single quantum .

3. Dynamic Coupling and Contracts

The final piece of the quantum definition is runtime synchronous coupling — how quanta interact to form workflows in distributed architectures.

How services call each other is a hard trade-off because it's a multi-dimensional decision space influenced by three interlocking forces:

Communication : Synchronous vs asynchronous connections.

Consistency : Whether workflows require atomicity or can accept eventual consistency.

Coordination : Whether workflows use orchestration or choreography -style service-to-service communication.

These three factors jointly influence key architectural decisions. Crucially, architects cannot make these choices in isolation: each option exerts gravitational pull on the others. For example, transactions are easier in synchronous + orchestration architectures; larger scale is only achievable in eventual consistency + asynchronous + choreography systems. Treating these three forces as interrelated forms a three-dimensional space.

Three-dimensional decision space for communication, consistency, coordination
Three-dimensional decision space for communication, consistency, coordination

A constant factor permeating almost every architectural decision: contract . It broadly refers to how different parts of the architecture connect . In software we loosely use contract to describe integration points; SOAP, REST, gRPC, XML-RPC are all part of the design process. We broaden and unify the definition:

Contract: the format used by parts of the architecture to convey information or dependencies.

This covers all technologies connecting system parts: transitive dependencies of frameworks/libraries, internal/external integration points, caches, and any other inter-component communication.

Software architecture contracts form a spectrum from strict to loose .

Contract spectrum from strict to loose
Contract spectrum from strict to loose

Strict Contracts

Require adherence to names, types, order, and all other details without ambiguity. The strictest example is remote method invocation — remote calls mimic internal method calls, matching names, parameters, types, etc. Many strict contract formats mimic method-call semantics; a whole stack of protocols carry some form of RPC — gRPC is a popular remote call framework that defaults to strict contracts .

Many architects like strict contracts because they mimic internal method-call semantics. But strict contracts create brittleness in integration architecture — to be avoided. Contracts are the glue of distributed architecture: the more frequently they change, the larger the ripple effect on other services. However, architects are not forced to use strict contracts — only when they provide advantage.

Looser Contracts

Include REST and GraphQL — very different from each other but both looser than RPC-style formats. In REST, architects model resources rather than method/process endpoints, making contracts less brittle. Example: a RESTful resource describing aircraft seat info — later someone adds engine details, the query doesn't break — adding information doesn't break existing consumers . GraphQL is often used in distributed architectures to provide read-only aggregated data , avoiding expensive orchestration calls across many services.

Anti-Pattern: Stamp Coupling

A common anti-pattern: Stamp Coupling . Wishlist only needs Customer Profile's name, but architects include all Profile fields in the contract anticipating future needs. Result: Profile changes a field Wishlist doesn't care about, breaks the contract, requires coordinated fix — introduced unnecessary breaking changes making architecture brittle with almost no benefit . Contracts should stay at need-to-know level, balancing semantic coupling and necessary information, not manufacturing brittleness.

Loosest End: Key-Value Pairs

The loosest end of the spectrum: YAML/JSON key-value pairs — raw facts only, no metadata or type information. Enables extremely decoupled systems but at a cost: contract certainty and validation disappear, application logic increases; concerns originally handled by contracts often shift to fitness functions .

4. Evolutionary Architecture Template: Microservices

Microservice architecture draws physical bounded contexts between architectural elements, encapsulating everything that can change. This architecture is inherently designed for incremental change . In microservices, bounded contexts serve as quantum boundaries , containing database servers and other dependencies; may also include search engines, reporting tools — anything contributing to the service's functional delivery.

Traditionally isolated roles — architects and operations — must collaborate in evolutionary architecture. Architecture is abstract until deployed ; developers must care how their components assemble in the real world. Regardless of architectural pattern, architects should explicitly define their quantum size . Small quanta mean small scope, fast change — small pieces are usually easier to manage than large ones. Quantum size determines the lower bound of incremental change achievable in an architecture.

Combining continuous delivery engineering practices with bounded context physical partitioning forms the philosophical foundation of microservices and the source of our architectural quantum concept.

Layered Architecture vs Domain-Driven Partitioning

In layered architecture , focus is the technical dimension — how application mechanisms operate: persistence, UI, business rules, etc. Most software architectures primarily address these technical dimensions. But another perspective exists. Suppose a key bounded context called Checkout exists — where does it live in layered architecture? Domain concepts like Checkout get smeared across layers — part in UI, part in business rules, persistence at bottom. Because architecture is sliced by technical layers, this architecture has no clear domain-dimension concept .

Layered architecture smearing domain concepts
Layered architecture smearing domain concepts

From a domain perspective, layered architecture's evolvability is zero . In highly coupled architectures, changes are hard because the parts you want to change are highly coupled. And in most projects, the common unit of change is precisely domain concepts . If teams are also siloed by layered architecture roles, changing Checkout requires coordination across many teams.

Conversely, if domain dimension is the primary architectural partitioning: each service defined around DDD concepts, encapsulating technical architecture and dependencies (databases, etc.) into a bounded context, forming a highly decoupled architecture. Each service owns all parts of its bounded context, communicates with other contexts via messages (REST or message queues). Thus no service is allowed to know another service's implementation details , avoiding inappropriate coupling. Operational goal: replace one service with another without disturbing other services .

Domain-driven microservice architecture
Domain-driven microservice architecture

Microservices roughly follow seven principles:

Model around business domain : Focus on business domain, not technical architecture; quantum reflects bounded context. Don't mistake one bounded context = one entity — it represents a business context/workflow . Goal isn't making services as small as possible but creating useful bounded contexts .

Hide implementation details : Technical architecture encapsulated within service boundaries; services integrate via messages or resources, not by exposing database schemas.

Automation culture : Embrace continuous delivery; use deployment pipelines for rigorous testing, automated provisioning and deployment.

Highly decentralized : Microservices are share-nothing architectures, goal is minimizing coupling; generally prefer duplication over coupling . Example: CatalogCheckout and ShipToCustomer both have an Item concept with similar names/attributes — teams want to reuse, thinking it saves effort — result is more trouble: changes must propagate across all teams sharing the component, and every service change risks breaking the shared component. Each keeps its own Item, passes only needed information, enabling independent evolution.

Independent deployment : Each service component deploys independently — a defining advantage; deployment and ops tasks usually fully automated including parallel testing and continuous delivery.

Isolate failures : Each service must handle reasonable error scenarios and recover. Circuit breakers, bulkheads, and other DevOps best practices are common; many microservice architectures follow the Reactive Manifesto .

Highly observable : No one can manually monitor hundreds of services, so monitoring and logging are first-class citizens. If ops cannot monitor a service, it effectively doesn't exist.

Microservices' primary goal: use physical bounded contexts to isolate domains and focus on understanding the problem domain. Therefore architectural quantum = service , making it an excellent evolutionary architecture example: a service changing its own database doesn't affect others because other services are not allowed to know implementation details like schemas. Of course, the changing service must deliver the same information via integration points (preferably protected by consumer-driven contract fitness functions), letting callers' developers remain happily unaware of the change.

As an evolutionary architecture template, microservices score naturally high on evolvability:

Incremental change easy on both fronts (each service forms bounded context around domain concept; heavy reliance on continuous delivery automation).

Guided change also works well (atomic and holistic fitness functions easy to build — clear service boundaries, multi-level testing inside services, integration coordination and testing between services).

If benefits are so clear, why wasn't this used before? Years ago, machines couldn't do automated provisioning. VMs were hand-crafted with long delivery cycles; OSes were commercially licensed with almost no automation support. Budget constraints affect architecture — hence developers built increasingly clever shared-resource architectures partitioned by technical layers. If ops is expensive and painful, architects route around it, like stuffing everything into enterprise service buses in SOA days. Continuous delivery and DevOps added a new variable to this dynamic equilibrium : machine definitions entered version control with extreme automation support; deployment pipelines spin up parallel test environments enabling safe continuous deployment; software stack largely open-source, reducing licensing concerns. Thus the community started building more domain-centric architectural styles.

In microservices, domain encapsulates technology and other architecture , so cross-domain evolution is easy. No architectural perspective is inherently right — it merely reflects goals developers set for the project. If you only watch technical architecture, cross-technical changes are easier; but ignoring domain perspective makes cross-domain evolution no better than a big ball of mud.

5. Reuse and Data Mesh

Code reuse is good, but like all good ideas, many companies overdo it , creating trouble. Every company craves reuse because software looks so modular, like electronic components — yet despite years of promises, truly modular software keeps eluding us .

Software reuse is hard and doesn't happen automatically. Many optimistic managers assume any code developers write is inherently reusable; reality differs. Some companies have produced genuinely reusable code, but it was deliberate and extremely difficult; developers often spend huge time trying to build reusable modules with little actual reuse.

In SOA, common practice: find commonalities, maximize reuse. Company has Checkout and Shipping contexts; architects discover both have Customer concept, so merge into a shared Customer service coupling both Checkout and Shipping — pursuing SOA's ultimate canonicality : one shared home per concept.

Ironically, the harder developers try to make code reusable, the harder it becomes to use . Making code reusable means adding options, decision points to accommodate different use cases; more hooks added for reuse, the more they hurt basic usability.

In other words, code's usability is often inversely proportional to its reusability. Building for reuse requires adding features for future varied uses; these future-oriented additions make it harder for developers to use the code for a single purpose .

Microservices avoid code reuse, adhering to prefer duplication over coupling : reuse implies coupling, while microservices极度解耦. But microservices' goal isn't embracing duplication — it's isolating entities within domains . Services sharing a class aren't independent. In microservices, Checkout and Shipping each have their own internal Customer representation; when customer info collaboration is needed, they pass relevant information to each other — architects don't reconcile or unify different Customer versions. Reuse benefits are illusory , while its coupling brings real harm. So architects, though aware of duplication's downsides, accept localized damage to avoid excessive coupling 's architectural damage.

Reuse can be asset or liability. Must ensure coupling points introduced by reuse don't conflict with other architectural goals. Example: microservices often use service mesh to couple service parts together to unify an architectural concern.

Effective reuse = abstraction + low volatility. A common challenge today: reconciling two opposing organizational goals: enterprise-wide reuse vs isolation via bounded contexts . Large enterprises naturally want maximum reuse across the ecosystem — more reuse, less from-scratch code. But reuse creates coupling, which many architects want to avoid, especially far-reaching coupling. Reusable things that change frequently and are used by several different architectural parts cause problems.

Following other distributed architecture trends, Zhamak Dehghani and other innovators adapted microservices' domain decoupling and service mesh core ideas, modified them, and applied them to analytical data . Operational/analytical data separation is another example of orthogonal coupling, but more complex than simple operational coupling.

Data Mesh is a decentralized approach to sharing, accessing, and managing analytical data . It serves broad analytical scenarios — reporting, ML model training, insight generation — by aligning data architecture and ownership with business domains , enabling peer-to-peer data consumption. Built on four principles:

Domain-oriented data ownership : Data owned and shared by the domain most familiar with it — either the producer or primary consumer. Architecture supports multiple domains sharing/accessing data peer-to-peer distributedly, no data-warehouse intermediate transformation step, no data-lake centralized storage.

Data as a product : To prevent data silos and encourage domain sharing, data mesh introduces data-as-product concept with organizational roles and success metrics ensuring domain-provided data offers good experience to all organizational data consumers. This principle yields a new architectural quantum — Data Product Quantum (DPQ) — responsible for maintaining and serving discoverable, understandable, timely, secure, high-quality data to consumers.

Self-serve data platform : Empower domain teams to build and maintain their own data products with a self-serve platform — declarative data product creation, discovery via search/browse, managing data lineage and knowledge graphs.

Federated computational governance : Ensure that even with decentralized ownership, org-wide governance requirements — compliance, security, privacy, data quality, data product interoperability — are consistently satisfied across all domains. Data mesh introduces a federated decision model of domain data product owners; their policies are automated and embedded as code in every data product . Architectural implication: each data product quantum contains a platform-provided embedded sidecar storing and executing policies at data read/write access points.

Data mesh core builds on modern distributed architectures like microservices. Like service mesh, teams build a DPQ adjacent to but coupled with their own service .

Data Product Quantum alongside service
Data Product Quantum alongside service

Service Alpha has both behavior and transactional (operational) data ; this domain also contains a DPQ with its own code and data, acting as interface to the system's analytical/reporting part. DPQ is a set of operationally independent but highly coupled behaviors and data.

Common DPQ types in modern architectures:

Source-aligned (native) DPQ : Represents the collaborative architectural quantum (usually a microservice) providing analytical data, serving as collaborative quantum .

Aggregate DPQ : Synchronously or asynchronously aggregates data from multiple inputs.

Purpose-specific DPQ : Customized for a specific need, may cover analytical reporting, BI, ML, etc. A domain can have multiple DPQs (different analytical types have different architectural characteristics, e.g., performance requirements).

Each data product quantum acts as its service's collaborative quantum (cooperative quantum) : an operationally independent quantum communicating with its "collaborator" via asynchronous communication and eventual consistency , yet maintaining tight contract coupling with the collaborator, while coupling to the analytical quantum (responsible for reporting, analytics, BI) is generally looser contract coupling . Two collaborative quanta are operationally independent yet represent two sides of the data : operational data in the service, analytical data in the DPQ.

The system also has a part responsible for analytics and BI, forming its own domain and quantum. To operate, this analytical quantum has static quantum coupling to the DPQs it needs; it may call DPQs synchronously or asynchronously depending on request type — e.g., some DPQs provide SQL interfaces allowing synchronous queries; other needs may require aggregating across multiple DPQs.

Data mesh is an excellent innovative mashup of microservice architecture and analytical data, and a roadmap for managing orthogonal coupling in distributed architectures . Sidecar and collaborative quantum concepts let architects selectively layer one architecture atop another — preserving preferred domain modeling (e.g., DDD) while allowing separated concerns to be well-governed and access what they need.

Ultimately, the primary characteristic determining architectural evolvability is controlled coupling . Whether inspired by connascence locality or DDD's bounded contexts, controlling the scope of implementation coupling is key to building evolvable architectures . Contracts let different architectural parts communicate without creating tight coupling points; using loosely defined coupling points, flexible contracts, and contract-based fitness functions, architects can define systems that meet requirements without creating governance and change obstacles.

6. Let Data Evolve Together

Relational and other data stores are ubiquitous in modern software projects. This is a coupling form often trickier than architectural coupling : data teams aren't accustomed to unit testing, refactoring, and other engineering practices; databases often become integration points, making data teams afraid to change due to ripple effects.

Data is a critical dimension to consider when building evolvable architectures. Microservice-style architectures demand far more architectural consideration around data partitioning, dependencies, transactionality — previously solely data team territory.

Evolutionary Database Design

When developers can build and evolve database structures as requirements change , evolutionary design at the database layer occurs. Database schemas, like class hierarchies, are abstractions ; when the real world changes, abstractions must follow, or they gradually drift from reality.

Evolutionary Patterns

How to build systems supporting evolution while using traditional tools like relational databases? Key: let schema and code evolve together . Continuous delivery solves "how to shove traditional data silos into modern software projects' continuous feedback loops." Developers must treat database structure changes like source code — tested, version-controlled, incremental :

Tested : Data teams and developers must rigorously test schema changes for stability. If using ORM or similar data mapping tools, consider adding fitness functions to ensure mappings and schema stay in sync .

Version-controlled : Schema must be version-controlled with the same code that uses it. Source code and database schema are symbiotic — neither works without the other; artificially separating these naturally coupled things only brings needless inefficiency.

Incremental : Schema changes should accumulate gradually like source code changes. Modern engineering practices avoid manual schema updates, preferring automated migration tools .

Database Migration Tools

Database migration tools let developers (or data teams) make small, incremental database changes and auto-apply them as part of the deployment pipeline . They range from simple CLI tools to complex IDE-like tools. To change schema, developers write small database migration scripts (also called delta scripts) .

Database migrations let DBAs and developers incrementally manage schema and code as a unified whole. Pulling database changes into the deployment pipeline's feedback loop gives developers more opportunities to incorporate automation and earlier validation into the project's build rhythm.

Shared Database Integration

A common integration pattern is shared database integration : using the database as a data-sharing mechanism. Three apps share one relational database — projects often default to this (governance requires everyone use the same relational DB, so why not share data across projects?). But architects quickly discover using the database as an integration point fossilizes the schema across all sharing projects .

If one coupled app wants to evolve capabilities via schema changes? ApplicationA changes schema, potentially breaking the other two apps. Fortunately, a common refactoring pattern resolves this coupling: Expand/Contract . It avoids timing issues by building a transition phase in the refactoring : developers have a starting state and ending state ; during transition both old and new states coexist . This transition state provides backward compatibility and gives other systems in the enterprise enough time to catch up . For some organizations, transition state may last days to months.

As shown in various scenarios, data teams and developers can leverage database native capabilities to build evolvable systems. Expand/Contract is a subset of a larger pattern — Parallel Change — used to safely implement backward-incompatible interface changes.

As long as correct engineering practices like continuous integration and source control are applied, databases can evolve alongside architecture. Being able to easily change database schemas is crucial: databases are abstractions based on the real world, and the real world changes unexpectedly. Data abstractions resist change more than behavior, but they must still evolve . Refactoring databases is a critical skill for both data teams and developers; to build evolvable systems, developers must embrace effective data practices together with data teams.

7. Data Entanglement and Governance

Data teams' tools and engineering practices are outdated relative to traditional development — refactoring, unit testing, dependency tracking, lint, mock/stub, all commonplace for developers, are often absent for data teams. Database data structures and application code are bidirectionally coupled; data teams struggle to refactor databases without involving application/ETL/report developers; under priority tug-of-war, database refactoring gets deprioritized, yielding suboptimal abstractions .

Transactions are a special form of coupling — absent from traditional architectural analysis tools, yet defining how business processes flow . Transaction boundaries often reveal how business concepts couple at implementation layer; transaction context required to support business often defines the lower bound of service granularity . Architects must consider all coupling dimensions (classes, packages/libraries, data schemas, transaction contexts) together — ignoring any breaks evolution. Physics binds atoms with strong nuclear force; transactions to architectural quanta are as strong nuclear force to atomic nuclei . Systems often can't escape transactions, so architects should constrain transaction contexts as much as possible ; also don't take microservices too literally — useful bounded contexts are the goal. Data teams spent decades stitching schemas together, have no interest in reversing; architects' desired granularity must match data concerns , otherwise sliding into inappropriate coupling.

Schema remains an abstraction of the real world, which changes. Data teams believing schemas never change are ignoring reality . Unwilling-to-refactor teams cope by adding yet another join table — works short-term but masks the true abstraction . Rare truly refactoring data teams eventually build a fossilized world — stacked with schema relics from different versions. Legacy data is also a tax on evolvability: it has survived multiple software generations, each with its own persistence quirks, resulting in data that's inconsistent at best, garbage at worst . Before building evolutionary architecture, first ensure developers can also evolve data — both schema and quality. Organizations must first clarify: what is true value — preserving legacy data forever, or having evolutionary change capability. Keep truly valuable data, archive older data for reference, but remove it from the evolution mainline.

Microservices split databases into one-per-service, dismantling the native governance data teams trust (e.g., referential integrity), making it hard to convince skeptical data teams. Architects can plug continuous fitness functions into the build to ensure critical mechanisms remain intact, giving data teams confidence.

Referential integrity is data-schema-level governance, but its cost is architectural coupling — two services bound together, hard to evolve independently. In distributed systems, teams use globally unique IDs for entities, write fitness functions guaranteeing delete propagation : when an entry is deleted by its owner, deletion propagates to other services that may still reference that entity.

Microservices force teams to distinguish who owns data (writes) vs who can read . Common pattern: owner publishes changes; readers pull at startup and cache in-process , refresh on demand; readers needing updates request owner. Avoids shared-database service coupling without introducing service-to-service communication overload.

Stored procedures are hard to unit test, refactoring-unfriendly , behavior isolated from other source code. Migrating to microservices must move them into code . Approach remains Expand/Contract : expand phase, add replacement method calls in code replacing stored procedures; contract phase, use fitness functions to confirm all dependencies migrated, delete stored procedures. This is the database version of Strangler Fig. Alternative: don't refactor stored procedures , create a wider data context — choose larger-grained service. No one-size-fits-all advice; teams must weigh trade-offs.

Microservices let architects choose persistence mechanisms per problem domain — relational, graph, key-value, no longer constrained by single standard. Decomposing monoliths gives teams opportunity to split data into different, more representative types : some data fits key-value better; some problems solvable in seconds with graph databases take hours or days in relational. But migrating from single database to multiple (even same type) brings new problems — everything in software architecture is a trade-off . Architects may struggle to convince data teams to accept multiple data stores, so must clearly articulate inherent trade-offs of each approach .

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

microservicesdatabase migrationbounded contextevolutionary architecturedata meshcontract designarchitectural quantumconnascence
Code Archaeology
Written by

Code Archaeology

Learn from the past to understand the present

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.