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 is a service name that depends 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 sections, 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 runtime.
Figure 6-33. 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-34. 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-35. 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-36. 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 considering 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-37. 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-38. 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 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-39. Position of the Identity service in eShopOnContainers
However, Ocelot also supports sitting the Identity/Auth microservice within the API Gateway boundary, as in this other diagram.
Figure 6-40. Authentication in Ocelot
Because eShopOnContainers application has 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. Since there's only one cross-cutting concern in eShopOnContainers, it was decided to just handle the security service out of the API Gateway realm, for simplicity’s sake.
In any case, if the app is secured at the API Gateway level, the authentication module of the Ocelot API Gateway is visited at first when trying to use any secured microservice. That re-directs the HTTP request 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.