Building Evolutionary Architectures - Support Constant Change

My personal notes on the book Building Evolutionary Architectures - Support Constant Change.

  • An initial part of an architect’s job is to understand the business or domain requirements for a proposed solution.
  • The necessary analysis in architecture design and the inevitable clash of competing factors requires balance, but balancing the pros and cons of each architectural decision leads to the tradeoffs.
  • Software becomes harder to change over time.
  • Modern engineering practices invalidate that premise by making change less expensive by automating formerly manual
  • An evolutionary architecture supports guided, incremental change across multiple dimensions.
  • An architecture that allows small, incremental changes is easier to evolve because developers have a smaller scope of change.
  • As architecture evolves, we need mechanisms to evaluate how changes impact the important characteristics of the architecture and prevent degradation of those characteristics over time.
  • To build evolvable software systems, architects must think beyond just the technical architecture.
  • Dimensions of architecture — the parts of architecture that fit together in often orthogonal ways.
  • Each project has dimensions the architect must consider when thinking about evolution.
  • Conway introduced the notion that the social structures, particularly the communication paths between people, inevitably influence final product design.
  • Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. (Melvin Conway)
  • Every time a delegation is made and somebody’s scope of inquiry is narrowed, the class of design alternatives which can be effectively pursued is also narrowed. Stated another way, it’s hard for someone to change something.
  • Structure teams to look like your target architecture, and it will be easier to achieve it.
  • The definition of evolutionary architecture that we state here includes two critical characteristics: incremental and guided.
  • To build architectures that truly evolve, architects must support genuine change, not jury-rigged solutions. Going back to our biological metaphor, evolutionary is about the process of having a system that is fit for purpose and can survive the ever-changing environment in which it operates.
  • An evolutionary architecture consists of three primary aspects: incremental change, fitness functions, and appropriate coupling.
  • In software, fitness functions check that developers preserve important architectural characteristics.
  • An architectural fitness function provides an objective integrity assessment of some architectural characteristic(s).
  • Using a systemwide fitness function aids our understanding of necessary tradeoffs when individual elements of the fitness function conflict with each other.
  • Systemwide fitness functions allow architects to think about divergent concerns using the same unifying mechanism of fitness functions, capturing and preserving the important architectural characteristics.
  • Automated checks are preferable, some projects cannot automate all fitness functions.
  • Developers commonly express fitness functions using different kinds of mechanisms, such as tests or metrics.
  • Fitness functions can also be used to maintain coding standards.
  • The fitness functions collectively denote what matters to us in our architecture, allowing us to make the kinds of trade-off decisions that are both crucial and vexing during the development of software systems.
  • Atomic fitness functions run against a singular context and exercise one particular aspect of the architecture. An excellent example of an atomic fitness function is a unit test that verifies some architectural characteristic.
  • Holistic fitness functions run against a shared context and exercise a combination of architectural aspects such as security and scalability.
  • Triggered fitness functions run based on a particular event, such as a developer executing a unit test, a deployment pipeline running unit tests, or a QA person performing exploratory testing. This encompasses traditional testing such as unit, functional, behavior-driven development (BDD), and other tests developers.
  • Continual tests don’t run on a schedule, but instead execute constant verification of architectural aspect(s) such as transaction speed.
  • Static fitness functions have a fixed result, such as the binary pass/fail of a unit test. This type encompasses any fitness function that has a predefined desirable value: binary, a number range, set inclusion, and so on.
  • Dynamic fitness functions rely on a shifting definition based on extra context.
  • Static fitness functions have a fixed result, such as the binary pass/fail of a unit test. This type encompasses any fitness function that has a predefined desirable value.
  • Developers will execute most fitness functions within an automated context: continuous integration, deployment pipelines, and so on.
  • Some aspects of software resist automation. Sometimes, a critical dimension within a system, such as legal requirements, defies automation.
  • The path to better efficiency eliminates as many manual steps as possible, but many projects still require necessary manual procedures.
  • While most fitness functions trigger on change, architects may want to build a time component into assessing fitness.
  • Another common use of this type of fitness function is a break upon upgrade test.
  • While architects will define most fitness functions at project inception as they elucidate the characteristics of the architecture, some fitness functions will emerge during development of the system.
  • Some architectures have specific concerns, such as special security or regulatory requirements.
  • Many problem domains contain drivers that lead architects toward one or more set of important characteristics.
  • Teams should identify fitness functions as part of their initial understanding of the overall architecture concerns that their design must support.
  • Early identification of fitness functions help architects plan for breaking a large system into smaller systems, each dealing with a smaller set of fitness functions.
  • Keep knowledge of key and relevant fitness functions alive by posting the results of executing fitness functions somewhere visible or in a shared space so that developers remember to consider them in day-to-day coding.
  • Classifying fitness functions into categories helps prioritize design decisions.
  • A fitness function review is a meeting with key business and technical stakeholders with the goal of updating fitness functions to meet design goals.
  • We want our architecture to evolve in a guided way, so we place constraints on different aspects of the architecture to reign in undesirable evolutionary directions.
  • Evolutionary architecture implies incremental change, meaning the architecture should facilitate change in small increments.
  • Architectural monitoring — monitoring not only the services, but also the routes between services. When the operations group observes that no one has routed to a particular service within a given time interval, they automatically disintegrate that service from the ecosystem.
  • Architecture isn’t a static equation but rather a snapshot of an ongoing process.
  • The deployment pipeline also offers an ideal way to execute the fitness functions defined for an architecture.
  • The determining factor of atomicity comes down to what developers are testing and how broad are the results.
  • Hypothesis-driven development. Under this process, rather than gathering formal requirements and spending time and resources building features into applications, teams should leverage the scientific method instead.
  • Experiments should run long enough to yield significant results.
  • Modularity describes a logical grouping of related code.
  • Components are the physical packaging of modules. Modules imply logical grouping, while components imply physical partitioning.
  • An architectural quantum is an independently deployable component with high functional cohesion, which includes all the structural elements required for the system to function properly.
  • Creating common shared artifacts causes a host of problems, such as coupling, more difficult coordination, and increased complexity. The bounded context concept recognizes that each entity works best within a localized context.
  • Event-driven architectures (EDA) usually integrate several disparate systems together using message queues.
  • Architects shouldn’t choose an architecture without evaluating it against the real problems they must solve.
  • Data is an important dimension to consider when creating an evolvable architecture.
  • Evolutionary design in databases occurs when developers can build and evolve the structure of the database as requirements change over time. Database schemas are abstractions, similar to class hierarchies. As the underlying real world changes, those changes must be reflected in the abstractions developers and DBAs build. Otherwise, the abstractions gradually fall out of synchronization with the real world.
  • Developers must treat changes to database structure the same way they treat source code: tested, versioned, and incremental.
  • DBAs and developers can utilize the native facilities of databases to build evolvable systems.
  • Expand/contract is a subset of a pattern called parallel change, a broad pattern used to safely implement backward-incompatible changes to an interface.
  • Transactions are a special form of coupling because transactional behavior doesn’t appear in traditional technical architecture-centric tools.
  • Developers encounter transactions as coupling points when attempting to translate heavily transactional systems to inappropriate architectural patterns like microservices, which impose heavy decoupling burdens.
  • When DBAs don’t restructure the database, they’re not preserving a precious enterprise resource, they’re instead creating the concretized remains of every version of the schema, all overlaid upon one another via join tables.
  • Trying to keep every scrap of data couples the architecture to the past, forcing elaborate workarounds to make things operate successfully.
  • Refusing to refactor schemas or eliminate old data couples your architecture to the past, which is difficult to refactor.
  • The database can evolve right alongside the architecture as long as developers apply proper engineering practices such as continuous integration, source control, and so on.
  • Architects must treat data as a primary concern when building an evolutionary architecture.
  • Building new projects that handle unexpected change is easier if a developer chooses the correct architectural patterns and engineering practices to facilitate evolutionary architecture.
  • Adding evolvability to existing architectures depends on three factors: component coupling, engineering practice maturity, and developer ease in crafting fitness functions.
  • Many architects are tempted by the highly evolutionary microservices architecture as a target for migration, but this is often quite difficult, primarily because of existing coupling.
  • Transactional coupling is as real as coupling between classes, and just as insidious to eliminate when restructuring architecture.
  • One of the first tasks when untangling a code base is understanding how things are joined.
  • When decomposing a monolith, even if it is possible to break the classes into small enough pieces, breaking the transactional contexts into similar pieces may present an unsurmountable hurdle.
  • When decomposing a monolithic architecture, finding the correct service granularity is key.
  • When developers have identified the correct service partitioning, the next step is separation of the business layers from the UI.
  • The next step is service discovery, allowing services to find and call one another.
  • All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections. (Dave Wheeler and Kevlin Henney)
  • Immutable infrastructure follows our advice to remove needless variables. Building software systems that evolve means controlling as many unknown factors as possible.
  • Build just-in-time anticorruption layers to insulate against library changes.
  • Microservices architectures are designed to be share nothing architectures — each component is as decoupled as possible from other components, adhering to the bounded context principle.
  • Service templates are one common solution for ensuring consistency. These are preconfigured sets of common infrastructure libraries like service discovery, monitoring, logging, metrics, authentication/authorization, and so on.
  • Because frameworks are a fundamental part of applications, teams must be aggressive about pursuing updates.
  • Update framework dependencies aggressively; update libraries passively.
  • When versioning services, prefer internal versioning to numbering; support only two versions at a time.
  • Vendor king antipattern, an architecture built entirely around a vendor product that pathologically couples the organization to a tool.
  • By placing an external tool or framework at the heart of the architecture, developers severely restrict their ability to evolve in two key ways, both technically and from a business process standpoint.
  • Understand the fragile places within your complex technology stack and automate protections via fitness functions.
  • When coupling points impede evolution or other importance architectural characteristics, break the coupling by forking or duplication.
  • Resume-Driven Development pitfall — utilizing every framework and library possible to tout that knowledge on a resume.
  • In large organizations, we find the Goldilocks Governance model works well: pick three technology stacks for standardization — simple, intermediate, and complex — and allow individual service requirements to drive stack requirements. This gives teams the flexibility to choose a suitable technology stack while still providing the company some benefits of standards.
  • A strong correlation exists between the ability to release software and the ability to evolve that software design.
  • Continuous Delivery tracks cycle time: the elapsed time between the initiation and completion of a unit of work, which in this case is software development.
  • Speed of evolution is a function of cycle time; faster cycle time allows faster evolution.
  • Beware of long planning cycles that force architects into irreversible decisions and find ways to keep options open. Breaking large programs of work into smaller, early deliverables tests the feasibility of both the architectural choices and the development infrastructure.
  • Organizing teams around domains implicitly means organizing them around business capabilities.
  • In general, the more layers of indirection between a developer and their running code, the less connection they have to that code.
  • By thinking of software as a product, it shifts the company’s perspective in three ways. First, products live forever, unlike the lifespan of projects. Cross-functional teams (frequently based on the Inverse Conway Maneuver) stay associated with their product. Second, each product has an owner who advocates for its use within the ecosystem and manages things like requirements. Third, because the team is cross-functional, each role needed by the product is represented: business analyst, developers, QA, DBA, operations, and any other required roles. The real goal of shifting from a project to a product mentality concerns long-term company buy-in.
  • For any dimension in our architecture that requires protection from the side effects of evolution, we create fitness functions. A common practice in microservices architectures is the use of consumer-driven contracts, which are atomic integration architecture fitness functions.
  • The way firms organize and govern their own structures significantly influences the way that software is built and architected.
  • The role of the enterprise architect revolves around guidance and enterprise-wide fitness functions. Microservices architectures reflect this changing model.the role of the enterprise architect revolves around guidance and enterprise-wide fitness functions. Microservices architectures reflect this changing model.
  • Business people are often wary of ambitious IT projects, which sound like expensive replumbing exercises. However, many businesses find that many desirable capabilities have their basis in more evolutionary architectures.