Skip to content

Building a tenant change service

Jon P Smith edited this page Jul 11, 2022 · 5 revisions

The AuthP tenant admin has methods to create, change, delete and so on a tenant for you. But when one of these tenant admin methods are called you need you nearly always need to apply a change the tenant's data, for instance if you delete a tenant you would expect all the tenant's data should be deleted too. The solution is to create a class that follows the ITenantChangeService and I refer to this class as a tenant change service.

Its not a trivial task to create a tenant change service, but there are many examples (currently 3) in the AuthP repo which should help you. This document starts with the why and the design of tenant change service and then covers each of the example implementation of the ITenantChangeService interface.

NOTE: Remember that you need to register your tenant change service during the registering the AuthP in your ASP.NET Core - see Registering your tenant change service section in the Multi tenant configuration document.

The design of the ITenantChangeService

The ITenantChangeService interface defines various methods which are called from the tenant admin once the AuthP's Tenant entity as been updated. Then the correct in your registered tenant change service is called. At the end of this process the AuthP's Tenant entity will be changed and your tenant change service has the necessary changes to the tenant's data.

To ensure that the two changes are in step the tenant admin method calls your tenant change service method within a transaction. This means if your tenant change service method fails then both database's changes are rolled back. The diagram below shows how translations within the AuthP DbContext and the tenant data DbContext ensures that the two DbContext are in step.

TenantChangeService process

The implementation of your tenant change service

There are six multi-tenant types implementation of the ITenantChangeService (NOTE: The last two are referred as hybrid). They are:

TenantType num databases num tenants per db Example change service
SingleLevel one database many see InvoiceTenantChangeService
HierarchicalTenant one database many see RetailTenantChangeService
SingleLevel many databases one (Sharding) see ShardingSingleDbContext
HierarchicalTenant many databases one (Sharding)
SingleLevel many databases many & one (Hybrid) see ShardingSingleDbContext
HierarchicalTenant many databases many & one (Hybrid)

The ITenantChangeService has methods to cover all these six multi-tenant types, but you only need to implement the methods that your type needs. See each method and when it is called and what it has to do.

Constructor of the tenant change service - used in all six multi-tenant types

This will provide you with a tenant DbContext to access your tenant data. There are two options

  • For the "one database" types you can inject your tenant DbContext to use in this service. This service won't have the correct DataKey, so you will use the IgnoreQueryFilters method and then use a where clause to select the correct tenant data.
  • For the "many databases" types you should inject the DbContextOptions<YourTenantDbContext>. Using this and the DataKey found in the method's properties you can then create a tenant DbContext to use.

CreateNewTenantAsync

This is called on the creation of a new tenant. Its job is to set up any tenant data you need added to the tenant DbContext, for instance in Example3 the tenant DbContext has a entity called CompanyTenant which is used to display the name of the tenant.

NOTES:

  • For the many databases multi-tenant types you should migrate the database if it hasn't been used yet. See ShardingSingleDbContext example.
  • If the AuthP's Tenant's HasOwnDb is true, then its worth checking that there's other tenant data in the database

SingleTenantUpdateNameAsync - used when the TenantType has the SingleLevel member set

This is called when the tenant's name is changed. This allows to update any entities in the tenant DbContext which contains the name.

SingleTenantDeleteAsync - used when the TenantType has the SingleLevel member set

This is called to delete all of the tenant's data. A delete of tenant's data when there are other tenants can be tricky to write as you need to take account of the relationships and you can't just reset all the tables.

HierarchicalTenantUpdateNameAsync - used when the TenantType has the HierarchicalTenant member set

This is called when the tenant's name is changed, but in a hierarchical multi-tenant structure the renaming of a tenant requires the renaming of any children tenants. Therefore this method takes in a list of all the AuthP Tenants that were changed.

HierarchicalTenantDeleteAsync - used when the TenantType has the HierarchicalTenant member set

This is called when a tenant, and its children need to be deleted in AuthP and you need to delete data in the tenant's DbContext. Therefore this method takes in a list of all the AuthP Tenants that should be deleted, which are ordered with lowest child tenants first.

MoveHierarchicalTenantDataAsync - used when the TenantType has the HierarchicalTenant member set

This moves a tenant to a new level in the hierarchical data - see this section for a good definition and diagram. To do this you work through the list AuthP's Tenants and change the DataKey of the tenant DbContext entities.

MoveToDifferentDatabaseAsync - used when using the hybrid approach

This moves the data in one database to another database. Usually you do this to change a tenant from "many per db" to "one per db". This is very complex and you might not want to provide this feature - see the ShardingTenantChangeService tenant change service with implements this, but on a simple database.

NOTES:

  • You should migrate the database if the database hasn't been used yet. See ShardingSingleDbContext example.
  • If the AuthP's Tenant's HasOwnDb is true, then its worth checking that there's other tenant data in the database

Additional resources

Articles / Videos

Concepts

Setup

Usage

Admin

SupportCode

Clone this wiki locally