ASP.NET Core: Microservices and Multi-tenancy

  • Be as small as possible
  • Focus on one aspect of the domain
  • Ability to deploy as a single unit
  • Ability to scale individually
  • Should be independent from other domain-centric services

Context

A microservice architecture mostly contains of the following elements:

  • Domain-centric microservices (products, orders, invoices,…)
  • An API Gateway to aggregate these services
  • A Service Discovery service to help discover these services
  • A message bus, to allow for communication between the services

Multi-tenancy within a Microservice

There are several ways to enable Multi-Tenancy for a microservice. For the most flexible approach, a plugin-based multi-tenancy system is most desirable. The plugin needs to respect a certain Contract, the contract will be called from the microservice in order to execute the action on the plugin. A plugin-framework will bridge the call between the microservice API (Host) and the plugin (Remote).

  • The method being called still exists in the same form (method signature)
  • The host and plugin are both targeting netcoreapp (version does not matter)

Use Case: Products

The implementation

  • Explicit loading by providing the name of the plugin assembly
  • Implicit loading by scanning a certain folder for candidate plugin assemblies
  • Staticly hardcode the assembly name of the plugin to be loaded (like MyPlugin.dll)
  • Let the context decide whatever assembly needs to be loaded (NameOfMyTenantBasedOnTheHTTPHeader.dll)

Configuration of Prise

We will get to the plugin implementations later, first we need to configure Prise. In order to do so, please look at the Startup.cs file for this example.

.WithDefaultOptions(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"))
.ScanForAssemblies(composer => 
composer.UseDiscovery())
.ConfigureSharedServices(sharedServices =>               
{
sharedServices.AddSingleton(Configuration);
})
.WithAssemblySelector<TenantAssemblySelector<IProductsRepository>>()

Implementing the OldSQLPlugin

Now, we’re ready to start implementing our plugin, starting with the plugin that will be loaded for tenants that are using a legacy SQL server on-premise.

  • an Internal Constructor for the plugin
  • a PluginBootstrapper
  • a Plugin Factory Method
dotnet publish

Our second Plugin, the SQLPlugin

Whilst supporting the old on-premise SQL Servers for our Tenant 1 users, we were in the process of migrating some databases to the cloud, to Azure SQL Databases. For this, we needed another plugin, for the users for Tenant 2.

Our third plugin, using Table Storage

For our new tenants, we’re rolling out a TableStoragePlugin. Their data will be stored in an Azure storage account, using storage tables. This repository requires a bit more setup, but semantically, should be the same as the other plugins. We need a class that implements the IProductsRepository (Contract), an internal constructor, a PluginFactory method and a PluginBootstrapper.

Deploying our plugin-based Microservice

Deploying such a microservice is rather simple, the Products.API can be deployed simply by doing a dotnet publish for the Release configuration and copying it to your web application (IIS or Azure). In addition, there needs to be a Plugins directory in the published web application. This will contain all the published plugins that can be loaded at runtime, based on the Tenant HTTP Header.

Contents of the deployed Products.API Microservice
Contents of the Plugins directory
The TenantAssemblySelector defaults to the OldSQLPlugin, when no Tenant HTTP Header was provided.

References

OldSQLPlugin code can be found here: https://github.com/merken/Prise.Examples/tree/master/MultiTenantMicroserviceWithDiscovery/Plugins/OldSQLPlugin

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store