Chris Richardson writes a reference site on microservice architecture patterns such as saga, CQRS, API gateway, and transactional outbox.
HN commenters call it a gold reference for microservices and cite its patterns, including separate databases per service, though one disputes advice drawn from it.
> Once the code for all destinations lived in a single repo, they could be merged into a single service. With every destination living in one service, our developer productivity substantially improved. We no longer had to deploy 140+ services for a change to one of the shared libraries. One engineer can deploy the service in a matter of minutes.
This is the problem with the undefined nature of the term `microservices`, In my experience if you can't develop in a way that allows you to deploy all services independently and without coordination between services, it may not be a good fit for your orgs needs.
In the parent SOA(v2), what they described is a well known anti-pattern: [0]
Application Silos to SOA Silos * Doing SOA right is not just about technology. It also requires optimal cross-team communications. Web Service Sprawl * Create services only where and when they are needed. Target areas of greatest ROI, and avoid the service sprawl headache. If you cannot, due to technical or political reasons, retain the ability to independently deploy a service, no matter if you choose to actually independently deploy, you will not gain most of the advantages that were the original selling point of microservices, which had to do more with organizational scaling than technical conserns.
There are other reasons to consider the pattern, especially due to the tooling available, but it is simply not a silver bullet.
And yes, I get that not everyone is going…
Gateway APIs: http://microservices.io/patterns/apigateway.html
Assuming you've read that then I'd add the following. If you don't have something approaching a gateway, you're probably doing a big disservice to your users (assuming clients fetch data via API, rather than using a traditional render-on-the-server framework like Django or Rails).
The value proposition of GraphQL is based on the assumption you're already doing things right by users, and its possible to fulfill the data requirements for a single page or app screen in a single API call. What this probably means is:
* You have a custom endpoint per screen
* That endpoint to either a) be versioned or b) support as many historical versions of your app as are in production
* You might have a gateway per client (this is the full backend-for-frontend pattern)
* Every time you add functionality, or change existing functionality, you're adding to what quickly becomes a huge set of endpoints
What GraphQL promises (and delivers on) is the ability to get all the benefits of the backend-for-frontend pattern, without anyone ever having to write an endpoint specifically for a give client use case. The clients convert their data requirements into a query, and the GraphQL server returns the exact data you need, no more, no less. It requires some discipline when it comes to evolving the schema over time, but it works really really well. And when implemented intelligently has comparable performance to hand-crafted endpoints…
Go out and buy/find/read the following books:
(1) Patterns of Enterprise Software Architecture https://www.oreilly.com/library/view/patterns-of-enterprise/0321127420/
WHY: What kind of things do we need to build enterprise software applications, and where should we draw lines to separate concerns for these subsystems?
(2) Gang of Four Objected oriented design patterns https://www.digitalocean.com/community/tutorials/gangs-of-four-gof-design-patterns
WHY: what are the recurring themes in doing good OOP as described by the guys who have been doing it since the beginning?
(3) Microservice Design Patterns https://microservices.io/patterns/microservices.html
WHY: What are the recurring patterns, themes, and challenges in building good micro service architectures?
(4) Uncle Bob’s Clean Code https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship
WHY: Write code that somebody else could pick up, read, and not be completely baffled by. In all likelihood, the next person to read your code will be you. Here’s how to do it.
(5) SOLID Design patterns
WHY: Essentially how to do good OOP part deux.
These are basically, in my opinion, the equivalent of University Physics or Eisberg/Resnick QM textbooks. They’re seminal texts with a lot of valuable insights into patterns, jargon that other seniors throw around, etc. Also depending on your learning style I’ve seen this material covered in a billion different shapes: YouTube pseudo-lectures, GitHub repos, blog…
To all the folks saying “durable execution frameworks solve this”—you’re right, but a lot of what’s described in the article isn’t quite the same as durable execution a la temporal. The approach described (transactional outboxes for side effectful operations, and care taken to be idempotent or resumable where possible, and to gracefully degrade, slow down, or rate limit where you can) achieves some of the same properties as a given durable execution framework, its true, but you don’t necessarily need to rewrite your code to be fully event sourced or use a framework to get a lot of those benefits, as the article demonstrates.
Transactional outboxes specifically are one of my favorite patterns: they’re not too hard to add and don’t require changing many core invariants of your system. If you already use some sort of message bus or queue, making publishes to it transactional under a given RDBMS is often as simple as adding some client side code and making sure that logical message deduplication and is present where appropriate: https://microservices.io/patterns/data/transactional-outbox.html
If you use a separate message broker (Kafka, SQS, RabbitMQ) with this pattern, you’ll also need a sweeper cron job to re-dispatch failed publishes from the outbox table(s) as well.
Bonus points if this can be implemented on top of existing trigger-based audit table functionality.
It's industry best practice -- not a joke. What's more, the recommendation is to have a separate database per service -- one table, one service, one database.
Fuethermore, when a join is desired, the best practice is to implement not just a service that joins the data but maintains it in a table/materialized view of its own, along with a message server such as Kafka. The services responsible for the tables to be joined (customers and orders, for instance) put events on this message bus which the joining service subscribes to in order to know when to update its view. See: https://microservices.io/patterns/data/cqrs.html
And don't get me started on how you do transactions that touch multiple tables in this setup: https://microservices.io/patterns/data/saga.html
I have seen these deployed and advocated as the modern way to write business applications in the wild.
We are in the middle of the first production-scale deployment of a greenfield microservice-based application now and there are certainly a lot of pain points. On the flip side, I've been a part of about half a dozen extremely successful strangler migrations[0], some of which were downright easy even with a complicated business domain. I often wonder if we would have been better off deploying a monolith in half the time and immediately starting a strangler migration once the rate of business changes slows down. I've become more and more convinced over the past decade that Monolith First + Strangler Migration is most stable, safest way to deliver mid-sized software projects.
[0] https://microservices.io/patterns/refactoring/strangler-application.html