Microservices: Architecture for Containerized .NET Applications notes

Recently I finished the book “.NET Microservices: Architecture for Containerized .NET Applications”. I have to admit that it was a quite impressive content. It covers, not only the Microsoft products details offer when developing and architecting microservices with containers and deploying them in Azure. It also expand to technology-agnostic details about patterns and practices when you need to design and implement a microservices based solution.

The following are the notes I wrote down over the time I was reading the book. This could be seen as a quick summary-reference to keep in mind the main topics of the book.

Data sovereignty in microservices

Each microservice owns its data. It is private to it and must be accessed by other microservices through the microservice’s API. Having multiple microservices using the same schema leads to issues and it is a bad practice.

Defining the scope of a microservice is an important duty. You must be careful when deciding what the boundaries are for a microservice. Something that can help is analyzing the bounded context (talking about DDD concepts) and deciding whether it could be represented by a microservice. Just a small caveat: a microservice doesn’t necessarily match to a bounded context. A microservice could be one or more services or processes. In that situations, data can be shared as long as the services are coherence.

Options to tackle distributed data bases for query aggregations:

  • API data gateway: An API that aggregates data from multiple microservices. May create one for business case.
  • CQRS: Applying this patter with a read only data base tailored for your app needs.
  • Cold data: Such as those data warehouse.

To handle data consistency among all microservices, use asynchronous communication mechanisms such as Message Communications or publisher – subscriber pattern.

Communication between Microservices

Strive for having asynchronous communication between them. Avoid using http chains. Avoid using synchronous http request/response communication chain among microservices, even for querying data. If you need data owned by other microservices, propagate that data (only what you need) into your microservice’s database by eventual consistency mechanisms.

API Gateway pattern

It is similar to the facade pattern from OOP. There may be services per business boundaries and client apps consumers. Never create a gateway for all microservices; it might turns into a monolithic.

When an API is based purely on client needs, it is named Backend for Frontend pattern.

API Gateways products available: Azure API Management and Ocelot (there are many others available).

Resiliency and Health of Microservices

A microservice must reports its health. In the same way, the logging format must be aligned among different microservices in order for the operation’s team to understand and address failures.

A microservice must be able to restart in a different machine.

For a microservice to report health, there are 2 libraries: ASP.NET healthchecks library and Beat Pulse.

Internal vs External Microservice architecture

The external is as the microservice approach per application. The internal is like how components are split. Each microservice could implement a different internal architecture based on its needs.

The point is: “No particular architecture pattern or style, nor any particular technology, is right for all situations.”

Building event-driven communication Microservice systems

Highlight: Every piece of important software component, such as: Database, Redis cache, Message Queue or Microservice must reside in its own container to keep separation of concerns.

Use products like RabbitMQ for proof of concepts or testing purposes. For production environments, use high level service bus products like: Azure Services Bus, NServiceBus, MassTransit or Brighter.

Observable pattern vs Publisher/Subscriber pattern.

Both broadcast events to others in a event-driven way. The main difference is: Observable broadcast its events itself. In Publisher/Subscriber there is an event bus or message broker in the middle.

Testing ASP.NET Core services and web apps

Types of tests to be performed:

  • Unit test: Test component behavior.
  • Integration tests: Test how components interacts with external artifacts such as databases, I/O, etc.
  • Functional tests: Tests from the User perspective.
  • Service tests: end-to-end test of service use case.

Tackling complexity on Microservices through DDD and CQRS patterns.

CQS

It is a simplified version of the CQRS pattern. It encourage to separate queries from commands.

It states: queries do not mutate the system while commands do so.

Domain Entity pattern

An entity is defined by its identity. It should be the same across different boundaries. Entities must have behavior. Here is where business rules, validations and behaviors reside.

Important

  • Define one rich domain model for each business microservice or Bounded Context.
  • An entity’s identity can cross multiple microservice or Bounded Context.
  • Domain entities must implement behavior in addition to implementing data attributes.

Value Object pattern

The main characteristic is that Value Objects don’t have any identity. What really matters about them is the behavior they offer. They describe a domain aspect but not their own identity. We care about what they are instead of who they are.

The Aggregate Root pattern

The entity root may have many child entities and value objectsThe main purpose of it is ensure aggregate consistency. Any change to the aggregate must happened through the entity root, even changes on child entities.

In DDD we do not have navigation between different aggregates. We keep them separated. The way to reference another aggregate is by having a Id.

When coding your domain entity classes, you may have a Entity base class and an empty interface just to mark the entity as an Aggregate root (for example: IAggregatRoot). However, keep in mind that a marker interface is considered an anti-pattern.

Properties in an aggregate root should be either private or readonly to enforce transactions and business rules validations at the aggregate root level. It is a pattern in DDD. Remember, aggregate root must ensure the integrity of the whole aggregate.

Domain Events

Use these to implement domain rules without coupling the code. These are like Event to keep consistency among Microservice but the difference is that Domain Events occur almost immediately. Domain events are aimed to propagate side effects to other aggregates.

Domain events handlers must be implemented in the application layer.

It is important to mention that domain events can generate integration events outside of the microservice boundaries. In this case, the domain event handler can publish those events to an event bus.

Designing the Infrastructure Persistence Layer

Define one repository per aggregate

Use one repository per aggregate root. The only channel to update the db must be through the repository. You can query the db through other mechanism (like when implementing the CQRS). However querying does not change any state but updates do change data state.

The Hi/Lo algorithms

This is an algorithm that take a batch of id from the database to use in our database operations in code. So, the database round trips to get those ids is avoided. This is implemented in Entity Framework Core through the method “ForSqlServerUseSequenceHiLo”

Solid Principales

You can easily know how much dependencies your classes have just by looking at the constructor (besides you should be using DI). If there are too many dependencies it is a sign that class is trying to do so much and violating the Single Responsibility Principle.

Commands

These are related to the CQRS pattern. They act as a way to perform application operations. Commands just contain data and are immutable. Command handlers on the other side, take the command, domain and infrastructure objects and perform operations among them. The important point is to keep them as simple as possible. Remember domain logic must reside inside domain objects.

The way to invoke a command handler is through in-memory mediator patter artifact or asynchronous message queue.

A difference between command events and integration events is that the application always must know the result of the command operation or, at least, if the command was accepted and validated. Commands never uses “fire and forget” approach.

Implement Resilient Applications

Strategies to handle partial failures

  • Use asynchronous communication across internal microservices. The only exception is for client to first level/API gateway microservices.
  • Use retries with exponential back-off.
  • Work around network timeouts.
  • Use the Circuit Breaker pattern.
  • Provide fallback.
  • Limit the number of queued requests.

Retries with exponential backoff

This technique is that when a request fails, the subsequent retires are performed with an increase wait time until the maximum number of retries is reached.

Entity Framework Core Resiliency for Azure SQL Database

Entity Framework Core has a built-in characteristic to perform automatic retry operations when the database connection fails. This is done at the configuration section of Entity Framework Core in the startup class.

Monitoring and Health Check

It is important to check the health of your services. First of all, define what health means to your service: if it depends on other services, database, queue system, etc.

You can implement your health check in .NET Core through the HealthCheck library.

When using an orchestrator services such as Kubernetes or Azure Service Fabric, these offer their own health check implementation. Plus, depending on the specific configuration, they can stop redirecting traffic to the unhealthy service and start a new instance of it.

It is important to visualize, analyze and rise alerts when your services are not healthy. For this matter, you can use custom monitoring pages like that offered by the HealthCheck library or an advance service like Azure Monitor. Plus you can save the events stream to Power BI, Kibana or Splunk.

Make secure .NET Microservices and Web Applications

Authentication

There are several options for this matter.

If you are using an API Gateway, that is a good place to put the authentication mechanism. But make sure the services are not available directly or, at least, messages towards them must include some authentication information.

Another option is using a dedicated service for authentication. Options are: using Azure Active Directory (in Azure) or a tokenized service approach, sharing cookies or tokens among services.