-
Notifications
You must be signed in to change notification settings - Fork 930
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multi-Tenancy: Implement tenant per Database strategy #2108
Conversation
src/NHibernate/Cfg/Loquacious/IDbIntegrationConfigurationProperties.cs
Outdated
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Not sure if I added |
Sorry for being late here, but I personally think that the Hibernate structure is much cleaner. However, this version can certainly be wrapped away into extension methods or app unique "session providers", and made to look like the Hibernate version. That's not saying that this is bad or that the Hibernate implementation is good. Like a lot of the Hibernate code it's quite bad. What it boils down to is that NHibernate's extensibility should allow for something like this, without requiring very specific code changes. Injecting strategies, providers and decorators should do the trick. |
Could you be more specific. As it's unclear to me which part and why would you want to wrap in hibernate version. So could you describe some scenario that cleaner in hibernate? |
Gladly. I just think that bringing things like a ConnectionProvider up to the topmost API feels a bit awkward. Multi tenancy is to me a more infrastructural concern. Just "tagging" the session, or even better, use the (I)CurrentTenantIdentifierResolver method that Hibernate does, would be more flexible, I think. It would also allow for different kinds of tenancy strategies, which may not necessarily use different connection providers (just as the Hibernate implementation does). My objection really isn't with your implementation. It rather stems from a frustration with the old codebase in the configuration and execution pipeline, which is such a feast of bad practices (all inherited from Hibernate). With a properly extensible API, adding something like multi tenancy wouldn't require core changes. A similar extension, NHibernate.Shards, would also be able to inject itself with ease. Maybe something for 7.0 |
I don't see it that way. It just means that you split your tenant specific logic across different classes. And it means user needs to configure NHibernate in more places and implement more Nhibernate specific interfaces. That doesn't look cleaner to me. Now all that can be implemented in user code using convinient for user data models and be wrapped in whatever way he likes.
As I already said you don't need to provide tenant connection provider for other tenancy strategies - just add different ctor to |
"Weight" is not the issue. It's placement of responsibilities. It can be wrapped away. I just think that it should be by default.
OK, we're different. To me, it's cleaner.
It may need that, but IMHO adding ctors isn't a convenient extensibility model. It's clearly a matter of taste, but to me:
|
A bit late to this, but isn’t this handled perfectly well by the NHibernate Shards project? We use that perfectly well already to implement multi-tenancy across databases. Wouldn’t it be better to merge that into the base NHibernate code base, or add additional effort to that project? |
All you need for multi-tenancy is to create separate session factory per tenant. Using NHibernate.Shards for this is very strange decision. But well - use whatever makes you happy. And this PR allows to share the same session factory for all tenants. |
bd03db8
to
ea4c208
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have only reviewed the tests for now.
I share at least a part of @gliljas viewpoint about opening session. I think we should only provide the tenant id string for opening a session for a tenant. Supplying the whole tenant configuration at each session opening looks like a code smell to me.
I would rather have to add tenant configurations to the session factory first, which would have a (maybe concurrent) dictionary of them, then start opening session by just specifying their tenant id.
src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs
Outdated
Show resolved
Hide resolved
src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs
Outdated
Show resolved
Hide resolved
src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs
Outdated
Show resolved
Hide resolved
src/NHibernate.Test/MultiTenancy/DatabaseStrategyNoDbSpecificFixture.cs
Outdated
Show resolved
Hide resolved
I don't know anything less convenient than single string param for whole configuration. That's some
It's basically tenantIdentifier + tenantConnectionString. And it's wrapped in class to simplify further extensions for us without breaking changes
Introducing yet another dictionary when it can be avoided - again makes no sense to me and looks like code smell to me too. Update Yeah it's pretty much the same arguments I've already said to gliljas. But my big reluctance on using string key is based on personal experience where such design would lead to some awkward code to "deconstruct" string for retrieving tenant configuration. Also I don't like idea of keeping tenant configurations in session factory dictionary. Sometime it's not something that can be/should be populated beforehand - tenant configuration can be dynamic, obtained on request and there is no reasons to keep it after end of request/session polluting session factory |
For me, building some string based convention for supplying settings in a string to be parsed is not to be done. I am speaking about nothing else than a tenant identifier, with each tenant to be configured at the session factory level. I do not understand the need for supplying all the tenant settings at each session opening. It looks unpractical to me.
Maybe then we should have both possibilities. |
So OK it's about simple scenario when you have small static list of tenant configurations. It's not very useful for scenarios when tenant configurations are dynamic.
And still unless some part of code is bound to be executed for given tenant - you need some code that constructs required
It's extensible. You can easily wrap this code to be executed the way you like. Via string identifier or via some custom user class. That's how it supposed to work. And why to add additional dictionary lookup in our code on each session opening if we can avoid it. It can easily be added by user if needed.
As I already said I think it's trivial task that can be done in user code. |
I remain unconvinced. This "supply the whole configuration at each session opening" pattern allows to supply inconsistent configurations, like supplying the same tenant identifier with different connections. @hazzik, @maca88, may we have your thoughts about this PR in its current state? |
I believe it's done - Also added default It doesn't have built in ability to specify static tenant configuration - but again it's trivial task. And hibernate also doesn't provide such ability. Example of static configuration : public class StaticMultiTenantConfigurationConnectionProvider : DefaultMultiTenancyConnectionProvider
{
private Dictionary<string, string> _dic = new Dictionary<string, string>
{
{"tenant1", "tenantConnectionString1"},
{"tenant2", "tenantConnectionString2"},
};
protected override string GetTenantConnectionString(TenantConfiguration configuration)
{
return _dic[configuration.TenantIdentifier];
//Or something like:
//return ConfigurationProvider.Current.GetNamedConnectionString("tenantConnectionFor." + configuration.TenantIdentifier);
}
} |
I revisited the PR and made some refactoring that I would propose:
|
IMO it's useful to have it even for Discriminator case and it simplifies multi-tenant setup. But fine by me if others members agree it's better not to have it. Also then it seems it makes sense to add
Why? It was added explicitly for convenience. It's been bugging me with Agree with other changes. |
Actually scratch it - let's get rid of |
Pushed all changes except for removing |
Ok, now I'm taking a look... :) |
Yeah about time. I'm done polishing it :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am now overall agree with this PR, excepted for a few minor points.
May you also update the first comment (description of this PR) for reflecting this PR current way of working for the feature?
I believe I resolved all review suggestions and made last small change - exposed session |
there is some example of this working? I'm trying to implement MultiTenancyConnectionProvider, but it seems to be more required configuration than just config.DataBaseIntegration(x =>
{
x.MultiTenancy = MultiTenancyStrategy.Database;
x.MultiTenancyConnectionProvider<MyMultiTenancyConnectionProvider>();
}); It should be used? Or it is better to create a SessionFactory for each tenant, like some people said? |
Fixes #974
Based on hibernate implementation (related commits) with some modifications (omitted unused classes, interface members and modified tenant session configuration)
Example of tenant configuration:
multi_tenancy.strategy
andmulti_tenancy.connection_provider
NHibernate configuration properties (hibernate-configuration\session-factory
):Or example with by code configuration:
Where
MyMultiTenancyConnectionProvider
must implementIMultiTenancyConnectionProvider
. Can be implemented based on providedAbstractMultiTenancyConnectionProvider
. Example of implementation:You can create subclass of
TenantConfiguration
and provide some specific details about you tenant configuration and use it to obtain proper tenant connection inside yourIMultiTenancyConnectionProvider
implementation:Current tests are not exactly multi-tenant as executed on the same database. For proper multi-tenant tests we need additional db which I'm not sure how to properly add (and I think should be done as separate PR). Still current tests make sure that different connection string is used for different tenants (by injecting AppName on SQL Server) and make sure that second-level/query cache is separated for different tenants. Let me know if I missed something to test.
Things to implement in future: