A few weeks ago I finished reading this book, which was quite an intersting view on the microservices design problem. Most of the articles related to this topic tend to be either very conceptual and high-level (bounded context, culture shift, containers etc.) and usually describe how the whole distributed system should look like. One of the benefits of microservices is the ability to use different technologies for different services. And that makes total sense in a large enterprise, where you often have to deal with multiple programming languages and frameworks, or in a unicorn startup, where just everything is possible. But I am sure there hundreds of smaller companies with software systems already built and core technologies already chosen. How do you step into the shiny world of microservices with your specific background? How do you implement a microservice itself in a given programming language?
I feel that until now there wasn’t a good example of building microservices specifically in .NET and from application architecture perspective instead of enterprise architecture. And in my opinion, Microservices in .NET Core by Christian Horsdal Gammelgaard fills this gap in a very solid way. It uses an e-commerce system as an example throughout the book, starting by explaining basic microservices concepts and then going deep into detail across multiple topics, including reliability, testability, reusability, and many other -ilities. Christian uses a very structured approach, with eash chapter defining a generic problem area, then scoping it down to the specific software system being discussed, and then providing lots of code examples which show how to actually implement a given feature. Some solutions are clearly opinionated, but very openly: such places are usually accompanied with a discussion of other options. For example, the suggested approach to asynchronous handling of cross-service events is to expose an
/events endpoint in a microservice, which allows any interested subscriber to fetch events that happened after a specified ID. That was a new idea to me, since I would normally think of a message queue as a way to publish/subscribe events, but obviously this simple implementation has its benefits: at a price of a tighter coupling between publishers and subscribers, we are avoiding all the complexity and additional efforts of managing message queues infrastructure.
What I also really appreciated was the author’s going through common patterns in building distributed systems and giving practical solutions to them. If someone is building such a system for the first time, it might not be clear which cross-functional aspects should be taken into account. But retro-fitting them months later would be much, much harder than building them in right from the get go. So, Christian matter-of-factly explains how to support correlation tokens, how to make microservices’ communication robust with Polly, how to apply centralized logging with Serilog and Elasticsearch, and how the token-based authentication can be organized using IdentityServer. Some practical examples go even deeper into code level, showing, for instance, how a factory pattern can be used to inject standard HTTP headers into every request made by an
HttpClient instance. That, in my opinion, is what makes this book unique: whether you are an architect or a developer, you will find tons of useful information and hands-on code snippets.
Another interesting point is that, despite talking about .NET Core, the book uses Nancy as the web framework. Surely the same things could be achieved with vanilla ASP.NET Core, but Nancy allows to simplify so many things and write elegant and minimalistic code that is all about your application logic, not plumbing and infrastructure. I’ve used it several years ago in one of the projects, and it was a joy to work with. Obviously, being a long-time user of and contributor to Nancy project, Christian explains everything about Nancy in a very detailed way and convincingly demonstrates that it is a viable alternative to other .NET web frameworks.
With that said, there are certain parts in the book where everyone will have strong opinions on the topic. The suggested idea of packaging and reusing common middleware implementations as NuGet packages is probably a very good and practical solution, when one needs to implement many completely different new microservices across differect organizations. However, when applied to dozens of constantly evolving services in a single company, this might become difficult to maintain, since I think it would be quite hard to come up with such a middleware system upfront and expect no changes to it. And when changes do happen (new security infrastructure, a different distributed monitoring system etc.), this common package becomes a coupling point between many already deployed services, so updating them all without breaking any can become quite tricky. In some cases, as Sam Newman’s Building Microservices book suggests, it might be beneficial to accept some level of code duplication and use source-controlled quickstart project templates with this common functionality instead of NuGet packages. In any case, both approaches have their places and the answer is always “it depends”.
In conclusion, I highly recommend this book to any .NET developer or architect interested in doing microservices and looking for practical advice. Also, there is Christian’s talk on NDC London 2017, where he touches upon similar topics, but more focusing on using ASP.NET Core middleware to add cross-cutting functionality to web applications and to compose these pieces into a reusable microservices platform.