For a lot of engineers, getting into microservices can be tough, because it’s hard to decide where lines should be drawn. For me, 80% of services fall into one of five categories, and dividing the responsibilities this way allows you to think of how to engineer features by piping services together kinda like you would do in Unix shell scripting.
Let’s for a moment talk about what all microservices have in common. Eric Evans, the father of Domain Driven Design, defines them as the following: “[services] that can consume and produce messages.” (https://www.youtube.com/watch?v=yPvef9R3k-M)
With that in mind, for each service pattern, I will talk about the types of messages that are produced or consumed.
These messages again, can be subdivided into two categories: Events, and Commands.
Before we get started though, and because context is important, I first heard of these microservice patterns from Matt Walters, the creator of the library servicebus. Servicebus is a Node adaptation of a popular .Net library called NServiceBus, which was created and popularized by Udi Dahan.
Servicebus allows you to easily write send and listen commands, and publish and subscribe to events using a universal language, with JSON payloads. This means other programming languages could easily implement the same interfaces and be able to seamlessly participate in a system composed of parts written in many languages.
If you’re a Go, or Python developer, who would like to contribute to that cause, send me a message!
And without further ado, the 5 microservice patterns.
1. Model Services
If MVC comes to mind, then you are on track with this type of service. The Model Services are where your models should live. The boundaries are typically made at the Aggregate or Entity level, depending on the complexity of the domain.
Model services consume messages about things that are relevant within their context. For example, if you had an Inventory Service, some command messages that would be relevant to consume would be inventory.product.create, or inventory.product.increaseStock. In response, you’ll want to produce some Event messages so the rest of the system can be aware of how the model is changing, and respond to those changes. The event messages produced in this example would be inventory.product.created and inventory.product.stockLevelIncreased.
UPDATE: 1/7/19 — Check out my new article on HackerNoon which talks about Event Sourcing for models.
2. Denormalizer Services
Denormalizers are exactly what Relational databases are doing, except, for a distributed system. They are joining together multiple normalized sources of input into a readable data structure that a client can consume.
For example, imagine you have an e-commerce app. When stock levels increase or decrease, or, become available in your inventory, your application should know about it.
This means with a denormalizer service you are subscribing to the events being emitted from the Model service above, and if you are using MongoDb, using something like mongoose to persist that data in a perfect structure for that particular application to consume.
Imagine if your application engineers are using something like Meteor with MongoDB — they just got real time inventory from an external system without having to write a line of code. This also works great with RethinkDB paired with GraphQL subscriptions!
3. Gateway Services
Gateway Services can be used very similarly to Denormalizers. Instead of connecting to a database, however, it is a connection to an API.
I was recently working with a recommendation engine, called LiftIgniter, with which our inventory needed to be synchronized. The service subscribes to inventory.product.updated and inventory.product.added events, and simply POSTs the formatted data to the appropriate endpoints.
Later on, an additional service was added that listened for the same events, and by building a Magento Gateway service, we were able to keep an ecommerce store up to date with the changing inventory levels as well!
4. Ingestor Services
All we’ve talked about so far is working with data that is propagating through the system, or created in Model services. However, it’s a frequent requirement to get external data INTO the system. Conceptually, data from an outside source needs to be ingested into the universal language the rest of the system speaks. This is the job of an ingestor service.
Ingestor services are typically only producing messages. These services usually involve either receiving an API POST over HTTP, or, running a CRON job, and scraping at an interval. The fetched or received data is then published to the system using the universal language (AMQP w/ JSON).
5. Adapter Services
An adapter service is a more rare use case, but worth mentioning. Similar to a Gateway service, an Adapter consumes messages, then uses that data to invoke a library on the system. An example of this might be using a graphics manipulation tool like ImageMagick. ImageMagick is a powerful tool, but does not have Node.js bindings. An adapter service solves this by executing a child process, and then producing messages with the results, in the universal language of the system.
UPDATE: Jan. 12, 2018
A day after publishing this article, I realized there could be confusion about where API’s fit in. So I’ve decided to add this section and explain my reasoning. They are most definitely microservices, but they are not really new, and were not in my list of new things to teach.
API services should be kept lightweight. If you are building a whole bunch of business logic into an API, then you’re building a monolith. It’s slightly better than the combination of an application and a server that we used to see with “n-tier” architectures, but eventually leads to the infamous “Big Ball of Mud” or “Spaghetti Code” as well.
To accomplish this, use the “Denormalizer Service” from above to project a query efficient view of the data into the database(s) that the API reads from. This creates a unidirectional flow of data, or as I heard first from Matt, a “Unidirectional System”.
Using these above patterns allow you to work with immutable events in a unidirectional workflow. If you’re into application development, you are no doubt familiar with how Redux has changed the game for state management. Having one store with state that trickles down the component tree allows you to easily reason about how actions affect the state because they are simple immutable facts that all occur in a centralized location.
If you follow the above patterns, you will be using what is sometimes more complicatedly called Command Query Responsibility Segregation, or CQRS. Commands are consumed by Model Services, and Events are produced that are consumed by Denormalizer or Gateway Services, which update Read models. Queries are then made against the Read model.
Because you are using immutable messages, this makes Event Sourcing the perfect pattern for building your Model services. Another creation of Matt Walters that I've also contributed to and is worth checking out, is a micro-framework called sourced that works perfectly in harmony with servicebus, to easily add Event Sourcing capabilities to consume your service’s Events, and persist them to a database.
Finally, it’s worth mentioning, that with simplicity of services, some complexity is necessarily moved to the architecture. Hopefully I’ve provided you a way of mentally building microservice systems in your head with some generic lego blocks.
If you haven’t read my other article, “The 10 Puzzle Pieces of an Effective Microservice Architecture” read that as well! It covers the components that make a microservice architecture.
If you want to skip the part where you spend months learning how to set this all up yourself, I am preparing a web course that will show you how I’ve systemized the process, and codified the system, so you can be up, running, and efficient with Microservices in the quickest time possible! Sign up now to get access before everyone else! Visit Microservice Driven for info!
Interested in hearing MY DevOps Journey, WITHOUT useless AWS Certifications? Read it now on HackerNoon. DevOps is a prerequisite to effective microservices.
Thanks for reading! Please share with others if you've found this useful!
Want to learn more about me and my story? Click here to read about me and how my agency, Unbounded, can help you set up a simple microservices architecture.