Building Microservices: Designing Fine-Grained Systems
Rate it:
Read between April 25 - November 21, 2021
22%
Flag icon
We’ve spoken before (probably ad nauseum by this point) about our services being fashioned around bounded contexts. Our customer microservice owns all logic associated with behavior in this context.
22%
Flag icon
One of the acronyms we developers hear a lot is DRY: don’t repeat yourself. Though its definition is sometimes simplified as trying to avoid duplicating code, DRY more accurately means that we want to avoid duplicating our system behavior and knowledge.
22%
Flag icon
DRY is what leads us to create code that can be reused. We pull duplicated code into abstractions that we can then call from multiple places. Perhaps we go as far as making a shared library that we can use everywhere! This approach, however, can be deceptively dangerous in a microservice architecture.
22%
Flag icon
One of the things we want to avoid at all costs is overly coupling a microservice and consumers such that any small change to the microservice itself can cause unnecessary changes to the consumer. Sometimes, however, the use of shared code can create this very coupling.
22%
Flag icon
My general rule of thumb: don’t violate DRY within a microservice, but be relaxed about violating DRY across all services.
22%
Flag icon
A model for client libraries I like is the one for Amazon Web Services (AWS). The underlying SOAP or REST web service calls can be made directly, but everyone ends up using just one of the various software development kits (SDKs) that exist, which provide abstractions over the underlying API. These SDKs, though, are written by the community or AWS people other than those who work on the API itself. This degree of separation seems to work, and avoids some of the pitfalls of client libraries. Part of the reason this works so well is that the client is in charge of when the upgrade happens. If ...more
23%
Flag icon
However, according to at least one person at Netflix, over time this has led to a degree of coupling between client and server that has been problematic.
24%
Flag icon
“Be conservative in what you do, be liberal in what you accept from others.”
24%
Flag icon
With semantic versioning, each version number is in the form MAJOR.MINOR.PATCH. When the MAJOR number increments, it means that backward incompatible changes have been made. When MINOR increments, new functionality has been added that should be backward compatible. Finally, a change to PATCH states that bug fixes have been made to existing functionality.
24%
Flag icon
One approach I have used successfully to handle this is to coexist both the old and new interfaces in the same running service. So if we want to release a breaking change, we deploy a new version of the service that exposes both the old and new versions of the endpoint.
26%
Flag icon
This pattern is sometimes referred to as backends for frontends (BFFs).
27%
Flag icon
My clients often struggle with the question “Should I build, or should I buy?” In general, the advice I and my colleagues give when having this conversation with the average enterprise organization boils down to “Build if it is unique to what you do, and can be considered a strategic asset; buy if your use of the tool isn’t that special.”
27%
Flag icon
The temptation — and often the selling point of the CMS — is that you can customize the CMS to pull in all this special content and display it to the outside world. However, the development environment for the average CMS is terrible.
28%
Flag icon
Avoid database integration at all costs.
28%
Flag icon
Understand the trade-offs between REST and RPC, but strongly consider REST as a good starting point for request/response integration.
28%
Flag icon
Prefer choreography over orc...
This highlight has been truncated due to consecutive passage length restrictions.
28%
Flag icon
Avoid breaking changes and the need to version by understanding Postel’s Law and...
This highlight has been truncated due to consecutive passage length restrictions.
28%
Flag icon
Think of user interfaces as composit...
This highlight has been truncated due to consecutive passage length restrictions.
29%
Flag icon
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.
29%
Flag icon
So what makes a good seam? Well, as we discussed previously, bounded contexts make excellent seams, because by definition they represent cohesive and yet loosely coupled boundaries in an organization. So the first step is to start identifying these boundaries in our code.
29%
Flag icon
The first thing to do is to create packages representing these contexts, and then move the existing code into them.
29%
Flag icon
Our code should represent our organization, so our packages representing the bounded contexts in our organization should interact in the same way the real-life organizational groups in our domain interact.
29%
Flag icon
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, and we have a lot of tools at our disposal to track our progress.
29%
Flag icon
Pace of Change Perhaps we know that we have a load of changes coming up soon in how we manage inventory. If we split out the warehouse seam as a service now, we could change that service faster, as it is a separate autonomous unit.
29%
Flag icon
Team Structure MusicCorp’s delivery team is actually split across two geographical regions. One team is in London, the other in Hawaii (some people have it easy!). It
29%
Flag icon
would be great if we could split out the code that the Hawaii team works on the most, so it can take full ownership.
29%
Flag icon
Security MusicCorp has had a security audit, and has decided to tighten up its protection of sensitive information. Currently, all of this is handled by the finance-related code. If we split this service out, we can provide additional protections to this individual service in terms of monitoring, protection of data at transit, and protection of data at rest
29%
Flag icon
Technology The team looking after our recommendation system has been spiking out some new algorithms using a logic programming library in the language Clojure. The team thinks this could benefit our customers by improving what we offer them. If we could split out the recommendation code into a separate service, it would be easy to consider building an alternative implementation that we could test against.
29%
Flag icon
Tangled Dependencies The other point to consider when you’ve identified a couple of seams to separate is how entangled that code is with the rest of the system.
30%
Flag icon
A common practice is to have a repository layer, backed by some sort of framework like Hibernate, to bind your code to the database, making it easy to map objects or data structures to and from the database.
31%
Flag icon
Once we are satisfied that the DB separation makes sense, we can then think about splitting out the application code into two services.
32%
Flag icon
In many ways, this is another form of what is called eventual consistency. Rather than using a transactional boundary to ensure that the system is in a consistent state when the transaction completes, instead we accept that the system will get itself into a consistent state at some point in the future.
32%
Flag icon
What we have to do is issue a compensating transaction, kicking off a new transaction to wind back what just happened.
32%
Flag icon
But what happens if our compensating transaction fails? It’s certainly possible. Then we’d have an order in the order table with no matching pick instruction. In this situation, you’d either need to retry the compensating transaction, or allow some backend process to clean up the inconsistency later on. This could be something as simple as a maintenance screen that admin staff had access to, or an automated process.
32%
Flag icon
An alternative to manually orchestrating compensating transactions is to use a distributed transaction.
32%
Flag icon
The most common algorithm for handling distributed transactions — especially short-lived transactions, as in the case of handling our customer order — is to use a two-phase commit.
33%
Flag icon
When you encounter business operations that currently occur within a single transaction, ask yourself if they really need to. Can they happen in different, local transactions, and rely on the concept of eventual consistency? These systems are much easier to build and scale (we’ll discuss this more in Chapter 11).
33%
Flag icon
If you do encounter state that really, really wants to be kept consistent, do everything you can to avoid splitting it up in the first place. Try really hard.
33%
Flag icon
A change in architecture as fundamental as moving to a microservices architecture will cause a lot of disruption, but it doesn’t mean we have to abandon everything we do. The audience of our reporting systems are users like any other, and we need to consider their needs.
34%
Flag icon
the nature of reporting is often that we access the long tail of data. This means that we may well request resources that no one else has requested before (or at least not for a sufficiently long time), resulting in a potentially expensive cache miss. You could resolve this by exposing batch APIs to make reporting easier. For example, our customer service could allow you to pass a list of customer IDs to it to retrieve them in batches, or may even expose an interface that lets you page through all the customers. A more extreme version of this is to model the batch request as a resource in its ...more
34%
Flag icon
BatchRequest, perhaps passing in a location where a file can be placed with all the data. The customer service would return an HTTP 202 response code, indicating that the request was accepted but has not yet been processed. The calling system could then poll the resource waiting until it retrieves a 201 Created status, indicating that the request has been fulfilled, and then the calling system could go and fetch the data. This would allow potentially large data files to be exported without the overhead of being sent over HTTP; instead, the system could simply save a CSV file to a shared ...more
34%
Flag icon
We try to reduce the problems with coupling to the service’s schema by having the same team that manages the service also manage the pump. I would suggest, in fact, that you version-control these together, and have builds of the data pump created as an additional artifact as part of the build of the service itself, with the assumption that whenever you deploy one of them, you deploy them both.
35%
Flag icon
The main downsides to this approach are that all the required information must be broadcast as events, and it may not scale as well as a data pump for larger volumes of data that has the benefit of operating directly at the database level. Nonetheless, the looser coupling and fresher data available via such an approach makes it strongly worth considering if you are already exposing the appropriate events.
36%
Flag icon
I really like Jez Humble’s three questions he asks people to test if they really understand what CI is about: Do you check in to mainline once per day?
36%
Flag icon
You need to make sure your code integrates. If you don’t check your code together with everyone else’s changes frequently, you end up making future integration harder. Even if you are using short-lived branches to manage changes, integrate as frequently as you can into a single mainline branch. Do you have a suite of tests to validate your changes? Without tests, we just know that syntactically our integration has worked, but we don’t know if we have broken the behavior of the system. CI without some verification that our code behaves as expected isn’t CI. When the build is broken, is it the ...more
36%
Flag icon
A passing green build means our changes have safely been integrated. A red build means the last change possibly did not integrate. You need to stop all further check-ins that aren’t involved in fixing the builds to get it passing again. If you let more changes pile up, the time it takes to fix the build will increase drastically. I’ve worked with teams where the bui...
This highlight has been truncated due to consecutive passage length restrictions.
37%
Flag icon
This impacts our cycle time, the speed at which we can move a single change from development to live.
37%
Flag icon
With well-defined structure, you can easily map the builds to certain parts of the source tree. In general, I am not a fan of this approach, as this model can be a mixed blessing. On the one hand, my check-in/check-out process can be simpler as I have only one repository to worry about. On the other hand, it becomes very easy to get into the habit of checking in source code for multiple services at once, which can make it equally easy to slip into making changes that couple services together. I would greatly prefer this approach, however, over having a single build for multiple services.
37%
Flag icon
Here each microservice has its own source code repository, mapped to its own CI build. When making a change, I run only the build and tests I need to. I get a single artifact to deploy. Alignment to team ownership is more clear too. If you own the service, you own the repository and the build.
37%
Flag icon
continuous delivery is the approach whereby we get constant feedback on the production readiness of each and every check-in, and furthermore treat each and every check-in as a release candidate.