Base solution for your next web application

Activities of "JeffMH"

I am wondering if anyone has any experience in converting the framework to work with a database per tenant. With Azure Elastic DB coming out, this is a great way to architect a multi-tenant solution. Having worked with a very large Multiple-Tenant, Single Database before, I just can't see using this ever again. Very limited on what you can do to scale out. Not a fan of sharding. Of course, this is my preference and doesn't have to be everyone's, so choices are good!

<a class="postlink" href="https://channel9.msdn.com/Events/Build/2016/P522">https://channel9.msdn.com/Events/Build/2016/P522</a>

So, I am setting off on the journey to modify this to work with multiple databases. I am hoping some thought has been put into this by others and hoping to have a discussion about what it takes. I hope I can give back to the community with this. So, I don't mind doing some work here, but hoping I can get a little assistance with this to get started.

Here are my thoughts and I am curious to see if anyone else has thought about this or might make sure that my logic is correct:

Design

  • Host DB and TenantDB are the same root data model from a frameworks perspective. Can I leave out the new tables I create? Do I care if we can't? If I can't, maybe I need a different context and setup for the domain specific tables(Product, Customer, etc). Keep the migrations seperate.
  • Create the same tenant in both the Host and Tenant. Use Same TenantId in both to tie the two together. This allows me to do some fancy reporting at some point by tying the ID's together nicely. Maybe I have some ETL that extracts certain information about each tenant into a data warehouse. I would leave each database in multi-tenant mode. This also may allow us to do a hybrid scenario. Maybe a premium addition comes with it's own Database or you could just spread out your tenants to seperate DB's (forced sharding).
  • Store URL, DB Server and Database as attributes on a tenant. Tenant Settings won't work here since I need to know what store to get the settings from. This is determined based on URL.
  • When a login request comes in, check Host DB for the Tenant by URL and gather DB information for all connections. For Host URL, this would be the master database. For Tenant URL's, this would be the individual tenant database.
  • All remaining request use the Tenant settings to know which DB to hit.

Changes that I see:

  • Will need to set the Tenant connection dynamically. Need access to the settings from within the DbContext to set the connection dynamically.
  • In the Context, add some code to dynamically set the connection based on the current Tenant. Use Host by default if one isn't set.
  • What settings need to be in the Tenant, what settings need to be in the Host. Email, user settings, etc. May need to add a HostSettings. How does this impact SettingManager.
  • Would the Linked Accounts feature work? To me this is not a deal breaker. It's ok if this feature only works within a single tenant but would be nice if it worked accross tenants.

Any thoughts would be very helpful. I am sure you all have thought about this and any pointers would be helpful.

Wow. I am impressed. I have a timeline of about end of July for my project. If you think it might be possible to have this implemented by then, that is more than awesome.

I agree with you, that implementation sounds awesome. The contexts and the settings were my hangups. You have some specific settings classes and I was thinking there needed to be some concept around Host settings. Basically, you will bring in the idea of a Host DB. Sounds very good!

And as far as the filter goes. In reality, you could remove the filtering from the code when you are in a Multi-DB environment, but in reality, I would almost be in favor of leaving it in. Let's say I have a trial version of the product, maybe I want to run some tenants on one database. Like you said, the code should not have to be aware of the "separation" of the Host and the Tenants.

Anything I can help with just let me know. I will gladly help, test, anything I can do I am here.

Is it documented somewhere what I can remove from the web application if I want to do the SPA vs MPA? I would like to get rid of the MPA version to limit developer confustion. We removed Area, but I wanted to double check and make sure if there was anything else or I already got it wrong lol.

Thanks

We are using Typescript in our project but we did not convert anything from the framework. We had to declare a few variables from abp and AspZero(just declared them as any), but overall the experience has not been too bad. I don't want to convert the framework because the more we touch the framework, the harder it is to merge changes that are released.

Here is the first part of our custom typings file for the project:

declare var App: any;
declare var app: any;
declare var abp: any;
declare var appModule: angular.IModule;
declare var _: any;

Those few declares have got us through several screens. You can see we were able to add a type to appModule. If there are things we can add types to as we go we usually add them in. I think the "_" var is from underscore.js, but we have not checked to see if we can download a typing for that yet.

Here is a look at one of our controller constructors and how we inject the common items:

//- Injects dependencies into the constructor.  Used by Angular.
        static $inject = ["$scope", "$uibModal", "$stateParams", "uiGridConstants", "abp.services.app.pbcSection", "framework.services.kendo.datasourceservice"];
        
        constructor(
            private $scope: angular.IScope,
            private $uibModal: angular.ui.bootstrap.IModalService,
            private $stateParams: Framework.Interfaces.IFilterTextStateParamsService,
            private uiGridConstants: any,
            private pbcSectionService: any,
            private kendoDataSourceService: Framework.Services.Kendo.IDataSourceService) {

It would be nice if we had typed Services and DTO's. It would make the development experience a little nicer. I am strongly debating writing a T4 template that creates typings for our DTO / ApplicationServices. I think this would go a long ways to making the experience better.

Anyway, just wanted to pass along our strategy. We are only about a month into development so we are still learning.

Yes. That is exactly what we are doing. We are not touching any ABP or Zero files. We are just implementing our own controllers / modules / directives in TS.

What I did to do an example typescript controller, I copied there User list page into another folder and made a User list using TS. Once you dig in, it's not bad at all. If you just take the example walk-through they did and try and do that in Typescript, it will not be that bad. You will just find you need to declare some of the global variables they use. My post has a few in it. My post is a little vague, but if you just sit down and try and write a typescript controller in your own implementation, you will figure it out pretty quickly.

I have a question. I am finally getting around to migrating over to the new release(which is awesome by the way!). I guess a quick recap of what I am doing:

I am splitting the context. I have a HostDbContext (inherits from AbpZeroHostDbContext) and an TenantDbContext (inherits from AbpZeroTenantDbContext). I am making the Host database strictly Hosts, if I am going to have a Shared database, it will not be in the Host DB. This makes Migrations much simpler IMO.

So, as I am making my changes, I come accross the AbpZeroDbMigrator. I notice this object that it inherits from will run migrations against both the Host and the Tenant databases but the class is only accepting one Context / Migration Configuration:

using Abp.Dependency;
using Abp.Domain.Uow;
using Abp.MultiTenancy;
using Abp.Zero.EntityFramework;

namespace MyPortal.Framework.EntityFramework
{
    public class AbpZeroDbMigrator : AbpZeroDbMigrator<TenantDbContext, Migrations_Tenant.Configuration>
    {

I changed this code to use just the TenantDbContext and the Tenant Migrations but this will only make the Create Tenant part of the system work. The Migrator console application needs to execute Migrator code for both the Host and the Tenant.

I am not sure what was intended for me to do here. Do I create a AbpTenantDbMigrator and AbpHostDbMigrator and modify the console application accordingly? I could implement another MultiTenantMigratorExecuter that would take in both Migrators....

Anyway, I am just looking for guidance. I sort of worked my way through what I thought you were intending me to do but there is not alot of guidance on using Multiple contexts, how to use the current migrations with both contexts, etc. I think you all need to come up with an example of using seperate DB's so we can see what you intended for us to do when using multiple contexts.

I will try and create a document on what I did. I would really like to know if I am doing everything "as intended". Once I get this part worked out, hopefully I can get that done.

Thanks!

Thanks for the quick reply.

Yes, I tossed around keeping it one Context to simplify but I just liked the clean separation. I know it's a bit more complicated, but I just felt like I wanted the system to fail if I try and Add an Edition record to a Tenant DB. But I do respect keeping it one for simplification. I definitely did not want to use separate contexts with similar schemas.

I will make these changes and share them here in case anyone else wants to be brave like me :)

Before I start my next step, making all this work in Azure, have you all tried this yet? I am not 100% sure, but I have a feeling the Create tenant database process when adding a tenant won't work (not sure you can create DB's like that and not sure DTC works in Azure). Just wondering if you all may have worked through any of this.

Thanks again!

Question

In a multi-tenant environment, it would be nice to have a setting at the tenant level that was the "host" url.

host.somecompany.com.

As you parse the URL for the tenant name, if it was that setting value, then it would use the Host as the tenant. Right now, I have this out on Azure so I can go to the root website URL to get into the Host, but I would like to give my admin users a proper URL to get to the host.

If there is another way to do this let me know.

Answer

You are correct. I was thinking Host setting not tenant setting.

Question

I am on v0.9.6 of the framework:

I am running this code and when I run it, the IMustHaveTenant filter is not getting reset properly on exit of the Using.

using (UnitOfWorkManager.Current.SetTenantId(null))
{
    result = _tenantRepository.Get(tenantId.Value);
}

UnitOfWorkManager.Current.SetTenantId(tenantId.Value);

Before I execute the using, there is one FilterParameters in the MustHaveTenant data filter with tenantId = 7. Upon calling SetTenantId(Null), the filter parameter value tenantId turns to 0 as expected. Immediately after the using block, the MustHaveTenant FilterParameters is removed, there is no parameters in the data filter. I expect this to turn back to the way it was before I called SetTenantId(null). This is causing my next get from a MustHaveTenant table to fail because it passes Zero as the parameter value to Sql.

Right now, I am working around the issue by calling the SetTenantId(tenantId.value). This sets the parameters back to the way it was. But, I shouldn't need to call that. This is actually causing me several issues throughout the application.

I looked through the current code on GitHub and I don't see anything that jumps out at me as though it has changed in the latest version of the BaseUnitOfWork.

Here is a video of what is happening:

[https://1drv.ms/v/s!Am91meCOztkro7p10v62y081S_XS_w])

Thanks!

Showing 1 to 10 of 66 entries