Awesome
Book Fast (Docker)
A sample demonstrating how to implement a containerized multitenant facility management and accommodation booking application. It uses microservices architecture and relies on a bunch of Azure services.
Features
Architecture
- 4 bounded contexts
- CQRS and DDD (with reliable domain events)
- ASP.NET Core 6, Blazor, gRPC
- Kubernetes and Docker Compose
Build and deployment
- GitHub actions
- Azure Container Registry
- Helm
Security
- Multitenant Azure AD organizational accounts
- Azure AD B2C authentication for customers
- OpenID Connect and OAuth2
Azure services
- Azure Kubernetes Service
- Azure Container Registry
- Azure SQL databases
- Azure Storage
- Azure Service Bus
- Azure Search
- Application Insights
- Azure KeyVault
Running the applicaton
The application can be run locally in Docker Compose to facilitate VS debugging experience. In Staging/Production modes it's supposed to be run in a Kubernetes cluster.
Running locally in Docker Compose
docker-compose -f "docker-compose.yml" -f "docker-compose.development.yml" --no-ansi build
docker-compose -f "docker-compose.yml" -f "docker-compose.development.yml" --no-ansi up -d --no-build --force-recreate --remove-orphans
Running in Kubernetes (AKS)
There are GitHub workflows to build and deploy the application. It is expected that the following variables are set in GitHub secrets:
REGISTRY_NAME
- name of the docker registry to push images to (e.g. myregistry.azurecr.io)REGISTRY_USERNAME
- username to log in to the registryREGISTRY_PASSWORD
- password to log in to the registryKEYVAULT_NAME
- name of the KeyVault (see below)USER_ASSIGNED_CLIENT_ID
- user-assigned managed identity that is configured to access KeyVault (list and get permissions)ACME_EMAIL
- a valid email address (Let's Encrypt will use this to contact you about expiring certificates and issues related to your account)DNS_BACKEND
- a URL for the backend API (e.g. backend.51.138.82.127.nip.io)DNS_FILES
- a URL for the backend API (e.g. files.51.138.82.127.nip.io)CLUSTER_RESOURCE_GROUP
- resource group name of the AKS clusterCLUSTER_NAME
- name of the AKS clusterAZURE_CREDENTIALS
- credentials for the AKS set context action
The credentials can be obtained by creating a new principal:
az ad sp create-for-rbac --name "myApp" --role contributor \
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \
--sdk-auth
# The command should output a JSON object similar to this:
{
"clientId": "<GUID>",
"clientSecret": "<STRING>",
"subscriptionId": "<GUID>",
"tenantId": "<GUID>",
"resourceManagerEndpointUrl": "<URL>"
}
If you already have a principal you can construct the credentials JSON using appropriate values. For the resourceManagerEndpointUrl
use https://management.azure.com/
.
Configuration
The application supports Development, Staging and Production environments. In Development it relies on User Secrets and in Staging/Production it uses Azure KeyVault.
Here's a short description of configuration parameters:
{
"ApplicationInsights": {
"InstrumentationKey": "Application Insights resource instrumentation key"
},
"Authentication": {
"AzureAd": {
"Instance": "Your Azure AD instance, e.g. https://login.microsoftonline.com/",
"Audience": "BookFast API AppId in Azure AD, e.g. https://devunleashed.onmicrosoft.com/book-fast-api",
"ValidIssuers": "Comma separated list of tenant identifiers, e.g. https://sts.windows.net/490789ec-b183-4ba5-97cf-e69ec8870130/,https://sts.windows.net/f418e7eb-0dcd-40be-9b81-c58c87c57d9a/"
}
},
"ServiceBus": {
"ConnectionString": "Connection string to Service Bus topic",
"Facility": {
"NotificationQueueConnection": "Connection string to the Facility service notification queue"
},
"Booking": {
"NotificationQueueConnection": "Connection string to the Booking service notification queue"
}
},
"Data": {
"Azure": {
"Storage": {
"ConnectionString": "Connection string to an Azure storage account"
}
},
"DefaultConnection": {
"ConnectionString": "Connection string to a SQL database"
}
},
"Search": {
"QueryKey": "Azure Search query key",
"AdminKey": "Azure Search admin key",
"ServiceEndpoint": "Azure Search service endpoint",
"IndexName": "Azure Search index"
}
}
User Secrets
All services are configured to use the same User Secrets ID for simplicity. So it's enough to configure User Secrets with the settings shown above once to be used in Development environment.
Azure KeyVault
Accessing KeyVault depends on how you run the application (in Kubernetes or in Docker Compose).
Kubernetes
The application has been designed to run in Azure Kubernetes Service (AKS). In Staging and Production modes services are configured to use pod identity called my-pod-identity
(see deployment files). This article provides details on how to set up pod identity in the application namespace.
The pod identity in question is expected to reference a user-assigned managed identity that is configured to access KeyVault (list and get permissions).
Docker Compose
Currently Docker Compose is supposed to be used locally on a dev box. In Production mode (that is used for demo purpose only) the following directory gets mounted to containers: ${OneDrive}/dev/BookFast/KeyVault
The directory is expected to contain 4 plain text files:
- tenantId
- clientId
- clientSecret
- keyVaultName
File names are pretty much self explonatory. Just make sure they don't contain extra spaces or line feeds. The app is going to try to create a ClientSecretCredential
to authenticate to KeyVault.
Alternatively it can also try to use a DefaultAzureCredential
if there is a KeyVaultName
environment variable. But in this case make sure to also define the following variables for DefaultAzureCredential
to work:
- AZURE_CLIENT_ID
- AZURE_CLIENT_SECRET
- AZURE_TENANT_ID
In both cases you need to have a corresponding principal in your Azure AD tenant that is also configured to access KeyVault secrets (list and get permissions).
Azure AD
Azure AD is used for organizational accounts of facility providers. You will need two applications in Azure AD: one for the APIs (Book Fast API app) and one for the web (BookFast app). Both applications should have multitenant support enabled. BookFast should have a delegated permission to access BookFast API app. If you're new to Azure AD the following post are going to help you out:
- Protecting your APIs with Azure Active Directory
- Enabling multitenant support in you Azure AD protected applications
Both apps have a user role called 'Facility Provider' that should be assigned to users to enable them to edit facilities. Please have a look at this post to understand how application and user roles are configured in Azure AD.
Azure AD B2C
Customer accounts are managed in Azure AD B2C. It supports self sign up, profile editing and 3rd part identity providers.
You will need to create a B2C tenant and an app. You will also need to policies:
- Sign in or sign up policy
- Profile edit policy
You may also find this post useful when setting you your application.
SQL Database
BookFast.Facility.Data
and BookFast.Booking.Data
projects contain EFCore migrations to set up you SQL database schema.
Service Bus
Azure Service Bus is used as a message broker for integration events.
Please make sure to provision a single topic with 3 subscriptions:
- Booking
- Facility
- SearchIndexer
Also provision 2 notification queues:
- bookfast-facility-notifications
- bookfast-booking-notifications
Azure Search
BookFast.Search.Adapter can be run from the command line as dotnet run provision
in order to create an index in your Azure Search service. It will require the following parameters to be defined in user secrets:
- Search:ServiceEndpoint
- Search:AdminKey
- Search:IndexName