I come from a "monolith" background. In fact, monolithic applications are basically the only kind of applications that I've ever built. InVision App is a monolithic application (more or less). But, we're slowly trying to change that. And when I say "we", I really mean other people on the team; because I have no idea what I'm doing when it comes to distributed systems. That said, I'm extremely eager to learn. I understand that this is the future and I either need to get on board and learn all the things; or I need to find something else to do. So, after months of dancing around the topic, I finally read Building Microservices - Designing Fine-Grained Systems by Sam Newman.
| || || |
| || |
| || || |
Building Microservices is packed with information, covering all aspects of the software development lifecycle. Everything from ideation to implementation; from greenfield projects to brownfield refactoring; from team design to tooling; from testing, building, deploying, and scaling to monitoring, security, and intrusion detection and prevention. A lot of it pertained directly to me, as a product engineer. But, a lot of it was definitely much more oriented towards platform and site-reliability engineers. Not to say that any of it was irrelevant; on the contrary, some of it just went farther outside my personal experience and skill-set.
As a company that is currently transforming 5 years of legacy monolith code into a distributed system, the parts of the book that I found most riveting were the parts that pertained to identifying and isolating bounded contexts. When it comes to refactoring an existing app, Newman seems to advocate finding those seams in the existing code first rather than trying to start with microservices.
CAVEAT: Since I am very new to this, please forgive me for misunderstanding or inadvertently misconstruing approaches that Sam Newman has outlined in the book.
In his book Working Effectively With Legacy Code, Michael Feathers defined the concept of a "seam" -- that is, a portion of the code that can be treated in isolation and worked on without impacting the rest of the codebase. We also want to identify seams. But rather than finding them for the purpose of cleaning up our codebase, we want to identify seams that can become service boundaries....
The first thing to do is to create packages representing these contexts, and then move the existing code into them... Over time, we start to see what code fits well, and what code is left over and doesn't really fit anywhere. This remaining code will often identify bounded contexts we might have missed....
This process could take an afternoon on a small codebase, or several weeks or months when you're dealing with millions of lines of code. You may not need to sort all code into domain-oriented packages before splitting out your first service, and indeed it can be more valuable to concentrate your effort in one place. There is no need for this to be a big-bang approach. It is something that can be done bit by bit, day by day. (Pages 131-133)
In fact, this is something Newman even talks about at the database level as well:
I would actually recommend that you split out the schema but keep the service together before splitting the application code out into separate microservices... (Page 145)
One of the driving forces behind this approach is cost. When you start moving to a microservices approach, mistakes can become much more expensive if the boundaries within the application are not well defined. A poorly decoupled system means that changes in one service can require changes in many other services, eliminating many of the benefits touted by microservices.
In general, microservices should cleanly align to bounded contexts. Once you become very proficient, you may decide to skip the step of keeping the bounded context modeled as a module within a more monolithic system, and jump straight for a separate service. When starting out, however, keep a new system on the more monolithic side; getting service boundaries wrong can be costly, so waiting for things to stabilize as you get to grips with a new domain is sensible. (Page 60)
Newman has even seem situations in which poorly defined microservices were merged back into a monolith while the team figured out how to proceed more appropriately.
Eventually the team merged the services back into one monolithic system, giving them time to better understand where the boundaries should exist. A year later, the team was then able to split the monolithic system apart into microservices, whose boundaries proved to be much more stable. This is far from the only example of this situation I have seen. Prematurely decomposing a system into microservices can be costly, especially if you are new to the domain. In many ways, having an existing codebase you want to decompose into microservices is much easier that trying to go to microservices from the beginning. (Page 61)
These seams, in the application, can often become reflective of the organizational structure of the business itself. In the past, this phenomenon - known as Conway's Law - has always been described to me as a negative; meaning, it was a mistake that software engineers made in their architecture. But, Newman sort of flips that on its head, talking about Conway's Law as a strength - as a natural way to align team design and microservice design with domain responsibilities.
As mentioned before, we look to draw our service boundaries around bounded contextes. It therefore follows that we would like our teams aligned along bounded contexts too. This has multiple benefits. First, a team will find it easier to grasp domain concepts within a bounded context, as they are interrelated. Second, services within a bounded context are more likely to be services that talk to each other, making system design and release coordination easier. Finally, in terms of how the delivery team interacts with the business stakeholders, it becomes easier for the team to create good relationships with the one or two experts in that area. (Page 305)
At work, as we evolve our architecture, teams are constantly working on different things. So, it becomes next to impossible to know what anyone is working on - or who is responsible for what functionality - without referring to the org-chart, which changes on a daily basis. Having teams aligned with domain functionality would be amazing.
I could keep going on about the goodness in this book (it's a large book); but, one final thing that I found fascinating was this concept of "backends for frontends" (BFFs). This is basically a take on the API Gateway concept that, at least for me, bridges the gap between the philosophy of microservices and the very practical need of building user-facing applications.
A common solution to the problem of chatty interfaces with backend services, or the need to vary content for different types of devices, is to have a server-side aggregation end-point, or API Gateway. This can marshall multiple backend calls, vary and aggregate content if needed for different devices, and serve it up....
The problem that can occur is that normally we'll have one giant layer for all our services. Theis leads to everything being thrown in together, and suddenly we start to lose isolation of our various user interfaces, limiting our ability to release them independently. A model I prefer and that I've seen work well is to restrict the use of these backends to one specific user interface or application...
This pattern is sometimes referred to as "backends for frontends (BFFs)". (Page 120-121)
As someone who knows very little about microservices and distributed systems, I found this book to be incredibly informative. But, as someone who "thinks in code", I did wish that there were more code examples. That said, I think I am simply viewing code as a "safety blanket" that let's me avoid learning the overarching philosophies of system design. If I am truly honest with myself, I think code examples would have detracted from the experience of the book, not enhanced it.
Reading a book like Building Microservices by Sam Newman is not a one-and-done kind of thing. Understanding an idea and internalizing an idea are two different things. This will definitely be a book that I have to read [parts of] several times before my mental model can really start to converge on anything meaningful. That said, this was a great book and I can definitely recommend it.