“Alright, could you please tell us a few benefits and drawbacks of working with microservices?” - starts the usual tech interview question, and the traditional answer often follows: “They’re smaller; therefore, they’re easier to develop and to troubleshoot. There’s better scaling control on parts of the system. They can be written in different programming languages. They can be deployed individually.”
In this series of articles about microservices, we’ll go through the widespread notions of what it feels like to work with microservices. We’ll attempt to explain under what circumstances some - arguably a little blunt - misconceptions hold true. We’ll do this in order to speed up your journey and personal hype curve of microservices, and so you can better understand what dynamics and challenges to prepare for.
Make sure to subscribe to our channels to be informed about subsequent articles in the series.
Belief: “Microservices are smaller, so they’re easier to develop.”
Reality: "True, if you can keep good control over functional boundaries."
Let’s think for a second: what makes development hard? It’s usually complexity. So comes the following question: what makes a software system complex? It’s usually the components being coupled too tightly together in a way that introducing a change in one particular area has implications in conceptually distant areas that are hard to foresee at the time of implementing the change. How can you avoid this? That’s where SOLID principles guide us to the right way but applied to services:
- Let’s assign services a single responsibility, and let’s consciously manage the conceptual boundaries of the services: for each concept, understand where its lifecycle is best managed and know how they’re going to be referenced from other places.
- Let’s structure the entire system and the individual services in a way that they’re open for reasonable and foreseeable extensions. Still, they’re closed for undesired modifications that adversely affect some sort of key conceptual behavior.
- The Liskov substitution principle instructs us to think in abstractions in all places: all concepts should be treated as abstract, but at a particular abstraction level, the constraints and expectations have to be very concrete (e.g. “the customer service is the authoritative data source for all core customer data”); specializations have to respect these constraints and expectations.
- The interfaces within the system have to be designed in such a way that they know the least about the other services they have to operate with, but the expectations against them have to be stated by them clearly.
- Concepts and mechanisms have to depend on abstractions, it almost comes as a natural consequence of the above principles.
While the SOLID principles are usually thought of as object-oriented system design principles, they’re beneficial as service-level design principles. To reach a conclusion on whether microservices are easier to develop and somehow inherently less complex, we can say that the more our overall system design respects SOLID principles at the service level, the easier it will be to develop individual services.
Let’s notice that you can only develop software this way if you have a team of system architects (each managing a set of services they’re responsible for) who understand the conceptual boundaries and interactions between the individual services and who can effectively communicate with each other and with the members of the teams they represent.
Last but not least, on SOLID principles, use your common sense. Let them be your guides, not ones that enforce unnatural and inconvenient constraints. Check out Dan North’s thoughts on SOLID.
Let’s move on to another typically object-oriented concept.
Belief: “Microservices allow better encapsulation of concepts.”
Reality: "True, as long as you can keep up with delivering what other teams need."
Some teams proceed more quickly than others for one reason or another. Maybe they’re better staffed because the project they work on is more strategic to the company. Whatever the reason is, there will always be services whose teams won’t be able to deliver new functionality as quickly as other teams would need. Now, here’s an advantage of a monolith system: if you as a developer want to implement a new capability (e.g. a feature), you typically implement everything yourself, and technically you do not depend on anyone else. That’s not the case with microservices: assuming you keep teams separate and responsible for just their own services, you either wait until they deliver the required capability for you and try to influence their priorities, or you resort to breaking encapsulation and connecting to their database directly, or asking them to put some quick hack in place so that you’re unblocked. No one wants to block others and become the usual culprit for delivery problems, so while these workarounds are technically very unappealing, they’re unfortunately very likely to happen.
Suppose it’s possible to rotate people between projects regularly, so they’ll understand the code of other services. In that case, they’ll be able to contribute in a way that the services can maintain a reasonable level of encapsulation.
But wait, aren’t teams supposed to be independent when you’re going with microservices?
Belief: “Teams can work independently on microservices.”
Reality: "True, but teams have to collaborate, communicate clearly and assertively and maintain a good mutual understanding of each other's technical stack and practical capabilities."
It’s very easy to see how naturally teams developing microservices exhibit Conway’s law. Having zero understanding of how the directly integrated systems work and not knowing the teams they’re developed by is a recipe for failure. Yes, those capabilities which can be independently changed for the particular service because there are no external interactions can be independently worked on, but isn’t that true for monoliths as well? It typically is. If it’s just a section on the frontend or it’s a particular backend function that is well-localized, it shouldn’t feel too different to work on that independently from what it feels like to work on a separate microservice - compile and startup times aside, of course.
Again, the key is clear boundaries, clear definition of concepts and interfaces at the boundaries and effective communication between teams integrating services.
To be continued… Follow us on Twitter or subscribe to our newsletter so you don’t miss part 2 of this article.