ABP Framework version: v9.0.4
- UI Type: Angular
- Database System: EF Core (PostgreSQL)
Hi,
We are currently working on a solution that involves Editions, where each Edition should include specific Features. These Features are to be represented as service modules in our architecture. We intend to set up dynamically a separate database for each tenant for every service module they are using.
What is the best practice to implement this please?
Thanks
11 Answer(s)
-
0
and if i have an EditionId, how to get the features related to it?
-
0
Hi,
ABP Framework allows tenants to use separate databases and Saas module has an UI to separate tenant databases manually at runtime. But there is no built-in feature for creating databases dynamically whenever a tenant created or edition changes for a tenant. You have to implement it in your application according to your logic.
Let me show the TenantAppService.cs implementation from the Saas Module:
If you create each tenant with
ConnectionStrings
property is configured and make sure distributed event is published, you can achieve this logic.You don't have to write all the codes here, I just wanted to explain the logic. You can easily replace TenantAppService in your application and configure connection string for each tenant dynamically:
[Dependency(ReplaceServices = true)] [ExposeServices(typeof(ITenantAppService))] public class CustomTenantAppService : TenantAppService { public CustomTenantAppService(ITenantRepository tenantRepository, IEditionRepository editionRepository, ITenantManager tenantManager, IDataSeeder dataSeeder, ILocalEventBus _localEventBus, IDistributedEventBus distributedEventBus, IOptions<AbpDbConnectionOptions> dbConnectionOptions, IConnectionStringChecker connectionStringChecker) : base(tenantRepository, editionRepository, tenantManager, dataSeeder, _localEventBus, distributedEventBus, dbConnectionOptions, connectionStringChecker) { } public override Task<SaasTenantDto> CreateAsync(SaasTenantCreateDto input) { // 👇 Something similar input.ConnectionStrings.Default = $"Server=localhost;Database=MyProjectName_{input.Name.Normalize()};User=root;Password=123456;"; return base.CreateAsync(input); } }
-
0
I have two questions plz:
1- How can we specify which service tenant database a microservice should work with (e.g., Microservice_Tenant1_Products, Microservice_Tenant2_Products, etc.)?
2- When we add new database migrations at the service, how should we handle them considering that each tenant has its own separate service database? how this migration will be applied to all service tenant dbs?
-
0
Hi
How can we specify which service tenant database a microservice should work with (e.g., Microservice_Tenant1_Products, Microservice_Tenant2_Products, etc.)?
Do you use micro-serivce template currently or you'll separate your services later? By default we don't suggest using different databases for tenants in the micro-service template currently, if you wish you can rea the discussion from here: https://abp.io/support/questions/8692/Problems-configuring-a-separate-database-for-each-tenant-in-a-microservices-application
If your template is not micro-service, you can customize
MultiTenantConnectionStringResolver
in your applicationhttps://github.com/abpframework/abp/blob/74d516829be7f05cfae7d4a67f18591b41e5446a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs#L76-L79
using System; using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; namespace AbpSolution1.AuthServer; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IConnectionStringResolver), typeof(DefaultConnectionStringResolver))] public class MicroServiceConnectionStringResolver : MultiTenantConnectionStringResolver { private readonly ICurrentTenant _currentTenant; private readonly IServiceProvider _serviceProvider; public MicroServiceConnectionStringResolver(IOptionsMonitor<AbpDbConnectionOptions> options, ICurrentTenant currentTenant, IServiceProvider serviceProvider) : base(options, currentTenant, serviceProvider) { _currentTenant = currentTenant; _serviceProvider = serviceProvider; } public override async Task<string> ResolveAsync(string? connectionStringName = null) { // ⚠️ Implement Your own logic, this is for demonstration var prefix = "Microservice"; var postfix="Products"; var tenant = _currentTenant.Name; // ... return $"Server=myServerAddress;Database={prefix}_{tenant}_{postfix};Trusted_Connection=True;" } }
When we add new database migrations at the service, how should we handle them considering that each tenant has its own separate service database? how this migration will be applied to all service tenant dbs?
DbMigrator project applies migrations for all the tenants in the host database. So whenever you run DbMigrator, all the databases will be migrated and data seed will be executed
-
0
Thanks for your advice.
I've made some adjustments to my architecture as follows:
- I now have an ABP microservice solution that should handle authentication.
- Additionally, I've set up another layered ABP solution for each used service.
For the microservice solution, I'll use a shared database, and for each layered ABP solution, I'll create a separate database for each tenant.
Is this approach okay for now?
Additionally, I would like to configure the layered ABP solutions to authenticate using the main authentication system in the microservice solution. I've already updated the authentication URL in the host's appsettings and the Angular environment file, but errors appeared and redirection is not working properly. What are other changes or configurations I need to make to accomplish this?
Looking forward to your input!
-
0
Any update please?
-
0
Thanks for your advice.
I've made some adjustments to my architecture as follows:
- I now have an ABP microservice solution that should handle authentication.
- Additionally, I've set up another layered ABP solution for each used service.
For the microservice solution, I'll use a shared database, and for each layered ABP solution, I'll create a separate database for each tenant.
Is this approach okay for now?
Yes it's ok. The limitation in the microservice scenario is
Inbox/Outbox
pattern. If your database doesn't have Inbox or Outbox events table, you can separate however you wish. But while using outbox pattern like in microservice, all the events have to be processed in the same ouw and transaction, and events does not have tenant data or they cannot be separated into databases without handling them and checking which tenant uses which database. So in a summary, only limitation is Inbox/Outbox pattern. If you application doesn't use it, you can separate without any limitation -
0
Additionally, I would like to configure the layered ABP solutions to authenticate using the main authentication system in the microservice solution. I've already updated the authentication URL in the host's appsettings and the Angular environment file, but errors appeared and redirection is not working properly. What are other changes or configurations I need to make to accomplish this?
thanks, but what about this issue please?
-
0
Hi
How can we specify which service tenant database a microservice should work with (e.g., Microservice_Tenant1_Products, Microservice_Tenant2_Products, etc.)?
Do you use micro-serivce template currently or you'll separate your services later? By default we don't suggest using different databases for tenants in the micro-service template currently, if you wish you can rea the discussion from here: https://abp.io/support/questions/8692/Problems-configuring-a-separate-database-for-each-tenant-in-a-microservices-application
If your template is not micro-service, you can customize
MultiTenantConnectionStringResolver
in your applicationhttps://github.com/abpframework/abp/blob/74d516829be7f05cfae7d4a67f18591b41e5446a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/MultiTenantConnectionStringResolver.cs#L76-L79
using System; using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; namespace AbpSolution1.AuthServer; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IConnectionStringResolver), typeof(DefaultConnectionStringResolver))] public class MicroServiceConnectionStringResolver : MultiTenantConnectionStringResolver { private readonly ICurrentTenant _currentTenant; private readonly IServiceProvider _serviceProvider; public MicroServiceConnectionStringResolver(IOptionsMonitor<AbpDbConnectionOptions> options, ICurrentTenant currentTenant, IServiceProvider serviceProvider) : base(options, currentTenant, serviceProvider) { _currentTenant = currentTenant; _serviceProvider = serviceProvider; } public override async Task<string> ResolveAsync(string? connectionStringName = null) { // ⚠️ Implement Your own logic, this is for demonstration var prefix = "Microservice"; var postfix="Products"; var tenant = _currentTenant.Name; // ... return $"Server=myServerAddress;Database={prefix}_{tenant}_{postfix};Trusted_Connection=True;" } }
When we add new database migrations at the service, how should we handle them considering that each tenant has its own separate service database? how this migration will be applied to all service tenant dbs?
DbMigrator project applies migrations for all the tenants
-
0
Additionally, I would like to configure the layered ABP solutions to authenticate using the main authentication system in the microservice >solution. I've already updated the authentication URL in the host's appsettings and the Angular environment file, but errors appeared and >redirection is not working properly. What are other changes or configurations I need to make to accomplish this?
Looking forward to your input!
i m asking about the auth issue please.
How can i make the layered ABP solutions (that will be my services) authenticate using the main auth existing in the main (microservice solution), not its existing auth?
-
0
You can use any IdentityModel Client package to use an OpenId Connect Authentication Server.
ABP already provides a package for it named
Volo.Abp.Http.Client.IdentityModel
https://abp.io/package-detail/Volo.Abp.Http.Client.IdentityModelIt has dependency to https://github.com/DuendeArchive/IdentityModel package. So you can follow it's instructions to configure it for more complex scenarios.
When whenever you use ABP's package and add it do
[DependsOn]
attribute, you can configure easily defining your credentials in appsettings something like this:"AuthServer": { "Authority": "https://localhost:44319", "RequireHttpsMetadata": true, "ClientId": "AbpSolution51_Web", "ClientSecret": "1q2w3e*" },
And make it configure in the
*Module.cs
file of your application:context.Services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies", options => { options.ExpireTimeSpan = TimeSpan.FromDays(365); options.CheckTokenExpiration(); }) .AddAbpOpenIdConnect("oidc", options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata"); options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.ClientId = configuration["AuthServer:ClientId"]; options.ClientSecret = configuration["AuthServer:ClientSecret"]; options.UsePkce = true; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("roles"); options.Scope.Add("email"); options.Scope.Add("phone"); options.Scope.Add("AbpSolution51"); });
Also you can create a new tiered application to see how it is configured properly.