As noted earlier, when you test multi-container applications, all the microservices need to be running within the Docker host or container cluster. End-to-end service tests that include multiple operations involving several microservices require you to deploy and start the whole application in the Docker host by running docker-compose up (or a comparable mechanism if you are using an orchestrator). Once the whole application and all its services is running, you can execute end-to-end integration and functional tests.
There are a few approaches you can use. In the docker-compose.yml file that you use to deploy the application (or similar ones, like docker-compose.ci.build.yml), at the solution level you can expand the entry point to use dotnet test. You can also use another compose file that would run your tests in the image you are targeting. By using another compose file for integration tests that includes your microservices and databases on containers, you can make sure that the related data is always reset to its original state before running the tests.
Once the compose application is up and running, you can take advantage of breakpoints and exceptions if you are running Visual Studio. Or you can run the integration tests automatically in your CI pipeline in Visual Studio Team Services or any other CI/CD system that supports Docker containers.
Background tasks and scheduled jobs are something you might need to implement, eventually, in a microservice based application or in any kind of application. The difference when using a microservices architecture is that you can implement a single microservice process/container for hosting these background tasks so you can scale it down/up as you need or you can even make sure that it runs a single instance of that microservice process/container.
From a generic point of view, in .NET Core we called these type of tasks Hosted Services, because they are services/logic that you host within your host/application/microservice. Note that in this case, the hosted service simply means a class with the background task logic.
Since .NET Core 2.0, the framework provides a new interface named IHostedService helping you to easily implement hosted services. The basic idea is that you can register multiple background tasks (hosted services), that run in the background while your web host or host is running, as shown in the image 8-25.
Figure 6-25. Using IHostedService in a WebHost vs. a Host
Note the difference made between WebHost and Host.
A WebHost (base class implementing IWebHost) in ASP.NET Core 2.0 is the infrastructure artifact you use to provide Http server features to your process, such as if you are implementing an MVC web app or Web API service. It provides all the new infrastructure goodness in ASP.NET Core, enabling you to use dependency injection, insert middlewares in the Http pipeline, etc. and precisely use these IHostedServices for background tasks.
A Host (base class implementing IHost), however, is something new in .NET Core 2.1. Basically, a Host allows you to have a similar infrastructure than what you have with WebHost (dependency injection, hosted services, etc.), but in this case, you just want to have a simple and lighter process as the host, with nothing related to MVC, Web API or Http server features.
Therefore, you can choose and either create a specialized host-process with IHost to handle the hosted services and nothing else, such a microservice made just for hosting the IHostedServices, or you can alternatevely extend an existing ASP.NET Core WebHost, such as an existing ASP.NET Core Web API or MVC app.
Each approach has pros and cons depending on your business and scalability needs. The bottom line is basically that if your background tasks have nothing to do with HTTP (IWebHost) you should use IHost, when available in .NET Core 2.1.
Let’s drill down further on the IHostedService interface since its usage is pretty similar in a WebHost or in a Host.
SignalR is one example of an artifact using hosted services, but you can also use it for much simpler things like:
You can basically offload any of those actions to a background task based on IHostedService.
The way you add one or multiple IHostedServices into your WebHost or Host is by registering them up through the standard DI (dependency injection) in an ASP.NET Core WebHost (or in a Host in .NET Core 2.1). Basically, you have to register the hosted services within the familiar ConfigureServices() method of the Startup class, as in the following code from a typical ASP.NET WebHost.
In that code, the GracePeriodManagerService hosted service is real code from the Ordering business microservice in eShopOnContainers, while the other two are just two additional samples.
The IHostedService background task execution is coordinated with the lifetime of the application (host or microservice, for that matter). You register tasks when the application starts and you have the opportunity to do some graceful action or clean-up when the application is shutting down.
Without using IHostedService, you could always start a background thread to run any task. The difference is precisely at the app’s shutdown time when that thread would simply be killed without having the opportunity to run graceful clean-up actions.
When you register an IHostedService, .NET Core will call the StartAsync() and StopAsync() methods of your IHostedService type during application start and stop respectively. Specifically, start is called after the server has started and IApplicationLifetime.ApplicationStarted is triggered.
The IHostedService as defined in .NET Core, looks like the following.
As you can imagine, you can create multiple implementations of IHostedService and register them at the ConfigureService() method into the DI container, as shown previously. All those hosted services will be started and stopped along with the application/microservice.
As a developer, you are responsible for handling the stopping action or your services when StopAsync() method is triggered by the host.
You could go ahead and create you custom hosted service class from scratch and implement the IHostedService, as you need to do when using .NET Core 2.0.
However, since most background tasks will have similar needs in regard to the cancellation tokens management and other tipical operations, .NET Core 2.1 will be providing a very convenient abstract base class you can derive from, named BackgroundService.
That class provides the main work needed to set up the background task. Note that this class will come in the .NET Core 2.1 library so you don’t need to write it.
However, as of the time of this writing, .NET Core 2.1 has not been released. Therefore, in eShopOnContainers which is currently using .NET Core 2.0, we are just temporally incorporating that class from the .NET Core 2.1 open-source repo (no need of any proprietary license other than the open-source license) because it is compatible with the current IHostedService interface in .NET Core 2.0. When .NET Core 2.1 is released, you’ll just need to point to the right NuGet package.
The next code is the abstract BackgroundService base class as implemented in .NET Core 2.1.
When deriving from the previous abstract base class, thanks to that inherited implementation, you just need to implement the ExecuteAsync() method in your own custom hosted service class, as in the following simplified code from eShopOnContainers which is polling a database and publishing integration events into the Event Bus when needed.
In this specific case for eShopOnContainers, it is executing an application method that is quering a database table looking for orders with a specific state and when applying changes, it is publishing integration events through the event bus (underneath it can be using RabbitMQ or Azure Service Bus).
Of course, you could run any other business background task, instead.
By default, the cancellation token is set with a 5 second timeout, although you can change that value when building your WebHost using the UseShutdownTimeout extension of the IWebHostBuilder. This means that our service is expected to cancel within 5 seconds otherwise it will be more abruptly killed.
The following code would be changing that time to 10 seconds.
The following image shows a visual summary of the classes and interfaced involved when implementing IHostedServices.
Figure 6-26. Class diagram showing the multiple classes and interfaces related to IHostedService
It is important to note that the way you deploy your ASP.NET Core WebHost or .NET Core Host might impact the final solution. For instance, if you deploy your WebHost on IIS or a regular Azure App Service, your host can be shut down because of app pool recycles. But if you are deploying your host as a container into an orchestrator like Kubernetes or Service Fabric, you can control the assured number of live instances of your host. In addition, you could consider other approaches in the cloud especially made for these scenarios, like Azure Functions.
But even for a WebHost deployed into an app pool, there are scenarios like repopulating or flushing application’s in-memory cache, that would be still applicable.
The IHostedService interface provides a convenient way to start background tasks in an ASP.NET Core web application (in .NET Core 2.0) or in any process/host (starting in .NET Core 2.1 with IHost). Its main benefit is the opportunity you get with the graceful cancellation to clean-up code of your background tasks when the host itself is shutting down.
https://www.stevejgordon.co.uk/asp-net-core-2-ihostedservice
https://github.com/aspnet/Hosting/tree/dev/samples/GenericHostSample
In the reference microservice application eShopOnContainers, it is using Ocelot because it is a simple and lightweight API Gateway that you can deploy anywhere along with your microservices/containers such as in any of the following environments used by eShopOnContainers.
The following architecture diagram shows how API Gateways are implemented with Ocelot in eShopOnContainers.
Figure 6-27. eShopOnContainers architecture with API Gateways
That diagram shows how the whole application is deployed into a single Docker host or development PC with “Docker for Windows” or “Docker for Mac”. However, deploying into any orchestrator would be pretty similar but any container in the diagram could be scaled-out in the orchestrator and the infrastructure assets such as databases, cache and message brokers should be offloaded from the orchestrator and deployed into high available systems for infrastructure, like Azure SQL Database, Azure Cosmos DB, Azure Redis, Azure Service Bus, or any HA clustering solution on-premises.
As you can also notice in the diagram, having several API Gateways allows the multiple development teams to be autonomous (in this case Marketing vs. Shopping) when developing and deploying their microservices plus their own related API Gateways. If you had a single monolithic API Gateway that would mean a single point to be updated by multiple development teams which could couple all the microservices with a single part of the application.
Going much further in the design, sometimes a fine-grained API Gateway can also be limited to a single business microservice depending on the chosen architecture. Having the API Gateway’s boundaries dictated by the business or domain will help you to get a better design. For instance, fine granularity in the API Gateway tier can be especially useful for more advanced composite UI applications based on microservices, because the concept of a fine-grained API Gateway is similar to a UI composition service. We discuss this topic in the section “Creating composite UI based on microservices”.
Therefore, for many medium- and large-size applications, using a custom-built API Gateway product is usually a good approach, but not as a single monolithic aggregator or unique central custom API Gateway unless that API Gateway allows multiple independent configuration areas for the multiple development teams with autonomous microservices.
As an example, eShopOnContainers has around 6 internal microservice-types that have to be published through the API Gateways, as shown in the following image.
Figure 6-28. Microservice folders in eShopOnContainers solution in Visual Studio
In the case of the Identity service, in our design we left it out of the API Gateway routing, but with Ocelot it is also possible to include it as part of the re-routing lists.
All those services are currently implemented as ASP.NET Core Web API services, as you can tell because of the code. Let’s focus on one of the microservices like the Catalog microservice code.
Figure 6-29. Sample Web API microservice (Catalog microservice)
You can see this is a pretty typical ASP.NET Core Web API project with several controllers and methods like in the following code.
The Http request will end up running that kind of C# code accessing the microservice database, etc.
In regards the microservice URL, when the containers are deployed in your local development PC (local Docker host), each microservice’s container has always an internal port (usually port 80) specified in its dockerfile, as in the following dockerfile.
But that port is internal within the Docker host, so it cannot be reached by the client apps, only to the external ports (if any) provided when deploying with docker-compose.
Those external ports should not be published when deploying into production environment because that’s why we’re using the API Gateway, to hide the direct communication to the microservices.
However, when developing, it might be useful to be able to access the microservice/container directly and run it through Swagger. That’s why in eShopOnContainers the external ports are still specified even when those won’t be used by the API Gateway or the client apps.
Here’s an example of the docker-compose.override.yml file for the Catalog microservice.
You can see how in the docker-compose.override.yml configuration the internal port for the Catalog container is port 80, but the port for external access is 5101. But this port shouldn’t be used by the application when using an API Gateway, only to debug run and test just the Catalog microservice.
Note that you usually won’t be deploying with docker-compose into a production environment because the right production deployment environment for microservices is an orchestrator like Kubernetes or Service Fabric and when deploying to those environments you will use different configuration files where you won’t publish directly any external port for the microservices but you will always use the reverse proxy from the API Gateway.
Let’s run the catalog microservice in your local Docker host either by running the full eShopOnContainers solution from Visual Studio (it’ll run all the services in the docker-compose files) or just starting the Catalog microservice with the following docker-compose command in CMD or PowerShell positioned at the folder where the docker-compose.yml and docker-compose.override.yml are placed.
This command will only run the catalog.api service container plus its dependencies specified in the docker-compose.yml, in this case, the SQL Server container and RabbitMQ container.
Then you could directly access the Catalog microservice and see its methods through the Swagger UI accessing directly through that “external” port, in this case http://localhost:5101
Figure 6-30. Testing the Catalog microservice with its Swagger UI
At this point you could set a breakpoint in C# code in VS, test the microservice with the methods exposed in Swagger UI, etc. and finally clean-up everything with the “docker-compose down” command.
But this direct access communication to the microservice (in this case through the external port 5101) is precisely what you want to avoid in your application by setting the additional level of indirection of the API Gateway, Ocelot, in this case. Therefore, the client app won’t directly access the microservice.
Ocelot is basically a set of middlewares that you can apply in a specific order.
Ocelot is designed to work with ASP.NET Core only and it targets netstandard2.0. This means it can be used anywhere .NET Standard 2.0 is supported, including .NET Core 2.0 runtime and .NET Framework 4.6.1 runtime and up.
You install Ocelot and its dependencies in your ASP.NET Core project with Ocelot NuGet package.
In the case of eShopOnContainers, its API Gateway implementation is a very simple ASP.NET Core WebHost project, and Ocelot’s middlewares handle all the API Gateway features, as shown in the following image.
Figure 6-31. The OcelotApiGw base project in eShopOnContainers
This ASP.NET Core WebHost project is basically made with two simple files, the Program.cs and Startup.cs.
The Program.cs just needs to create and configure the typical ASP.NET Core BuildWebHost.
The important point here for Ocelot is the configuration.json file that you must provide to the builder through the AddJsonFile() method. That configuration.json is where you specify all the API Gateway ReRoutes, meaning the external endpoints and ports and the correlated internal endpoints and internal ports.
There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global configuration allows overrides of ReRoute specific settings. It’s useful if you don’t want to manage lots of ReRoute specific settings.
Here’s a simplified example of ReRoute configuration file from one of the API Gateways from eShopOnContainers.
The main functionality of an Ocelot API Gateway is to take incoming http requests and forward them on to a downstream service, currently as another http request. Ocelot’s describes the routing of one request to another as a ReRoute.
For instance, let’s focus on one of the ReRoutes in the configuration.json from above, the configuration for the Basket microservice
The DownstreamPathTemplate, Scheme and DownstreamHostAndPorts make the internal microservice URL that this request will be forwarded to.
The port is the internal port used by the service. When using containers, the port specified at its dockerfile.
The Host will be a service name that will depend on the service name resolution you are using. When using docker-compose, the services names are provided by the Docker Host which is using the service names provided in the docker-compose files. If using an orchestrator like Kubernetes or Service Fabric, that name should be resolved by the DNS or name resolution provided by each orchestrator.
DownstreamHostAndPorts is an array that contains the host and port of any downstream services that you wish to forward requests to. Usually this will just contain one entry but sometimes you might want to load balance requests to your downstream services and Ocelot lets you add more than one entry and then select a load balancer. But if using Azure and any orchestrator it is probably a better idea to load balance with the cloud and orchestrator infrastructure.
The UpstreamPathTemplate is the URL that Ocelot will use to identify which DownstreamPathTemplate to use for a given request from the client. Finally, the UpstreamHttpMethod is used so Ocelot can distinguish between different requests (GET, POST, PUT) to the same URL and is obviously needed to work.
At this point you could have a single Ocelot API Gateway (ASP.NET Core WebHost) using one or multiple merged configuration.json files or you can also store the configuration in a Consul KV store.
But as introduced in the architecture and design section, if you really want to have autonomous microservices it might be better to split that single monolithic API Gateway into multiple API Gateways and/or BFF (Backend for Frontend). For that purpose, let’s see how to implement that approach with Docker containers.
In eShopOnContainers we’re taking advantage of a single Docker container image with the Ocelot API Gateway but then, at run time, we create different services/containers for each type of API-Gateway/BFF by providing a different configuration.json file for each container, in runtim
Figure 6-32. Re-using a single Ocelot Docker image across multiple API Gateway types
Therefore, in eShopOnContainers, the “Generic Ocelot API Gateway Docker Image” is created with the project named “OcelotApiGw” and the image name “eshop/ocelotapigw” that is specified in the docker-compose.yml file. Then, when deploying to Docker, there will be four API-Gateway containers created from that same Docker image, as shown in the following extract from the docker-compose.yml file.
Additionally, and as you can see in the docker-compose.override.yml file, the only difference between those API Gateway containers is the Ocelot configuration file which is different for each service container and specified at runtime through a Docker volume, as shown in the following docker-compose.override.yml file.
That is why in the source code and shown in the VS Explorer view screenshot below, the only file needed to define each specific business/BFF API Gateway is just a configuration.json file, because the four API Gateways are based on the same Docker image.
Figure 6-33. The only file needed to define each API Gateway / BFF with Ocelot is a configuration file
By splitting the API Gateway into multiple API Gateways, different development teams focusing on different subsets of microservices can manage their own API Gateways by using independent Ocelot configuration files while re-using the same Ocelot Docker image.
Now, if you run eShopOnContainers with the API Gateways (included by default in VS when opening eShopOnContainers-ServicesAndWebApps.sln solution or if running “docker-compose up”), the following sample routes will be performed.
For instance, when visiting the upstream URL http://localhost:5202/api/v1/c/catalog/items/2/ served by the webshoppingapigw API Gateway, you get the result from the internal Downstream URL http://catalog.api/api/v1/2 within the Docker host, as in the following browser.
Figure 6-34. Accessing a microservice through a URL provided by the API Gateway
Because of testing or debugging reasons, if you wanted to directly access to the Catalog Docker container (only at the development environment) without passing through the API Gateway, since “catalog.api” is a DNS resolution internal to the Docker host (service discovery handled by docker-compose service names), the only way to directly access the container is through the external port published in the docker-compose.override.yml, which is provided only for development tests, such as http://localhost:5101/api/v1/Catalog/items/1 in the following browser.
Figure 6-35. Direct access to a microservice for testing purposes
But the application is configured so it accesses all the microservices through the API Gateways, not though the direct port “shortcuts”.
As introduced previously, a very flexible way to implement requests aggregation is with custom services, by code. You could also implement request aggregation with the Request Aggregation feature in Ocelot, but it might not be as flexible as you need. Therefore, the selected way to implement aggregation in eShopOnContainers is with an explicit ASP.NET Core Web API services for each aggregator. According to that approach, the API Gateway composition diagram is in reality a bit more extended when taking into account the aggregator services that are not shown in the simplified global architecture diagram shown previously.
In the following diagram you can also see how the aggregator services work with their related API Gateways.
Figure 6-36. eShopOnContainers architecture with aggregator services
Zooming in into the diagram in the following image, you can notice how for the “Shopping” business area, the client apps could be improved by reducing chattiness with microservices by implementing those aggregator services under the realm of the API Gateways.
Figure 6-37. Zoom in vision of the Aggregator services
You can notice how when the diagram shows the possible requests coming from the API Gateways it can get pretty complex. Although you can see how the arrows in blue would be simplified, from a client apps perspective, when using the aggregator pattern by reducing chattiness and latency in the communication, ultimately significantly improving the user experience for the remote apps (mobile and SPA apps), especially.
In the case of the “Marketing” business area and microservices, it is a very simple use case so there was no need to use aggregators, but it could also be possible, if needed.
In an Ocelot API Gateway you can sit the authentication service, such as an ASP.NET Core Web API service using IdentityServer providing the auth token, either out or inside the API Gateway.
Since in eShopOnContainers is using multiple API Gateways with boundaries based on BFF and business areas, the Identity/Auth service is left out of the API Gateways, as highlighted in yellow in the following diagram.
Figure 6-38. Position of the Identity service in eShopOnContainers
However, Ocelot also supports to sit the Identity/Auth microservice within the API Gateway boundary, as in this other diagram.
Figure 6-39. Authentication in Ocelot
Since in eShopOnContainers we have split the API Gateway into multiple BFF (Backend for Frontend) and business areas API Gateways, another option would had been to create an additional API Gateway for cross-cutting concerns. That choice would be fair in a more complex microservice based architecture with multiple cross-cutting concerns microservices. But sin we have just one cross-cutting concerns in eShopOnContainers it was decided to just handle the security service out of the API Gateway realm, for simplicity’s sake.
In any case, the authentication module of Ocelot API Gateway will be visited at first when trying to use any secured microservice (if secured at the API Gateway level). That will re-direct to visit the Identity or auth microservice to get the access token so so you can visit the protected services with the access_token.
The way you secure with authentication any service at the API Gateway level is by setting the AuthenticationProviderKey in its related settings at the configuration.json.
When Ocelot runs, it will look at the ReRoutes AuthenticationOptions.AuthenticationProviderKey and check that there is an Authentication Provider registered with the given key. If there isn't, then Ocelot will not start up. If there is, then the ReRoute will use that provider when it executes.
Because the Ocelot WebHost is configured with the authenticationProviderKey = "IdentityApiKey", that will require authentication whenever that service has any requests without any auth token.
Then, you also need to set authorization with the [Authorize] attribute on any resource to be accessed like the microservices, such as in the following Basket microservice controller.
The ValidAudiences such as “basket” are correlated with the audience defined in each microservice with AddJwtBearer() at the ConfigureServices()of the Startup class, such as the code below.
Now, if you try to access any secured microservice like the Basket microservice with a ReRoute URL based on the API Gateway like http://localhost:5202/api/v1/b/basket/1 then you’ll get a 401 Unauthorized unless you provide a valid token. On the other hand, if a ReRoute URL is authenticated, Ocelot will invoke whatever downstream scheme is associated with it (the internal microservice URL).
Authorization at Ocelot’s ReRoutes tier. Ocelot supports claims-based authorization evaluated after the authentication. You set the authorization at a route level by adding the following to you ReRoute configuration.
In that example, when the authorization middleware is called, Ocelot will find if the user has the claim type “UserType” in the token and if the value of that claim is “employee”. If it isn’t then the user will not be authorized and the response will be 403 forbidden.