Microservice Solution: Adding New API Gateways
You must have an ABP Business or a higher license to be able to create a microservice solution.
API gateways are the entry points of your microservice system. They are responsible for routing the incoming requests to the correct microservices. In a microservice system, you can have multiple API gateways to serve different purposes. For example, you can have a public API gateway for your customers and a private API gateway for your internal services. In the solution template, there is a folder named gateways
that includes the API gateway projects. You can add new API gateways to this folder.
Additionally, there is a folder named _templates
in the root directory. This folder contains templates you can use to create new microservices, API gateways, and applications. These templates can be customized according to your needs.
Adding a New API Gateway
To add a new gateway application to the solution, you can use the gateway
template. This template creates a new ASP.NET Core application with the necessary configurations and dependencies. Follow the steps below to add a new web application:
In ABP Studio Solution Explorer, right-click on the gateways
folder and select Add
-> New Module
-> Gateway
.
It opens the Create New Module
dialog. Enter the name of the new gateway application, specify the output directory if needed, and click the Create
button. There is a naming convention: the Module name should include the solution name as a prefix, and the use of the dot (.) character in the Module name is not allowed.
The new gateway is created and added to the solution. You can see the new application in the gateways
folder.
Configuring the appsettings.json
The new gateway application is use the YARP as a reverse proxy. You can configure the appsettings.json
file to define the routes and endpoints. The ReverseProxy
section in the appsettings.json
file includes the configuration for the reverse proxy. You can add new routes and endpoints to this section.
{
"ReverseProxy": {
"Routes": {
"AbpApi": {
"ClusterId": "Administration",
"Match": {
"Path": "/api/abp/{**catch-all}"
}
},
"AdministrationSwagger": {
"ClusterId": "Administration",
"Match": {
"Path": "/swagger-json/Administration/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/Administration" }
]
},
"ProductService": {
"ClusterId": "ProductService",
"Match": {
"Path": "/api/productservice/{**catch-all}"
}
},
"ProductServiceSwagger": {
"ClusterId": "ProductService",
"Match": {
"Path": "/swagger-json/ProductService/swagger/v1/swagger.json"
},
"Transforms": [
{ "PathRemovePrefix": "/swagger-json/ProductService" }
]
}
},
"Clusters": {
"Administration": {
"Destinations": {
"Administration": {
"Address": "http://localhost:44393/"
}
}
},
"ProductService": {
"Destinations": {
"ProductService": {
"Address": "http://localhost:44350/"
}
}
}
}
}
}
Configuring the OpenId Options
We should configure the OpenId options by modifying the OpenIddictDataSeeder
in the Identity
service. Below is an example of the OpenIddictDataSeeder
options for the PublicGateway
application.
Add the created gateway URL to the redirectUris parameter in the CreateSwaggerClientAsync
method in the OpenIddictDataSeeder
class.
private async Task CreateSwaggerClientAsync(string clientId, string[] scopes)
{
var webGatewaySwaggerRootUrl = _configuration["OpenIddict:Applications:WebGateway:RootUrl"]!.TrimEnd('/');
//PublicGateway Url
var publicGatewaySwaggerRootUrl = _configuration["OpenIddict:Applications:PublicGateway:RootUrl"]!.TrimEnd('/');
...
await CreateOrUpdateApplicationAsync(
name: clientId,
type: OpenIddictConstants.ClientTypes.Public,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Swagger Test Client",
secret: null,
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
},
scopes: commonScopes.Union(scopes).ToList(),
redirectUris: new List<string> {
$"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html",
$"{publicGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // PublicGateway redirect uri
...
},
clientUri: webGatewaySwaggerRootUrl,
logoUri: "/images/clients/swagger.svg"
);
}
Add the new gateway URL to the appsettings.json
file in the Identity
service.
{
"OpenIddict": {
"Applications": {
...
"PublicGateway": {
"RootUrl": "http://localhost:44382"
}
}
}
}
Configuring the AuthServer
We should configure the AuthServer for CORS and RedirectAllowedUrls.
"App": {
"SelfUrl": "http://localhost:***",
"CorsOrigins": "...... ,http://localhost:44382",
"EnablePII": false,
"RedirectAllowedUrls": "...... ,http://localhost:44382"
}
Creating Helm Chart for the New Gateway
If you want to deploy the new gateway to Kubernetes, you should create a Helm chart for the new application.
First, add the new gateway to the build-all-images.ps1
script in the etc/helm
folder. You can copy the configurations from the existing applications and modify them according to the new application. Below is an example of the build-all-images.ps1
script for the PublicGateway
application.
./build-image.ps1 -ProjectPath "../../gateways/public/Acme.Bookstore.PublicGateway/Acme.Bookstore.PublicGateway.csproj" -ImageName bookstore/publicgateway
Since we want to expose our gateway outside the cluster, we should add the host URL to the values.projectname-local.yaml
file in the etc/helm/projectname
folder. Below is an example of the values.bookstore-local.yaml
file for the PublicGateway
application.
global:
...
hosts:
...
publicgateway: "[RELEASE_NAME]-publicgateway"
For development purposes, we should also create TLS certificates for the new gateway. You can edit the create-tls-certificate.ps1
script in the etc/helm
folder to generate TLS certificates for the new gateway. Below is an example of the create-tls-certificate.ps1
script for the PublicGateway
application.
mkcert --cert-file bookstore-local.pem --key-file bookstore-local-key.pem "bookstore-local" ... "bookstore-local-publicgateway"
kubectl create namespace bookstore-local
kubectl create secret tls -n bookstore-local bookstore-local-tls --cert=./bookstore-local.pem --key=./bookstore-local-key.pem
Lastly, we should define the new application in the _helpers.tpl file in the etc/helm/projectname/templates
folder. You can copy the configurations from the existing applications and modify them according to the new application. Below is an example of the _helpers.tpl file for the PublicGateway
application.
{{- define "bookstore.hosts.publicgateway" -}}
{{- print "https://" (.Values.global.hosts.publicgateway | replace "[RELEASE_NAME]" .Release.Name) -}}
{{- end -}}
Afterwards, we need to create a new Helm chart for the new gateway. You can copy the configurations from the existing applications and modify them according to the new gateway. Below is an example of the publicgateway
Helm chart for the PublicGateway
application.
# values.yaml
image:
repository: "bookstore/publicgateway"
tag: "latest"
pullPolicy: "IfNotPresent"
swagger:
isEnabled: "true"
# Chart.yaml
apiVersion: v2
name: publicgateway
appVersion: "1.0"
description: Bookstore Public API Gateway
version: 1.0.0
type: application
# publicapigateway.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
spec:
selector:
matchLabels:
app: "{{ .Release.Name }}-{{ .Chart.Name }}"
template:
metadata:
labels:
app: "{{ .Release.Name }}-{{ .Chart.Name }}"
spec:
containers:
- image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: "{{ .Values.image.pullPolicy }}"
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
ports:
- name: "http"
containerPort: 80
env:
- name: "DOTNET_ENVIRONMENT"
value: "{{ .Values.global.dotnetEnvironment }}"
- name: "ElasticSearch__IsLoggingEnabled"
value: "{{ .Values.global.elasticSearch.isLoggingEnabled }}"
- name: "ElasticSearch__Url"
value: "http://{{ .Release.Name }}-elasticsearch:{{ .Values.global.elasticSearch.port }}"
- name: "Swagger__IsEnabled"
value: "{{ .Values.swagger.isEnabled }}"
- name: "AbpStudioClient__StudioUrl"
value: "{{ .Values.global.abpStudioClient.studioUrl }}"
- name: "AbpStudioClient__IsLinkEnabled"
value: "{{ .Values.global.abpStudioClient.isLinkEnabled }}"
- name: "ReverseProxy__Clusters__Administration__Destinations__Administration__Address"
value: "http://{{ .Release.Name }}-administration"
- name: "ReverseProxy__Clusters__ProductService__Destinations__ProductService__Address"
value: "http://{{ .Release.Name }}-productservice"
# publicapigateway-service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
spec:
ports:
- name: "80"
port: 80
selector:
app: "{{ .Release.Name }}-{{ .Chart.Name }}"
# publicapigateway-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
annotations:
nginx.ingress.kubernetes.io/rewrite-target: "/"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-buffer-size: "32k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
cert-manager.io/cluster-issuer: "letsencrypt"
spec:
ingressClassName: "nginx"
tls:
- hosts:
- "{{ (include "bookstore.hosts.publicgateway" .) | trimPrefix "https://" }}"
secretName: "{{ .Values.global.tlsSecret }}"
rules:
- host: "{{ (include "bookstore.hosts.publicgateway" .) | trimPrefix "https://" }}"
http:
paths:
- path: /
pathType: "Prefix"
backend:
service:
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
port:
number: 80
After creating the Helm chart, you can Refresh Sub Charts in the ABP Studio.
Then, update Metadata information right-click the gateway sub-chart, select Properties it open Chart Properties window. You can edit in the Metadata tab.
Add the service name Regex pattern Kubernetes Services in the Chart Properties -> Kubernetes Services tab.
Last but not least, we need to configure the helm chart environments for identity microservice and auth-server application.
# identity.yaml
# Add this line to the "env:" section
- name: "OpenIddict__Applications__PublicGateway__RootUrl"
value: "{{ include "bookstore.hosts.publicgateway" . }}"
# authserver.yaml
# Concat the following lines for "App__CorsOrigins" section
- name: "App__CorsOrigins"
value: "...,http://{{ .Release.Name }}-administration,{{ include "bookstore.hosts.publicgateway" . }}"