Laravel Worldwide Meetup #5: Hotwire in Laravel & Single DB multi-tenancy

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hello good evening everybody uh thanks for joining uh this meetup uh i'm happy to uh to have another one and uh we took a break at the end of last year but i aim to have a meet up every month from now on now for the ones that are joining us for the first time i'd like to share what the uh what the purpose of this meetup is i'd like to introduce uh interesting topics and first-time speakers to the laravel community uh you won't see any of like the big or known uh names here but these are all people with very interesting ideas that i think deserve their time in the in the spotlight as well if you think you have like a good subject that you want to present um then share your idea at meetup.larval.com there's a form there where you can um where you can submit an idea and i'm open for everything for like simple ideas complex ideas long talks short talks it's all good just uh just send them to me now for uh the meetup of today we have two guests um later on as the second speaker we have kevin and he'll be talking about a strategy for a single database multi-tenancy in larval and it seems that multi-tenancy is like an evergreen subject in the larval community so i'm looking forward to that but the first speaker that we'll have on is tony visios and he will be talking about hotwire in larval let me bring him on hey tony how are you doing hello thanks for thanks for being uh on on this meetup um i've been looking forward to this talk yeah ever since that dhh shared stuff about hot wire and you have immediately jumped on the subject right yeah like those who know me knows that i'm uh base camper fiction aficionado i think like yeah i'm always reading stuff that they put out and reading um they put requests on rails and trying to learn how they build applications so yeah cool i was kind of ready when it came out [Laughter] so for uh people that uh don't know you can you tell us a little bit about yourself or where you're from what kind of projects are you working on uh how did you get a first experience at laravel yeah sure so i'm tony i'm from brazil i've been working with laravel since version version 4 came out [Music] before laravel i used to do cake php and also some vanilla php projects always as a full stack developer i've worked with a variety of different kinds of applications before but nowadays i'm working for worksite safety a canadian company they do safety training for organizations and i'm helping them building the new version of the website and also the new version of a lms application and we are using laravel out there so cool that's nice um okay now that people know you a bit i'd say just take it away tony and share us everything that you know about hotwire alright so i'm gonna share my screen let me know when you can see it yep we're all good awesome so yeah today we're going to talk about hot and first what is hotwire it's an alternative approach to building modern web applications without having to write much javascript it's an alternative to sbas and having your backends as apis and also it has a bit of native so we are not going to be covering that native aspect of uh turbo and turbo native in this talk we are going to mainly focus on the on the web portion of it so yeah um so this technique of sending hot wire by the way means html over the wire that's what the name means which is cool on its own um this technique consists of combining three different tools so first and you know the heart of it it's tubable turbo is the successful successor of the previous turbo links and you might be you know you might have heard of it before but yeah turbo is the new version of it and yeah better stronger and all that anyways so we are going to be focusing on toolbox in this talk and how we can get out of tubule um the second tool is stimulus so as we are going to see in the talk you don't need to write much javascript to get an interactive application but for those pages where you need a lot of interactivity you can write it you can write your javascript using either stimulus or alpine alpine or also works um so yeah as they claim here turbo will get you up 80 there and for the rest for the other 20 you can use stimulus or alpine wipe those two because the technique consists of sending in html over the wire so you will we are going to be inserting and uh replacing html elements from the screen so it's good that uh the html the js behavior is encoded in the html that enters the dom basically so that's the characteristics of both of these js frameworks and the third component is strata strata is not released yet but it it seems to me that it's going to be a an addendum to what's already possible with tuba native so today you can already write hybrid native applications using turbo and tuba native and strata is just going to make that easier it's not really easy yet so there's not much up there about it but they say that when it's out you're just going to be able to remove code from your mobile application so that's good i guess um yeah this talk we're going to make we're mainly going to focus on turbo so let's dive in i could just go over each of the the techniques inside toolbox itself but i thought it would be cool if i had a demo so that's that's what i built so i built a simpler version of basecamp it's called basic camp yeah little joke there so as you can see here this application is not using tuabo or anything it's just a traditional server rendered html application in laravel and if i log in with the correct password of course then i can see my projects i can create a project or i can view one project it's all doing full page refreshes and all that so that's good um there's nothing this is not all working by the way it's just the to-do's and the campfire that are working for now um so yeah this would be good to demo this this thing so first thing that we are going to do is install turbo so i'm going to this tab and i'm going to install hot wire tool on it oh yeah i have no slides it's a live coding thing so yeah um so if now that isn't now that it's installed i can actually go to my app.js and import the turbo library and now i can start the turbo process so and after that i have to compile the assets come on okay now if i refresh the page i'm going to disable the cache just so it gets a new version of the assets and now i if i click around i'm with the xhr tab open so i'm going to see every ajax request being done and now the application feels much more uh the transitions feels much more smooth here so yeah we are going to work mainly on the to do step and here we have a list of to-do's and if i click on the add to do it gets me to the create to-do form and then i can create a to-do and it gets me back to the to-do list so this is a traditional mvc application um what i'm going to do now is it would be cool if instead of bringing me to the forum to create it to do to this page it would be cool if the phone would be already in here in the to-do's list so since we start tuabo we can actually use some custom html tags so in the index page here i'm gonna wrap my link with the tuba frame this is a custom html tag that ships with doable and i'm going to give it an idea of create task just by doing that if i refresh here and i inspect the element i can see the turbo frame is there if i click on it let me go to the network tab if i click on it i see the request was made but nothing the button's gone that's because there was no matching turboframe on that response so nothing really worked yet so now we have to go to the create tools i'm saying to those but it's task so i'm in the create to-dos form and then i can wrap the form inside another tubule frame with a matching id on that page too and just by doing that now if i refresh and try again i should see that the form appears in line there on my list of to-do's that's cool but if i try to create it too you see that something is up here so there are a couple issues going on here so the to-do is not stacked on the list and the button reappeared here that's because one after we created to do we redirect the user to the to-do's list page and after that it renders the index with a list of to-do's which has a matching frame so that's not what we want but before we fix all this let's see another problem so if i control click on the form here it gets me to the create studio page which by the way this works really well if you have this technique works really well if you have javascript disabled so it gracefully downgrades for to not having javascript um to the way the application was working before you introduced turbo so that's cool if we submit the form here you see the validation message regular variable validation going on but if we try to submit the form here from the index you see that the button appeared and we don't see the validation messages and that's because right now that's because of how laravel handles the validation exception by default so right now the form was injected in the index page and we submitted that form which triggered the test store handler and the validation was uh thrown on that workflow and then it redirected us back to where we were before so we before we made the form we were in the index page so it renders the list again which has a matching frame with the ad form so toolball will just replace the matching frame that's not what we want here what we want is that doesn't matter where we submit the form from we always want to redirect back to the form that you know create task form so the form re-renders with the error and it has a matching frame so turbo will know to update that dom element so yeah we can fix that by going to the routes file and in the create tasks here i can actually try catch this statement and i can catch the validation exception and i can read through it but this time instead of redirecting back i'm going to force it to redirect to the create tasks endpoint which will render the form with the validation messages on it and all that so i'm gonna place this code inside this and now if we try again on the form and the create to-do form it should just work but if we try on the index 2 it also works there so that's awesome and now we can actually fix the issue that i just described about if i created to do it should appear on top and i want the form to re-render well i want a new version of the form to be appended or to replace the existing form which is dirty so we can do that by changing this code here i'm going to create a new view i'm going to create a toolbar folder inside my tasks folder and i'm going to create a view called created stream.blade by the way there's no package nothing here we're just using turbo as it comes out of npm um so what i'm going to place inside of this view if i go back to the documentation on two streams yeah so i guess i should mention why i'm using turbo streams so as we saw earlier we have tuber frames and tuber frames are really good for updating a single portion of the page but in this case we actually want to update two portions two parts of the page so we want to update the two bullies the to-do's list and the form we also want it updated with a new version of the form which is in the clean state and for that we can use tuber streams and here's what you can do with turbo streams so you can append elements to a target in the dom and this is a dom id there are some requests to make this a css selector but for now it's a dom id so we can append this to the list of messages in this case or we can prepend it to the list of messages or we can replace an element or we can update an element replacing will get rid of the existing element and add the new one in that place while updating will keep the element but change the content of the element so what we're actually going to do is i'm going to prepend the new to do and i'm going to update the form with a new version so i'm going to copy all this to that view and i'm going to delete the replace stream um and in here i actually want to change the target so i don't have a message started it's a task target and here i'm gonna use a task partial that i have and i'm gonna give it the task that i just created um this partial is also the same one that is being used in the index when i'm listing the tasks so if i go here i see that there is a 4h on all tasks and it includes the the same partial so that actually reminds me that i have to wrap this with a desks div the tuber stream target doesn't have to be a tuba frame it can be just an element with an id on your page um so the yeah we had we gave it an idea of tasks and then in the top stream view we just mentioned that we want to prepend this task in the tasks element and also we want to update the create task form and for that we are going to pass the form which is also inside a partial so i can just reuse it um and for that one we have to pass a project which i can get from the relationship of the task i've just created and i also have to pass a new model which i'm going to pass from the controller um the form is just a regular phone there's nothing different here so now in my route here i can actually return that view and how do i return it well let's go back to the application and then i can talk about the content negotiation that's going to happen so any turbo requests that is done by toolbox itself it's annotated with a accept header so if you see here the header it has this new content type of toolbox stream so any requests done by turbo will accept to the stream responses so we are going to check if the header has it so if this string exists in the request header in the accept header the request which string that one that we just copied from the dev tools um let me import this something's going on with my imports so if we just uh yeah we are checking if they had the accept header contains this string so if it actually accepts super streams if it does then we are going to render the view that we just created and for that view we are going to pass the to do that the test that we just created and a new task which is a new instance of the task model this is just so the form can render nicely um so we don't have this assigned yet so let's assign it and yeah that's it let's let's try this out i didn't have to refresh but i don't have all the habits so if i try to submit the form something is going to happen so if you check the response first the ui nothing changed but if we check the response the two streams responses are there but nothing changed and that's because we haven't changed the header that we like tuple doesn't know that this is a tuba stream response so it it assumes it's an html response and it tries to fetch tries to find a matching frame inside of the response so we have to tell it that this is actually a turbo stream response and how do we do that well we render the view and we also add the header the content type header and we use the same one that we are using here and now if we try again we should see yeah so everything worked the new to-do is there and the form gets a new version of the form so that's cool um something that i want to do real quick is would be cool if we could add it to those in line two so let's do that real quick if i go to the to-do's list and i actually if i go to the to do partial and if i wrap it with a tubular frame too and give it an id for this specific task and now if i go to the edit form for the to-do and i wrap the form the edit form with also a matching turbo frame the the same one as each individual task now i get inline editing for the to-do's which is cool um so this is pretty cool and that's pretty much what you can do with super frames um there is a lot of stuff more for instance i want if i wanted to eager load or lazy load the form here i don't like i don't want the user to press i want them to see the form right away as soon as the page loads i can actually give the tube of frame a source and pass a url and it will as soon as that tube frame hits the dom it will immediately make the request and find the matching frame on the response so this is cool because i could for instance in this basecamp talks a lot about this um i could cache this entire view with the tuple frame that laces lazy loads and i could serve the same view for all users and the part that it's actually specific for this user i can lazy load it um hey it does that with the little trace in the home screen and also i have an example here somewhere i'm going to show later that does the same thing but so yeah that's tuple super frame super streams that's hot wire as you can see here the the the controller or the actual the route handler it's actually a pretty bose i wouldn't like my controllers to all have this stuff so that's why i created the package that eases all this content negotiation going on and what the larvae should redirect routes to um so there are some conventions there basically you wouldn't need to try catch and redirect to the form so there's a convention that if you use the resource routes naming convention um any route that ends with a dot store will get redirected to its create version the same way any route that ends with the dot update will redirect the dot edit version and also you wouldn't need to do this manually there is a nice macro that we add to the request so you can just check if it wants turbo streams um and the same way for the response you don't need to do this manually you can just return response to the stream and pass it an eloquent model so now we know that this now we know where to look the partial for this model so it will look for the tasks resources in an underscore test partial there and unless you have the this exact same convention that i just created here so if i if there is a toolbar stream for created updated or deleted inside a tuba folder for the resource it will use that and there's there's more to the package actually so if we go here to the top um i haven't talked about this but i can also use web sockets and broadcast my model changes to all users connected to the page and all i have to do is annotate use this this trade on my model and this custom html tag that we ship with the package this will all be published to your assets when you publish the the package assets so you have full control over this um this will authenticate the user to this channel and handle everything permission related to broadcasting and all that we are using a lot of echo so you can use either pusher or the lot of our web sockets package i have an example here that's live and it's using the largo web sockets package so if we create like here i have a websocket connection and if i create a post on this screen it should show up on the other screen yeah you see you saw the posts appearing you saw the websocket message going on and here you can see the toolbar streams that went through the pipe so um it's the exact same tuba stream that we return to the user from the controller it's just like you you have to you can use it you can either update the current user via web sockets or you can update feed the current user with http with the response to the streams and update only the other users with broadcast messages from laravel echo so that's cool there are there's also a testing package that i wrote here and it gives you some niceties when you're testing so this straight interaction durable you can make a tubal request to a route and assert that you've got a two stream response and assert that you have a two string for this action for this target doing this action and all that the same way you can make a turbo native request um i haven't talked about tuba native just yet but there are some niceties on turbon native so you can you get a custom blade directive here to check um if you if this request was made via a turbo native client or not so you could render some specific style sheets for mobile or some elements for just for the mobile users or something like that so there's all that um this is it this is hot wire in laravel all the goodies and one thing that i really like about this approach is that it makes it makes simple things simple and complex things possible which is a quote from allen k so yeah that's it you're mute i think i'm indeed mute um yeah i was saying um it's it's pretty wild what you can do with um with all this do you know if there's anybody besides um yeah base camp and and hey obviously who are using these techniques already in production oh tony now you were remote communication yeah yeah from not exactly hot wire stuff like turbo it's just like new stuff but this technique is also being used by github and shopify to some extent i think that this all goes back to their paychecks way of sending html and you know updating the page yeah um so yeah pretty cool man pretty cool um do you have any work yet to be done on the package or can people just use it in um and yeah use all the things that hotwire provides or you still need to add some things yeah i want to add some stuff still and like i have an open question on how to handle software deletes for two streams um also some improvements that i wanted to make to the package itself but i think it's mostly there it's just i want more people to use it and you know give feedback and contribute as well um also there is like the companion app that i just showed this one tubular world which is live it's also open source and i'm gonna add some more features to it like i have a shopping cart example that i haven't shown here um there is also lazy loading happening here so like people can see this stuff going and all that so yeah the idea is that i i'm going to update this example application and even i even want to do the hybrid mobile app for this so yeah pretty cool man yeah thanks for for all the work and all the time you invest in this uh yeah i'm pretty sure that uh yeah this this technique will gain some some more traction in uh in the future so tony thanks for uh thanks again for uh for being here uh and yeah until a next time i'd say bye um okay with uh that amazing talk by tony dom we are going to have a five minute break and then we'll have uh kevin on and he'll be talking uh about a multi-tenancy strategy uh that he likes uh with using one database see you in five minutes i shouldn't mute here i am again um hello uh everybody welcome back i hope that you enjoyed that uh little break uh i'll bring on our second guest and it's kevin mckee hello kevin how are you doing how are you pretty good it's pretty good uh if i unmute uh at the right time then everything is good [Laughter] yeah indeed um so kevin um for the people that don't know you uh can you tell us a little bit about uh yeah the kind of projects you're working on where you're from and uh how you got into laravel yeah um i i'm kevin mckee i'm from st louis missouri which is right in the middle of the united states um i my experience with laravel i've actually only been using laravel since about 5.6 i've had a about a 12-year career in i.t um right now i've got a bunch of jobs i'm doing number one i'm the vp of information technology at a commercial cleaning company here in st louis and across the u.s um so we have some laravel apps that i uh help build and run there i'm also the co-founder of a laravel single database multi-tenant application called padmission which is a um it's an app that helps connect people who are experiencing homelessness to a place to live it's kind of like a zillow.com or for people who are currently homeless um so that's a really exciting project we're doing some really great work i'm super excited about that and that app is really the uh foundation of of my talk here today on multi-tenancy um and then if anyone if you do know me it would probably be from lara casts um i have a full series on laracast's called multi-tenancy and practice where i talk about this approach and a lot of other things go a little bit deeper into this single database multi-tenant approach now before we head in into the talk i have one question for you so the st louis you live in is that like the saint louis where larkin happened a few years ago um i don't think lara khan's ever been in saint louis um okay i've been in in uh doing laravel since 5.6 so definitely not since i've been in the community okay um yeah with that a postcard from st louis freak i've got to get that uh oh yeah if i get that i'll call the lawyers off and then you're you're good okay yeah if you're ready uh with your talk then the the stage is yours okay perfect thank you all right let's share the screen i'll add it and you're good to go yep so uh real quick freak just gave a great introduction but uh if you want to learn more about me my website kevinmckee.me we got links to all the things here i do have a free um course on contributing to open source if you've ever wanted to do that you can click that link here and i also have a snippet style podcast uh less than 10 minutes for each episode if you're interested in listening to that but let's get right into um single database multi-tenancy so what i'm gonna do i have no slides we're just gonna code and what we're gonna do in the next 30 minutes is build a single database multi-tenant app that is fully functional and it's going to be the whole design so that you could take this starting point and go build your sas application uh from here and everything is going to just work you're going to build your app just like you would build any other laravel app except it's going to be multi-tenant so you can have multiple customers or multiple companies with their set of users using your app but they're only seeing the data that belongs to them so there's three key components to multi-tenancy that we need to make sure we cover today so the data should be segmented by tenant in the database so uh when you're doing a single database approach that means you're gonna have a tenant id on all every or almost every table in the database um secondly you're going to be able to only see the data that belongs to your tenant so you can't see the data that belongs to another customer and then lastly you can only create data in your tenant so if you're creating something you're not going to be able to put it into someone else's instance so at the end i have some faqs we're going to go over where we talk about some of the pluses and minuses of this approach versus multiple databases so we're going to do this with this fire demo app so oops i should probably turn that off so fire is you guys have probably read fire tweets you know you've you've uh been watching a talk like tony's talk and thought man that talk that was fire uh so that's why i built this is a fake sas application that you and your team are gonna use to collaborate on all the amazing things that you've seen on twitter or learned about in laravel so to give you a quick demo of what it's going to look like right now this is just a shell of the ui where you've got a bunch of talks that you can add a new talk and then once you add a talk you're going to come in here and look at all the comments that you and your team are going to add as you're talking and collaborating about this whether it's a meetup talk or a conference or anything that you just did so you might also you know want to collect some fire tweets we're not actually gonna build this but um you know this would be uh this is the idea of this is a sas application you can imagine you want companies you want teams of people all collaborating on on things within the app but my team shouldn't see your team's stuff so hopefully that gives you a good foundation of what this multi-tenant or you know sas design is that we're going to do so let's go ahead and get started i'm going to log out here um and so the other thing i want you to know is this is aside from all the ui stuff i've done and i've done some um some controllers and and everything this is a standard laravel 8 with laravel breeze so i know there's a lot of authentication options in laravel but breeze is where we're starting from um so they published all of the authentication stuff and we're going to dig into that right now so the first thing we need to do if we're creating a multi-tenant application is create a tenant so i'm going to create a tenant model and sort of do artisan i've got an alias for that we're going to make a model for a tenant and i want a migration and a factory here so we're going to do that and so here's my application here and sorry let's go back to the app so the first thing we're going to want to do is oh and let me migrate my whoops my database okay so we need to register for the application uh this is your standard breeze uh registration and i just need to add a company because we need to know what company is signing up for this and create a tenant for that so if you go to the register view from blade or or from breeze sorry um you're going to see a let me grab my notes here i'm just going to add the company value or the company name here as an input so uh now when i refresh the page you can add your company name so this is again what makes it a sas application this is a whole company that's going to be using it then all we have to do is go into the register user controller and here i'm not going to do validation you would want to do that but the first thing i want to do is is make a tenant here and so let's get our model and we'll create it and we haven't actually created that in the database yet so let's go to the create tenants table and all i'm gonna do is have a string for the company name you're gonna eventually build this out to be a lot more but right now this is all i need and then lastly let's go to my tenant factory because we're going to be using factories and i just need the name to be a faker company okay so this is it let me migrate uh fresh one more time okay and so if we fill out this i'm gonna let's use my sas padmission and my name is kevin kevin kevin.com i'm not going to submit this just yet because um let's go back to that registered user controller i need to create this so the name is going to be from the request company name ah come on kevin type all right so that's good one thing i know is not going to work is i need to unguard that so that should work just fine and if i register here cross your fingers all right everything seems to have worked so i can see all this demo stuff if we look in our database we've got a tenant admission we've got a user and oh the one thing i didn't do back to it is go the registered user controller i have that tenant now i need to set the tenant id on the user so tenant id all right and the user model doesn't have that so let's create the users table and we're just going to add a table for a foreign id and this is going to be a tenant id on the user now a couple things i'm going to do i want an index on this because we're going to scope every query we make to the database to the tenant id so if you're going to have that as a where condition on all of your queries then you're going to want an index now as your application gets bigger you might add some more complex indexes but for now we're just going to do an index and then i also want to make this nullable and this is important because i want in my case to be able to um have a super user we're not going to go into that in this talk it's in my layer cast talk but a super user that can see data from every tenant so if they log in with a tenant id that's null then they get to see all the data which is one of the really great benefits of this single database approach so uh let's migrate our database one more time and that means i'm gonna have to um register once more let's go to register and admission kevin and now we're in and if we look at our database now i've got this tenant id of one and i'm associated to this tenant here so that's good so if we go back to we're just going to use this as our as our grounding point the data needs to be segmented by tenant in the database so that's really where we want to start and as we look at our app here's the next model we need to create is a talk but we want to make sure this talk also is segmented by tenant in the database so before we create it let's use a feature that um i don't know if a lot of people know about it or use it but it was introduced in laravel seven it's called the stubs so if i do php artisan stub publish this is going to create a whole folder let me show you this over here for stubs now all these red folders or all these red files these were published and this is what laravel uses whenever you create a new migration or a new model or anything so when we do artisan make model with a migration this is what is created and so because i want my migrations to have this tenant id i'm just gonna copy that from here and i'm going to put it into the stub now i don't want it nullable but i do want it there so now we have the tenant id in the stub which is great the other thing the factory i want all of my factories to be able to have a tenant as well so we have a tenant id on the factory and this is going to um if you're if you haven't used this yet in laravel um eight these this is gonna you know spin up a new tenant if you don't define the tenant id and then we would just have to up here use app models tenant so now when i go to php artisan make model we want a talk and i want a migration and a factory for my talks now let's take a look um create talks table i already have a tenant id on here so this is so important because you're not relying on the the developer to remember i need to put a tenant id on this now laravel is taking care of it for you and you can't mess it up the other reason i really like this is because there could be some models you don't want to scope to the tenant and then you can just delete it but it's always there by default and similarly we have our talk factory and this uh now has a tenant id there so we're going to do our create talks table and let's fill this in i've got a uh snippet here we just need a string a speaker and a date um a string for the name a speaker and a date and then we're going to go back to our factory and we're going to add that here so i've already added that tenant id so now we're going to have our faker name speaker and date so this is going to give us the ability to create some data so let's do that now we're going to go into tinker and we're going to say talk factory let's create three times a um talk and we want to put it in tenant one we do this um we didn't migrate our database um exits all right migrates let's try again there we go so now in our database we've got a talks table and we've got these three um talks that are there and then if we go back here and let's do it again but this time let's not pass an attendant id so this is going to do is create three more and they're all in different tenants so i'm logged in in tenant number one so i should only see the those three talks i created for tenant one so let's go and do that in our view first we have a talk controller and so here's the index i'm just gonna say talk right now i'm going to do it manually where the tenant id is one we're going to order by date descending and we're going to get and this is going to be talks let's import the talk model and then if we go to our uh talks index this is where these demos are now i'm using laravel blade components this is you don't have to know about those but it's a really nice way especially when you're doing a talk to hide a bunch of code that where the markup is confusing but i'm going to do a for each talks as talk and we're going to say talk list item and if i pass in the talk this is all going to work so this is in the source code which is is open source so you guys can see that and i'm going to delete these demo ones if i refresh my page if everything's working up i didn't format the so i go to my talk model i do have a date here so i need to unguard this and bid dates equals date all right so now if i refresh everything is working so we've got our three we're not seeing all six but the reason we're not seeing all six that are in the database is because we just hard coded that so in our talks controller we hard coded where the tenant id is one and that's not what we want to do again you don't want to rely on the developer to remember to scope every call to the tenant so what we're going to do is create a global scope so i always go to the laravel docs global scope let's pull it up obviously this is recorded i'm probably moving really fast for a lot of you guys but this is going to be recorded so you can come back and visit so as global scope is just going to apply some condition to the database on every single call so i'm just going to copy this example exactly i'm going to go up to my app folder i'm going to create a directory for scopes and i'm going to have a new file and this is going to be attendant scope.php i'm going to paste this in this is not ancient scope it's tenant scope and what i want to do here is just take this builder and say where tenant id and here i'm just going to call it one and then let me get rid of that that's okay so again i'm hard coding it here but now the only thing i have to do is apply the scope to my talk model and we can do that in a booted method so we can say static add global scope and then i just need to do a new tenant scope okay so let's see how this works if i go back to my app um let's delete that if i look at the query that's being run let me go back to my controller and where tenant id is one i'm going to delete it all we're doing is we're getting the talks from the database ordering and ordering them and if you look here i'm still only seeing those three and i still have this where tenant id is one scoped on my query which is exactly what we want now if i go back to the talk model and if i were to comment this out now i'm gonna see all six so as long as this is applied to my model then everything's going to work exactly like i expect it to so the only other thing now is instead of hard coding that number one what i want to do is get the id the tenant id on the user and so to do that i'm actually going to create some listeners so let's exit here so artisan make listener i want to use the session to hold the tenant id and we'll talk about more why i like that in a minute but we'll say set tenant id in session and we're going to make another listener that is remove tenant id from session and now we're going to go to the event service provider and we can hook into uh native laravel events so there's a login event and when this is called we're just going to respond with set tenant id and session and then similarly on log out we're going to remove tenant id from session okay so now in here we just need to uh within this handle method let's die and dump this event just so everyone knows what we're working with here and let's log out and we'll log back in and so this is what we get we get this user and so we can just access the user off this event and get the tenant id from it which is right here so what we want to do is say session and we're going to put a tenant id and it's going to be the event user tenant id okay and i did say uh so if the session has a tenant id there is an instance where uh i don't want to set this when it doesn't exist so we'll do that all right and then similarly on the um remove tenant id from session this one's easier all we have to do is session forget the tenant id all right so let's make sure this is working i'm going to go back here let's log in again and i can see in the debug bar one more time why isn't it working um set tenant id in section oh not this is in the wrong one this should be if um event user sorry that was probably confusing for all of you when i did that if the user has a tenant id we're going to do that all right sorry about that we're going to log out log back in and hopefully this is going to work now there we go so now the tenant id is here in the session we can use it and so if we go back to our global scope our tenant scope all we have to do now is say session and we could wrap this in if um session and iv all right so now this is still working and now as a user i'm only ever going to see the data that belongs to me so that is um the really big important uh piece of multi-tenancy now data is never going to leak from one tenant to the other it's really important so uh we have that working now um what do we need to do now let's go back to our fire.test data is segmented number one is done um the user should only see the data that belongs to his or her tenant we've got that one so now we have one left a user should only be able to create data in his or her tenant okay so if we go to the application here we have this ability to add a new talk so let's find that it's in our index box here and we have our ad talk form so i'm just going to go to this blade component add talk form and so we've already got this posting to a route and what we just need to do is go to that talks dot store so talk controller this is the store method and what we need to do is just take the request and we'll say talk create and i think i have this actually there we go so i don't have to type all that out um so we're just going to create the talk with the name the speaker in the date but what you can see we're missing here is the tenant id so if i were to try to submit that form right now it's going to throw an error because i didn't provide a tenant id so what i want to do is use this booted method to do a static creating so this is going to take a call back so we're going to pass in the model that we're working with and before so this is called right when i submit that form and laravel sees okay this is the model or this is the entry in the database that the user wants to create i have all the data before i submit it to the database let me do something here so that's where we are right now in the life cycle and so what i can do is just say model tenant id equals the tenant id that's in our session okay so before it's saved it's going to do this and this is really important and really powerful this is also just one of the reasons this is so important is because it makes sure you can never even like let's say i tried to pass in a tenant id that was different from my tenant because of this this would overwrite that even if somehow you didn't do your validation correctly and that got through when it gets here it's going to be overwritten by what's in the session which is exactly what we want so if we go back to the ui here let's say we have a hot wire talk from tony and let's just give a date here i'm going to add the event and everything worked and so you can see in our database now let's do a refresh the tenant id was added from the id that i had in the session so um everything works you don't have to remember to add anything if we take a look again at our our controller this is exactly how you would have created it if it wasn't a multi-tenant application but it is and so this structure is exactly what we need to make this app multi-tenant again you would add validation i'm not worrying about that just for the demo so we have this let me go back to our talk model and it's nice but like what am i going to do am i going to copy and paste this into every model i create the answer is no now we're going to go back to our stubs and use that but actually before we do that um i think this would be better in a trait so why don't we go ahead and make a trait uh let's go back over here let's add a traits full directory and whoa um a new file we'll call this uh belongs to tenant um and all right so now we have this um this is belongs to tenant okay and what we can do now is take all of this and just copy it and paste it in here and it's almost going to work exactly the one thing we have to do is we don't want to have a booted method here and a booted method on the model and potentially with other traits that override each other so instead of calling it booted we're going to call it boot belongs to tenant this is a convention that makes sure that nothing gets overwritten so it's just boot and then the name of the trait and then this should work so now if we go back to our talk we can just use belongs to tenant we can delete all of this and if we refresh our page everything is still working you can still see our queries are scoped to the tenant id you can see if i add a new one test tests uh that one worked too so everything is working um just as we would expect it which is which is really great so if we were to click in here um and try to do this comments functionality i'm not going to do it in the interest of time but you would build this exactly the same way as you would build any other application so this structure and it's it's actually almost done let's go back and and finish it off so now when i create a model what i want to do is have this trait applied to every model i can delete the tenant scope since it's now in here so let's go to our stubs and find the model and let's just take use belongs to tenant we'll put that right there and we need to import it and that looks good so let's just do a quick test let's do a make we'll say a talk comment and we want a migration and a factory and make a model talk comment what am i doing all right so the model the factory and the migration were all created so create talk comments table we have a tenant id already on it um comment factory we are relating it to the tenant and then the model itself it belongs to a tenant the the scope to make sure you're only seeing data from your tenant is applied and the scope and the making sure that when you create data it only gets put into your tenant um that works too so we're basically done with this uh whole structure that we built in just about 30 minutes our application is a fully functional we would have to go to the user model and add belongs to tenant now it's done so now we have a multi-tenant application and this is going to work really well for most applications let me go to the end of my talk here i have a little faq section that i want to cover before we get into any other questions this is single database multi-tenancy now this isn't everything um if you want to start you know working with the cache and making sure you're caching stuff that is uh you know tenant specific you'd have to work on that as well so it's not fully complete but it is it's a great starting point so why use single database versus multi databases my answer is speed and simplicity i think this is easy and you fully understand it i i really like when i don't have to use a package for something because i can just understand the code i wrote it all myself i fully understand it when you get into solving bugs when you get into anything else it's it's really important to understand your code and no one's going to you're never going to understand a package as well as you understand your own code so that's really number two here um you're also probably familiar with yagni you might get to the point where you'd start with a single database and have to migrate to multiple databases however you may not be there ever so let's not over complicate with multiple databases one other reason i don't have listed here though is what i alluded to before with a single database all of your database all of your data is in one place so if you wanted a super admin dashboard like show me all of my customers show me a count of all the talks that have been submitted well if you're in hundreds or thousands of databases um i'm sure the packages that that i know spacey has a package for multi-tenancy like i'm sure there's a way to do that but you can imagine it's it would be really difficult to make that query to a thousand different databases and compile all the data together and show it to your super admin dashboard whereas with single database you'd never have to worry about that it's all just there why do you store the tenant id in the session as opposed to just accessing it off the user model and that's again the reason i want that super admin dashboard where i can leave that as null so i don't ever want to be looking at that and having it be null the other reason is you do get into a little bit of complication with the laravel like going and authenticating a user and maybe trying to apply the tenant scope before it can that can i've had some bugs in that before so uh i found the session to be much better and it's secure like don't if you're doing this the only thing i would recommend is don't use cookies as your session driver so you don't want to store the tenant id in the front end make sure it's sitting on your server um lastly if you did want to explore a multiple database design why would you do that i there's two really big reasons for me number one if your app has so much data that you know just one tenant is going to have hundreds of thousands or millions of rows in the database well then you have all these customers and you've got tens of millions and hundreds of millions of of rows in a single database and that's going to slow your app down a lot so if you're planning to you know work with that much data then multiple databases is probably the right approach for you the other thing is what type of data are you planning to store if you're working with governments if you're working with really big companies or if you're working with really sensitive data like personal health information or financial like credit card numbers or something your customers are not going to want to hear that their sensitive data is in the exact same database as another customer's sensitive data so if the nature of your app is to either work with governments or big companies or really sensitive data my recommendation is to figure out now how to create a multi-database multi-tenancy as opposed to a single database so that's it um if anyone has any questions i'm happy to answer them i hope you guys enjoyed that i know i moved a little fast but if uh you know feel free to reach out with questions and uh go back and and watch the talk again or uh watch my larry cast series where i go then even more in depth on all these things you're muted freak not again here i am again so uh thanks for that that excellent uh talk kevin uh i really liked it i i i really like like the simplicity of the uh of the solution it's just yeah uh a few lines of code a modified step and then it's already multi-tenancy yeah yeah like i said this is how my um my sas app pad mission this is our our um approach so this is not theoretical it's it's live in production right now yeah yeah i think that yeah a lot of people will find this very very helpful and yeah i totally agree with uh the acne principle that you should start simple first then yeah maybe you just don't need a more complicated situation so i think that's very very good advice okay kevin uh thanks uh thanks for your talk uh it was great i hope you also had like a fun time giving it and for having me and i do hope that we'll see you at uh at larcon's in uh in the future should you should you like to do that absolutely i i can't wait and you know anyone here if you're watching right now and you see me at alaricon please come and say hi that's actually the main reason i like to to give talks is because i like to interact with people when we have these in-person conferences again i find it you know it's a really easy way for someone who might not feel real comfortable going up to a stranger but a lot of people are comfortable saying hey you did a great job on your talk so you know if you see me at a conference even if i'm not speaking there just come up and say hi i'd love to talk to you guys cool yeah i also hope that we'll have in-person conferences soon again because that's that's always better i think than like the virtual ones the virtual ones are nice to spread like um knowledge but in person it's good to have like these human interactions and to yeah form bonds and friends uh yeah i totally agree i can't wait to i don't think we've met in person freak i can't wait yeah i i hope it's it'll happen maybe at the end of the year probably probably next year uh then we'll shake hands like people used to do in in previous years and uh and have talked or at least a fist bump or an elbow bump yeah yeah indeed okay uh i'm going to close off kevin uh thanks again for uh for being here thanks everyone okay uh that was uh uh kevin with uh i think it was a really really good talk and we're at the end of the meetup um i'm going to repeat what i've said in the beginning of the meetup if you want to give a talk at this visual meetup head over to meetup.laravel.com there's a submission form there to propose a talk and i don't care about like the length of the talk or the level of the thought everything is uh is welcome um i already planned a next meetup as well it's also at meetup.laravel.com it's on the home page now the next meetup will be at uh the 23rd of february my first guest will be uh stefan baume and he will show some cool things that you can do with typescript typescript is like this uh amazing javascript language where everything is typed and yeah i think i can freely say that typescript really blows php's type script away it's it's pretty amazing and my second guest will be frank dior frank he made an excellent event sourcing library called event source but most of you probably know him from fly system which is also included in larval frank recently released a major new version of fly system version two and he will talk about the strange about the changes uh in that in that new release so i'm looking forward to that as well thanks again for joining and i'll hope i'll see you again next month bye everybody take care you
Info
Channel: Freek Van der Herten
Views: 4,754
Rating: undefined out of 5
Keywords:
Id: qqLVbd_uGiI
Channel Id: undefined
Length: 79min 34sec (4774 seconds)
Published: Tue Jan 26 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.