The road to microservices is dark and full of terrors. Do you really need what microservices offer? Are you really ready to lose the comfort of monolithic architecture? This article tries to summarize my view on the “microservices compromise”, what it requires and what it implies, beyond splitting a codebase into small pieces.
A map of the road to microservices
Large, coupled, but simple patterns, close to what is done in monoliths
- Small, decoupled but more complex ones, closer to microservice-friendly approaches
- The options marked with asterisks (*) correspond to a reasonable implementation of a “Modular monolith”, a local optimum of the compromises on decoupling, resilience, scalability, and scalability before having to pay the price of microservices architectures
- Microservices can bring benefits that are out of reach for even the most perfect monolith
- Start small, with a monolith to learn as much as possible about your domain and sharpen your skills and tools
Deployment
In which format are the services packaged and deployed?
- Platform-specific
- The service is compiled and packaged in a format adapted to the target deployment platform (e.g., RPM package), runnable on multiple deployment platforms.
- Are services deployed independently or all-at-once?
Organization
How are teams organized around the codebase boundaries?
- Monolithic
- A large, single team handles all the work
- Transversal
- Teams are organized around arbitrary technical boundaries (e.g., frontend, backend and ops)
- Feature teams – small, autonomous teams deliver end-to-end features
- Product teams – focused on their parts of the system
- Of course, there will be compromises to make regarding organization and processes
Integration: do services share the persistence mechanism?
Shared: a shared schema in a single database
- Isolated: one schema per service, one database, one service
- Independent: best stack chosen for each use case; probably some common guidelines
- Synchronous: synchronous calls over the network, for instance blocking requests through REST APIs
- asynchronous: asynchronous event-driven communication through a message bus
Technology stacks
What constraints are imposed on the choice of technologies (languages, frameworks, persistence…)?
Technology heterogeneity
In a monolithic architecture, the choice of language and framework is made once, usually at the very beginning of the project.
- Librairies tend to accumulate as new requirements arrive or because team members change, and so do their preferences in tooling.
- In microservices architecture, you can choose the right stack (language, frameworks, librairies, storage…) at the right moment, on a use case (microservice) basis.
How are logging and monitoring implemented?
Pattern
- Embedded
- Standard
- Despite custom implementations in each service, common guidelines about format and exposition mechanism are enforced
- External
- Logging and monitoring concerns are externalized to the infrastructure layer (e.g., Kubernetes)
Resilience and Scalability
Monoliths offer “all-or-nothing” availability. If anything goes wrong, the whole application is gone. With microservices, a failure only affects a few parts of the system, leaving most of the services running.
- Microservices allow the opportunity to react to partial failure and offer a degraded service to users instead of a global error message.
The technology zoo
Having multiple technologies in production means having to maintain a pool of experts for each of them, in each department.
- Too many constraints, and you don’t get the advertised benefits of technology heterogeneity; too few, and the fun at the technology zoo will not last long.
Small, focused and decoupled services
Microservices architectures are all about splitting software in multiple services and keeping them small
- When done right, this code split follows the lines of the various concerns handled by the application
- Each service is focused on a small set of features and does not have to care too much about the rest of the application logic
Authentication
How is authentication implemented?
Microservices, a definition
In monolithic architectures, all features are implemented in a single codebase, with no visible modularization
- Objects reference each other in complex and intricate ways, making the code hard to read, understand, and maintain
- The next logical step is to split this large codebase into multiple parts
- Microservices are “just” one step further, splitting again each of the codebases into smaller parts
Failure Handling
Pattern Description
Build
What is the project versioned, built and packaged?
- Monolithic
- A single, large codebase in a single repository, all built & packaged as one.
- Monorepo
- Multiple services in one repository, built as one, but packaged independently.
- Advanced monorepo, multirepo, or multiirepo.
- Caching mechanisms allow for only building changed services.
Transversal Teams and Change Management
If your entire enterprise is organized around technical boundaries, massive efforts will be required to evolve to a lot of small, focused, multidisciplinary teams.
- To reduce the coupling, transversal teams should really act as communities of practices (rather than as a pool of resources to deliver features) and be located as close to the product teams as possible.
Small and coupled
In monoliths, coupling can stay unnoticed for quite some time, before it eventually surfaces affecting development velocity, ease of maintenance, or performance. In microservices, there is nowhere to hide.
- With parts of the model distributed across the network, the cost of coupling is orders of magnitude higher, and will almost instantly result in maintenance burden and performance bottlenecks.
Promise and compromise
The only real guarantee of microservices architectures is to end up with a lot of small parts, lose transactional consistency, raise failure rates and challenge your organization.
- Decoupling, resilience, scaling, tech heterogeneity and small focused teams will not come for free. They require tremendous company-wide effort, from the leadership to the infrastructure teams to the people, processes and tools.
Testing What is the adopted testing strategy?
Pattern: Hope-based
- Manual: Tests are mostly manual, usually made during test campaigns before important releases
- Mostly end-to-end: tests at the UI or API level, consider the system as a whole
- Expert: A balance between many unit tests, some integration tests, and few end to-end (smoke) tests
- Sensei: Expert mode with consumer-driven contract testing between dependent services to detect breaking changes
Frontend
Is the frontend also split into modules or services? If so, with what granularity?
Walking the road
There will be many paths to explore, and many directions to choose from.
- This chapter is a short guide for some of the many crossroads you should prepare yourself to encounter
- For a variety of topics, mostly technical for now, we will propose a spectrum of practices, tools and patterns, to compromise over.
Cascading failures and stateful services
Microservices bring a different equation to the table
- Because they are composed of more moving parts (multiple hosts, multiple services, multiple network links between them), they are statistically more likely to fail
- Pushed to a certain scale, a microservice architecture is, at any point in time, statistically more than one failing component than to be entirely healthy
- microservices actually bring more failure, not less, so it is important to keep them localized and compensate for them
Architecture
Pattern Description
- Monolith: A single codebase with no further modularization
- Modular: A codebase split into modules with few dependencies
- Large services: Multiple large services with each its own independent codebase (10-20 features or one DDD Bounded Context)
- Microservices: Microservices, Microservices + Small services
- Aggregates: Many small DDD “Aggregates” (1-10 classes each) with loose references and duplication instead of dependencies
Consistency What data consistency guarantees does the overall system offer?
Pattern – transactional
Small, autonomous teams
As a software grows, the size of its supporting team tends to grow accordingly, often with the incorrect assumption that more people implies more features delivered
- Microservices offer the opportunity to naturally split the work between small teams, each focused and knowledgeable about their microservices and the few they interact with
The untold story of the compromise
There is no silver bullet and no free lunch, only compromises