More on this book
Community
Kindle Notes & Highlights
by
Sam Newman
Read between
August 6 - September 13, 2022
Microservices are independently releasable services that are modeled around a business domain.
From the outside, a single microservice is treated as a black box. It hosts business functionality on one or more network endpoints (for example, a queue or a REST API, as shown in Figure 1-1), over whatever protocols are most appropriate.
Independent deployability is the idea that we can make
a change to a microservice, deploy it, and release that change to our users, without having to deploy any other microservices.
If you take only one thing from this book and from the concept of microservices in general, it should be this: ensure that you embrace the concept of independent deployability of your microservices. Get into the habit of deploying and releasing changes to a single microservice into production without having to deploy anything else. From this, many good things will follow.
To ensure independent deployability, we need to make sure our microservices are loosely coupled: we must be able to change one service without having to change anything else. This means we need explicit, well-defined, and stable contracts between services.
By modeling services around business domains, we can make it easier to roll out new functionality and to recombine microservices in different ways to deliver new functionality to our users.
Arguably, with microservices we have made a decision to prioritize high cohesion of business functionality over high cohesion of technical functionality.
Don’t share databases unless you really need to. And even then do everything you can to avoid it. In my opinion, sharing databases is one of the worst things you can do if you’re trying to achieve independent deployability.
In my experience, a distributed monolith has all the disadvantages of a distributed system, and the disadvantages of a single-process monolith, without having enough of the upsides of either.
Understanding the tools that are available to help you get the most out of this architecture is going to be a key part of making any implementation of microservices a success.
I strongly advocate the implementation of a log aggregation system as a prerequisite for adopting a microservice architecture.
Be cautious about taking on too much new technology when you start off with microservices. That said, a log aggregation tool is so essential that you should consider it a prerequisite for adopting microservices.
These systems allow you to collect and aggregate logs from across all your services, providing you a central place from which logs can be analyzed, and even...
This highlight has been truncated due to consecutive passage length restrictions.
Microservices allow us to better align our architecture to our organization, helping us minimize the number of people working on any one codebase to hit the sweet spot of team size and productivity.
I feel that microservice architectures are often a bad choice for brand-new products or startups.
By keeping the number of assumptions small, it is easier to ensure that we can change one module without impacting others.
We want related behavior to sit together, and unrelated behavior to sit elsewhere.
A structure is stable if cohesion is strong and coupling is low.
Cohesion applies to the relationship between things inside a boundary (a microservice in our context), whereas coupling describes the relationship between things across a boundary.
Domain coupling describes a situation in which one microservice needs to interact with another microservice, because the first microservice needs to make use of the functionality that the other microservice provides.
Temporal coupling has a subtly different meaning in the context of a distributed system, where it refers to a situation in which one microservice needs another microservice to do something at the same time for the operation to complete.
One of the ways to avoid temporal coupling is to use some form of asynchronous communication, such as a message broker.
“Pass-through coupling”9 describes a situation in which one microservice passes data to another microservice purely because the data is needed by some other microservice further downstream.
The major issue with pass-through coupling is that a change to the required data downstream can cause a more significant upstream change. In our example, if Shipping now needs the format or content of the data to be changed, then both Warehouse and Order Processor would likely need to change.
One way to hide this detail would be to have Warehouse take in the required information as part of its contract, and then have it construct the Shipping Manifest locally, as we see in Figure 2-6.
One final approach that could help reduce the pass-through coupling would be for the Order Processor to still send the Shipping Manifest to the Shipping microservice via the Warehouse, but to have the Warehouse be
totally unaware of the structure of the Shipping Manifest itself.
Common coupling occurs when two or more microservices make use of a common set of data.
A potential solution here would be to ensure that a single microservice manages the order state.
The easy fix here is to have the Warehouse send requests to the Order service itself, where we can vet the request but
also hide the internal detail, making subsequent changes to the Order service much easier.
Ubiquitous language refers to the idea that we should strive to use the same terms in our code as the users use.
By working the real-world language into the code, things became much easier.
We want to treat aggregates as self-contained units; we want to ensure that the code that handles the state transitions of an aggregate are grouped together, along with the state itself. So one aggregate should be managed by one microservice, although a single microservice might own management of multiple aggregates.
In Figure 2-13, we have changed things to make the relationship explicit. Rather than a vanilla ID for the customer reference, we instead store a URI, which we might use if building a REST-based system.
Bounded contexts hide implementation detail. There are internal concerns—for example, the types of forklift trucks used are of little interest to anyone other than the folks in the warehouse. These internal concerns should be hidden from the outside world, which doesn’t need to know, nor should it care.
The aggregate is a self-contained state machine that focuses on a single domain concept in our system, with the bounded context representing a collection of associated aggregates, again with an explicit interface to the wider world.
one microservice can manage one or more aggregates, but we don’t want one aggregate to be managed by more than one microservice.
Volatility-based decomposition has you identify the parts of your system going through more frequent change and then extract that functionality into their own services, where they can be more effectively worked on.
It therefore follows that we must take into account the existing organizational structure when considering where and when to define boundaries, and in some situations we should perhaps even consider changing the organizational structure to support the architecture we want.
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 than trying to go to microservices from the beginning for this very reason.
We can mitigate the likely impact of this increase in latency by allowing for SKUs to be looked up in the Catalog microservice in bulk, or perhaps even by caching the required album information locally.
We could use a soft delete in the Album table so that we don’t actually remove a record but just mark it as deleted. Another option could be to copy the name of the album into the Ledger table when a sale is made, but we would have to resolve how we wanted to handle synchronizing changes in the album name.
Unfortunately, as we’ll cover in depth in “Database Transactions”, distributed transactions are not only complex to implement, even when done well, but they also don’t actually give us the same guarantees that we came to expect with more narrowly scoped database transactions.
With a reporting database, we instead create a dedicated database that is designed for external access, and we make it the responsibility of the microservice to push data from internal storage to the externally accessible reporting database, as seen in Figure 3-8.
When I make an in-process call, the underlying compiler and runtime can carry out a whole host of optimizations to reduce the impact of the call, including inlining the invocation so it’s as though there was never a call in the first place.
A developer needs to be aware if they are doing something that will result in a network call;
With communication between microservices, however, the microservice exposing an interface and the consuming microservices using that interface are separately deployable microservices.