I’m a big fan of writing module-based software, but I’m not so fond of relying on third-party packages for trivial things as they could always prevent you from upgrading. For the two years I have been writing module-based software with Laravel and I’m very happy with the result.
The deciding factor that drives me towards module-based software is the possibility for continuous improvement. Imagine you setup a project structure and 6 months later you decide that you made a lot of mistakes. It’s usually not easy to just improve the architecture without affecting the 6 months of existing code. There are two major points that I’ve noticed while analyzing this subject: you either have a standard in your whole project and stick with it or you modularize it and improve module by module.
Some people prefer to work with standards at all costs, even if that means being stuck with a standard that you no longer enjoy. Personally, I prefer continuous improvement. If the 20th module that I wrote is 100% different than the 1st one, I don’t mind. If I ever need to go back to Module 1 for a bugfix or refactoring, I can improve it towards the new standards being used by the latest modules.
If, like me, you would like to work on a module-based Laravel Application and avoid adding unnecessary third-party dependency to your project, here is how I did it.
Laravel Router system is kind of the entrypoint for the application. The first thing that requires changing is the default RouteServiceProvider.php
that should modularize the available routes.
We can get rid of the whole boilerplate that comes in that file and just setup one modules router.
Laravel comes with a few files on routes
folder. We can delete them all since we are not mapping them in the RouteServiceProvider anymore. Let’s create a single modules.php
router file.
Inside app
folder, let’s create the Modules/Books/routes.php
. Inside of it we can define the application routes for the Books module.
You may opt for Controller-based routing, which has been standard in Laravel. I personally liked the Good bye controllers, hello Request Handlers approach. Here is the ListBooks
implementation.
The BookResource
is Laravel’s transformation layer. As the Namespace suggest, we can create it at app/Modules/Books/Resources
folder.
Let’s also start the Authors module through the routes file.
Notice the namespace indicating we’re now writing files into app/Modules/Authors
folder. The Request Handler is also pretty straight forward.
Lastly we write the Resource class to transform the response into JSON.
Notice here how the Resource is going into another Module to reuse the Book Resource. This is usually a bad thing to do as modules should be completely self-sufficient and only reuse standard classes such as Eloquent Models or generic components designed for reuse on any module. The solution to this problem is to usually copy over the BookResource into the Authors Module, making it possible to change one without breaking the other or vice-versa. I decided to leave this cross-module usage as is to show that although a nice rule of thumb is to keep modules completely isolated from each other, it’s also okay to break that rule if you think that the use-case is simple enough and is unlikely to bring any problems. Always make sure to write tests to cover the features you write to avoid someone else from breaking your app unknowingly.
Although this is an extremely simple example, I hope it gives the notion that it’s easy to manipulate Laravel standard structure for your own needs. You can change file’s location incredibly easy in order to build a Module-based app. Most of my projects comes with App/Components
for generic classes that are reusable by any Module; App/Eloquent
to hold the Eloquent models and database relationships and the Modules
folder where we can structure any module-based feature. Here’s a folder structure of an app I’ve recently started working on:
The notion I want everybody to take away from this is that every module have it’s own set of needs and can have it’s own set of folders/entities/classes. There’s no need to standarize all modules to be exactly the same because some modules are a lot simpler than others and require a lot less structure. This example shows that AccountChurn
module provides an API via the Http folder while still providing Artisan commands via the Console. AccountOverview
on the other hand is Http-only and required repositories, value-objects (bags) and service classes (paginators) in order to deliver a great value.