Sorta-kinda Domain-Driven Structure in Laravel
I'm prefacing with this that I don't enjoy strict 'patterns' when building products. I enjoy TDD but I don't adhere to it all the time, I enjoy aspects of DDD but again going all in....well it just takes all the fun out of things.
I also hate theory-only solutions so what I want to share with you is a real-life approach we are taking to structuring our Laravel application in Octohook. It is serving us well, it makes finding our models and actions simple and just takes away a layer of thought when deciding where classes belong and how they are named.
I'll walk you through the structure of a fresh Laravel install and the steps we took to clean up the
app folder and then how we create a new 'domain' module.
Above we see something, likely familiar if you have ever used Laravel. Out-of-the-box Laravel encourages you to stick all your controllers, commands, middleware, requests, etc, all together. For prototyping and small applications this is great but for anyone built something of substance this soon gets cluttered.
At Octohook we worked on grouping our application into domain areas rather than class types, so the first thing we did was roll out a
Core contains everything Laravel needs to get started; your basic providers, kernels, exception handler, and a few other out-of-the-box pieces.
So this is what we end up with after a shuffle around (note the only thing under
app is the
Core folder now):
This already feels much cleaner, we've moved only the core, required classes into a
Core folder, we've removed 5 service providers, 2 auth-related middleware, and the default User model, then renaming the two kernel classes to make their use clearer.
⚠️ If you do take this approach, you'll need to update all your namespaces of the classes you move, rename the class itself (not only the file name) and update your
bootstrap/app.php files to ensure you are referencing the correct providers, models, and kernels.
After these changes, you should take a moment to appreciate the
app folder now, there is so much room to breathe, it's simpler, more zen. 🧘
What is a module?
After cleaning up we can get into the juicy stuff; modules. Modules are a way to group areas of logic or domain knowledge, small enough to keep things specific but broad enough to prevent you from having 100 modules in your 3-page web app!
If you are one of the lucky folks who have used Octohook then you will know we deal in 'Sources' (Sources are how we know where your webhooks are coming from), so for us it's natural we have a
Sources module. Here we have all our actions that control the CRUD aspects of managing Sources as well as the resource objects and the Source model itself.
I've purposely cleaned this module up a little to make a point, and it's worth noting we rely heavily on Laravel Actions, having actions as controllers and/or commands. But you could also have a
Commands folders here too. Think of these module folders as mini Laravel apps themselves.
💡 On naming things; the rule here is that pretty much everything has to contain the name of the module. This makes it 100x easier to locate files/classes when using an IDE.
We also started to introduce module service providers too, this is a service provider at the root of your module that is responsible for everything to do with the module, from registering bindings found in that module to setting up listeners and observers too. I'm going to go in-depth into these in a future post.
So I've shared one module as an example but I figured I'd give you a few other ideas to hopefully highlight the kind of sized module we aim to work with.
Authentication - This is where we keep everything from login to registration, it's also the module that houses the
RedirectIfAuthenticated.php middlewares we removed from the core module.
TeamManagement - Everything for managing teams, whether that is creating a team, managing members, or switching your current team.
Billing - This one feels obvious but for the sake of explaining, here we have everything that deals with Paddle (our subscription management service of choice) and the consuming of Paddle's webhooks for instance.
Final thoughts 🧠
I hope this inspires you a little to try out a new structure for your next big thing and if you do try it out, that it brings you a sense of calm and ease.
For me, the biggest pay off is reducing one level of 'where does this go' and 'what shall I name this'. It's these seemingly minor things that, for me, have made a huge difference in how I develop products. Less sweating the small stuff and putting more effort into what matters.