A Singapore Government Agency Website

Evolutionary Architecture

Evolutionary architecture is an approach to designing software systems that acknowledges and embraces change as a constant. Rather than aiming to define an ideal architecture up front that may become outdated or misaligned as requirements evolve, evolutionary architecture enables systems to grow and adapt over time without significant rework. It supports incremental change, allowing teams to make ongoing improvements while preserving architectural integrity.

A key characteristic of evolutionary architecture is that it prioritises flexibility and adaptability over rigidity. It does this by favouring loosely coupled components, clear boundaries (such as those defined by domain-driven design and bounded contexts), and principles like high cohesion and low coupling. These qualities make it easier to modify or replace parts of the system without impacting the whole.

Importantly, evolutionary architecture is not just about technical flexibility — it’s also a way to support business agility. It allows organisations to respond more quickly to public demands, user feedback, or regulatory changes, as the underlying systems are built to evolve rather than resist change. This is particularly useful when using patterns like modular monoliths or microservices, where bounded domains and decoupled components can be scaled or restructured as needed.

Architectural Fitness Functions

To guide this evolution, teams often define architectural fitness functions — automated checks that continuously validate whether the architecture meets desired constraints and quality attributes (like performance, security, or modularity). These functions are analogous to unit tests for code, and provide fast feedback and prevent architectural drift, enabling change without loss of control.

Enforcing Module Boundaries

One common use case for architectural fitness functions is ensuring that module or layer boundaries are respected. For example, in a layered architecture, a fitness function can check that the UI layer does not access the data access layer directly, bypassing the domain layer. This can be implemented using static code analysis tools that inspect dependencies and fail builds if violations are detected. It helps prevent unintentional coupling and keeps the architecture aligned with its intended shape.

Ensuring Microservice Independence

In a microservices architecture, services should be independently deployable. A fitness function might verify that there are no unexpected compile-time or runtime dependencies between services — for instance, checking that no shared databases are used across services, or that services communicate only through APIs. This ensures that one team’s deployment doesn’t inadvertently break another team’s service, preserving the autonomy of each service.

Monitoring Performance Expectations

Performance-based fitness functions can be used to ensure that architectural qualities like latency and throughput remain within acceptable limits. For example, automated performance tests might run in a CI pipeline to verify that API response times do not degrade beyond a defined threshold. If a regression occurs, the build fails, prompting investigation before code is merged. This allows performance expectations to be continuously enforced and not just tested late in the development cycle.

Validating Resilience and Fault Tolerance

Fitness functions can also be used to validate resilience patterns like timeouts, retries, circuit breakers, or failovers. For instance, chaos engineering tools can introduce faults into the system (such as service failures or network latency), and a fitness function can check whether the system still handles requests gracefully. This ensures that resiliency mechanisms remain effective as the system evolves and complexity increases.

Maintaining Security Constraints

Security-related fitness functions can enforce constraints like preventing the use of deprecated cryptographic algorithms, verifying that all APIs require authentication, or ensuring data is encrypted at rest and in transit. These checks help maintain the security posture of the architecture over time, especially as new features are added or libraries are updated.

Continuous Alignment with Architectural Goals

Architectural fitness functions are most powerful when integrated into the CI/CD pipeline, providing continuous feedback to developers. Rather than relying on occasional manual reviews or documentation audits, teams receive real-time indicators when architectural constraints are violated. This supports sustainable evolution by ensuring architectural quality is preserved as the system grows and changes.

By embedding architectural checks into the development workflow, teams can move faster with confidence, knowing that critical architectural qualities are being monitored and protected — even as the software evolves to meet new needs.

Chat with me!
Bot Launcher