Hi everyone! It's been a minute :slightly_smiling...
# general
e
Hi everyone! It's been a minute ๐Ÿ™‚ i am eager to get back into the solidus community and we're going to see how we can get involved since we're going to be depending on solidus (and all you fine folks) My time at care/of has come to an end, and now I'm onto a new company! This time I think I want to combine row level security (gem) or something of the sort with solidus to allow me to create a multi-tenant solidus e-commerce app (using postgres for a db). I was originally going to try to use Apartment to handle this, but RLS seems to be > one schema per tenant ๐Ÿคท I am creating a SaaS product where I don't want to be one SQL mis-step away from exposing one tenants' data to another. Has anyone ever tried to so something like that?
๐Ÿ‘‹๐Ÿผ 1
c
Hey @Eric Gross ๐Ÿ‘‹ Good to see you again! Glad to hear youโ€™re continuing on your journey to build on Solidus!
No experience with RLS or running multi-tenant Solidus stores, but one nice thing you get with using separate schemas for each tenant is that separation. If you are worried about keeping the data completely separate, I would imagine that would be the safer option. It also would give you the ability to possibly scale each tenant independently of others. I believe thatโ€™s the โ€œshopifyโ€ model as well, fwiw.
To get RLS in Solidus I imagine you would need to modify the schema pretty heavily, which for me seems like a drawback of that approach, but maybe there are other ways to achieve that.
e
RLS seems to be better than separate schemas based on the experience of those who tried to scale Apartment It looks as if adding a tenant_id column to every table is all I would need to do to the solidus schema. I'm trying this now for the orders table. But the problem I'm having is that when I try to save a new one via the backend (a cart) I haven't figured out how to set the tenant_id. RLS refuses to save a record that the user can't select ๐Ÿ‘ Any ideas?
I would be interested in creating a solution that's useful for others as well. It would be cool to make a gem that added multi tenancy to solidus in this fashion
๐Ÿ‘๐Ÿผ 1
b
If you're creating one tenant per use, I would recommend using Citus over Apartment so that you can partition all of the data appropriately since you'll need to provide the distribution id for the sql to access the relevant data anyways. If you want strong security, it might be better to create a new schema per whole Solidus store and write your core rails application in the public schema.
e
Citus looks very cool but azure isn't happening
RLS looks usable and secure enough i think
b
Yeah, you'll need to create your own managed PG instance since Microsoft acquired Citus Data.
e
I agree that looks like a cool solution. But RLS looks good enough for me on RDS
What would be the easiest way to tap into the back end when I'm creating a new cart to set the tenant_id? The ideal solution would help me set the tenant_id for every save of every model
b
Either everything is associated to a spree_stores.id or you can make your own tenant model to articulate that business logic.
c
Are you using a library for implementing RLS in Rails like this https://github.com/suus-io/rls_rails? Presumably you are going to have to modify the Solidus controllers to provide the tenant id to any AR calls that create records
๐Ÿ‘ 1
e
I literally want to add the column to every table ๐Ÿ˜ญ
this 1
b
Presumably you are going to have to modify the Solidus controllers to provide the tenant id to any AR calls that create records
Could be done through the api key in a header if doing API.
๐Ÿ‘๐Ÿผ 1
c
Yeah, but still going to need to mangle it into the attributes passed to AR right?
e
Yeah
I wonder if I could patch active record to do it for me all the time.
I'm initially trying to get it worked via the backend for my sanity.
c
Well you can add any shared logic to your
ApplicationRecord
class and your
ApplicationController
๐Ÿ‘ 1
But I canโ€™t think of a way to automatically add it to the attributes passed to AR operations, thatโ€™s still Solidus code that may need to change ๐Ÿค” Or at least if the controller provides an
*_params
method you would need to override that to include the tenant id from the session etc.
@Eric Gross are you using the new starter frontend? At least on the FE side all the code is going to be part of your application so those changes would be easy. The admin might be a different story
e
I'm thinking about admin
๐Ÿ‘๐Ÿผ 1
c
A lot of the admin controllers inherit from
ResourceController
so that would be something you can override with your logic
b
This is tough because I think RLS is a good choice, but there's lots of code that happens when you do
order.complete
for instance.
You may have to open up a lot of mode to ensure the tenant id is passed through.
It might be better to put Solidus not in its own tenant, but its own database for it to operate freely. I haven't looked at Rails new multi-database capabilities much though but it may or may not help in this situation.
e
I feel like I can throw it in a thread local variable and then just pull it out whenever I need it.
๐Ÿ˜… 1
b
My stomach doesn't feel very good after reading that.
e
I totally agree, but the idea of sifting through hundreds of methods and passing a new thing around sounds like a disaster as well.
Perhaps this is pointing out a limitation to the existing data model that could be improved by allowing additional arbitrary fields on one or all models ๐Ÿค”
c
Yeah, I did a search for
_params
in the backend controllers and basically you would want to decorate those methods to append your new tenant id. If we had a consistent naming convention this could potentially be easier to do, but also it may not be the right behaviour in every single case.
๐Ÿ‘ 1
e
I was trying to add the tenant_id in all different places when creating a new cart, but I didn't figure out where it would actually make it to the order. Any idea where one location that would work would be? No worries if not, I'll look further
e
No good call, i'll let you know ๐Ÿ™
I got it working with RLS! It wasn't that bad, and I didn't have to do anything nasty with threads or editing a ton of files. Does this seem like an option or a new gem or something i should keep to myself? ๐Ÿ˜‡
b
Is it gistable?
๐Ÿ‘ 1
e
happy to say it works but i haven't used all of solidus yet in there so i bet there are a few more tweaks that are needed
b
I have so many questions
First, why turn off caching?
e
Oops, i can probably remove that ๐Ÿ‘
b
Well, I can mostly understand what's going on.
The RLS setting on a per request makes me nervous. What happens when two requests occur at the same time?
I wonder if you could wrap requests in an around block.
And think of some magic from there.
I would run the connection.tables in the console and then paste the huge list in the code. That way it's explicit as to which tables are being changed.
But man, adding a tenant_id to each table..
I wonder if there's a similar method to load all your indexes, drop them, and create them with a prefix tenant id.
e
After improvements, does this seem like a gem or a feature or just a gist?
Having to rely on a relationship to get the tenant ID seems more problematic than adding into all the tables.
I'm surprised it works at all without having to do a lot more work.
For me the two hardest things were the fact that I had to use a structure.sql rather than a schema and creating the other user that wasn't a super user for the tests.
c
Nice work Eric, this looks pretty awesome. I personally think this would make a good gem. If you are interested in going in that direction, I would probably recommend you start by making it a private gem in your repo directly (less overhead while you are developing it). That will provide a level of isolation from your application and force you to make any changes as overrides etc rather than directly in you app. Itโ€™s definitely a bit more work but I find itโ€™s a good way to do it if you want to open source this eventually once itโ€™s fully fleshed out.
๐Ÿ‘ 1