In a previous post, I quoted a passage from Ruth Malan’s “Conway’s Law”:
The organizational divides are going to drive the true seams in the system.
The architecture of the system gets cemented in the forms of the teams that develop it. The system decomposition drives team ownership. Then the organizational lines of communication become reflected in the interfaces, with cleaner, better preserved interfaces along the lines where organizational distance increases. In small, co-located teams, short-cuts can be taken to optimize within the team. But each short-cut that introduces a dependency is like rebar in concrete–structurally efficient, but rigid. If the environment changes, demanding new lines to be drawn, the cost becomes clear. The architecture is hard to adapt.
Those eight sentences contain extremely important considerations for application architecture, particularly if you are considering a microservice architecture.
Microservice architectures, although they have gained currency due to a recent post by James Lewis and Martin Fowler, are not a new concept. Lewis and Fowler note a pedigree going back as far as 2011, and in the opinion of Steve Jones, “Microservices is SOA, for those who know what SOA is”. Roger Sessions published a 3 part series in the fall of 2012 detailing his concept of a “Snowman Architecture” (Part 1, Part 2, Part 3), which is fundamentally similar to the Lewis and Fowler model.
According to Lewis and Fowler, the characteristics of a microservice architecture are:
- Componentization via Services: Services are independently deployable, encapsulated, out-of-process components that can be composed into a system of systems with their interactions defined via service contracts (APIs).
- Organized around Business Capabilities: Services are vertical slices of technologies dedicated to particular business capability, ideally with coinciding service and team boundaries.
- Products not Projects: The team should own the service over its entire lifecycle, both development and support. (*)
- Smart endpoints and dumb pipes: Logic should reside within the services rather than the communications mechanisms that tie them together (such as an ESB).
- Decentralized Governance: Because of the partitioning inherent in this style, teams are not forced into one set of standards for their internal implementations. (*)
- Decentralized Data Management: Because of the partitioning inherent in this style, data will be distributed across multiple services with a tendency towards eventual consistency.
- Infrastructure Automation: Lewis and Fowler recommend automated testing, automated deployment and automation of infrastructure. (*)
- Design for failure: The distributed, partitioned nature of microservice architectures increases the need for system monitoring and resilience.
- Evolutionary Design: Smaller, modular services enable agile, controllable change.
* Note: I consider these to more in the realm of process than architectural style. That being said, I also consider most of these to be good practice for teams using this particular architectural style.
Composing a system of independent, autonomous components, as opposed to a monolith, allows them to be partitioned by business capability as well as be developed and maintained by a permanent team. This transforms Conway’s Law from an observation about the nature of software systems into a principle that can be used to improve the structure of those systems (“The organizational divides are going to drive the true seams in the system.”). The social system architecture will influence the software system architecture in any event, the question is whether that influence will be intentional or not.
The microservice architectural style has been described as “applying many of the principles of SOLID at an architectural level”. As opposed to monolithic architectures, it enables a high degree of flexibility, cohesion and composability (when done well). It is not, however, a free lunch. Distributed applications are more complex than monolithic ones in terms of development, operations, and governance. Accessing components that are out-of-process, not to mention over the network, is not the same as dealing with in-process components. Network latency becomes more and more important as applications become more distributed. Asynchronous operations can ease some of that pain, but at the cost increasing the complexity of the system.
Overly granular services can be another pitfall with this architectural style. In “Services, Microservices, Nanoservices – oh my!”, Arnon Rotem-Gal-Oz discusses the nanoservice anti-pattern where “…overhead (communications, maintenance, and so on) outweighs its utility”. To expand on his “so on”, security is one of those overhead aspects that can become more burdensome as the application becomes more distributed. At the very minimum, authorization, if the application is entirely internal, is more complicated than it would be with a monolith. External users and/or dependencies increase the scope of that security overhead, potentially adding multiple methods of authentication, encryption, etc.
Data architecture complexity is another consideration. It should be borne in mind that each microservice is its own self-contained system with its own data store(s). This adds fragmentation and most likely, redundancy, to the enterprise’s data architecture. Redundancy is not bad per se (e.g. caching), but it does require more in terms of governance to identify what source is authoritative and what others are not. Likewise, fragmentation is not always poor design (think bounded contexts with shared concepts), but it may require more effort to consolidate all the data for a given entity for reporting purposes. One might be tempted to “cheat” and go the shared database route, but that would be a mistake.
In a post on application boundaries, Martin Fowler observed that “essentially applications are social constructions…drawn primarily by human inter-relationships and politics rather than technical and functional considerations”. Realizing that, we can use the organizational dynamics to shape the components of our systems of systems in a way that helps, rather than harms the technical and functional considerations. The critical things to keep in mind are balance and intent.