citusdata / activerecord-multi-tenant

Rails/ActiveRecord support for distributed multi-tenant databases like Postgres+Citus
MIT License
720 stars 99 forks source link

Any way to achieve two level multi-tenancy? #203

Open vit-panchuk opened 1 year ago

vit-panchuk commented 1 year ago

Hello,

Thanks for implementing and supporting such a good gem we use in many of our projects!

We use the gem in our project to separate events such as fairs and conferences as tenants. They share users and have out-of-tenant configurations such as domains and routes, etc:

image

What we want is to add a completely new level of tenancy: vendors. Vendors should have their own separate scope of events, users and configurations:

image

As my experience with the activerecord-multi-tenant and common sense I see "Variant 2" as a viable option, where I would have two multi_tenant tables: events and vendors. On the contrary, events still should be dependent on vendors and it can mix up some stuff with database queries and the MultiTenant.current_tenant state.

"Variant 1" seems to me more logical and readable but I have no idea where to start with modifying the gem to make it work. I suppose it is doable to make it possible to pass the tenant as an array of a vendor and an event model as so: MultiTenant.with([current_vendor, current_event]) but I wanted to ask you, the authors, what you think about it.

gurkanindibay commented 1 year ago

Hey @vit-panchuk

I can not see any difference between Variant 1 and Variant2. Could you explain the difference? And could you give me some examples in code blocks?

Thanks

vit-panchuk commented 1 year ago

@gurkanindibay, thx for the quick response!

Variant 1 would be considered something like that:

# vendors: id, name, etc
class Vendor < ApplicationRecord
end

# users: id, name, vendor_id, etc
class User < ApplicationRecord
  multi_tenant :vendor
end

# urls: id, domain, route, vendor_id, etc
class Url < ApplicationRecord
  multi_tenant :vendor
  has_one :event
end

# events: id, name, vendor_id, etc
class Event < ApplicationRecord
   multi_tenant :vendor
   belongs_to :url
end

# exhibitors: id, name, event_id, vendor_id, etc
class Exhibitor < ApplicationRecord
  multi_tenant :vendor, :event # obviously, pseudocode
end

The Event model itself would have multi_tenant :vendor.

The User model has only multi_tenant :vendor because users are shared among events of the same vendor but are not shared among vendors.

All models inside the event tenant (e.g. Exhibitor, SideEvent, Product) would need to have both event_id (already implemented) and vendor_id (needs to be added) references and they would know they are inside an event and inside a vendor.

Obviously, if I decide to go this way, MultiTenant.current_tenant and other MultiTenant API should be modified to recognize the hierarchy of tenants (vendors > events).

Variant 2:

# vendors: id, name, etc
class Vendor < ApplicationRecord
end

# users: id, name, vendor_id, etc
class User < ApplicationRecord
  multi_tenant :vendor
end

# urls: id, domain, route, vendor_id, etc
class Url < ApplicationRecord
  multi_tenant :vendor
  has_one :event
end

# events: id, name, vendor_id, etc
class Event < ApplicationRecord
   belongs_to :vendor # or maybe I can even use `multi_tenant :vendor` here?
   belongs_to :url
end

# exhibitors: id, name, event_id, etc
class Exhibitor < ApplicationRecord
  multi_tenant :event
end

This way I am getting two federated "islands" of tenants that know nothing about each other existence (e.g. Exhibitor model does not have a direct reference to a vendor but it is still inside an event). No MultiTenant modifications, etc, but I am not sure it actually possible to track two MultiTenant.current_tenant states simultaneously (one for current_vendor and another for current_event).

gurkanindibay commented 1 year ago

You need to add vendor column into exhibitor table even if it is not required directly by your model. It is required by active record multitenant I know it is a de normalization but still we need since we can support one tenant at a time

vit-panchuk commented 1 year ago

So you meant it actually possible to have something like this?:

# vendors: id, name, etc
class Vendor < ApplicationRecord
end

# events: id, name, vendor_id, etc
class Event < ApplicationRecord
   multi_tenant :vendor
end

# exhibitors: id, name, event_id, vendor_id, etc
class Exhibitor < ApplicationRecord
  multi_tenant :vendor
  multi_tenant :event
end