Keeping your Laravel applications DRY with single action classes

Rémi Collin
6 min readMay 24, 2018

--

One question which often comes up when speaking of application architecture, is the classical “where do I put this bit of code”. Laravel being quite a flexible framework, the answer to that question is not always easy. Do I write my logic in the Model, in the Controller, somewhere else ?

Writing logic in the controller is perfectly ok when you know you will have only one access point to your application. But today it’s pretty common to have several endpoints sharing the same functionalities.

For example, most application will have a form to register users, that will call a controller and return a meaningful view depending on success or failure. If there is also a mobile application, it will probably have an API endpoint dedicated to user registration, which will return JSON. It’s also pretty common to have also an artisan command to create users, especially in the early stage of development.

This code duplication may seems pretty harmless, but still, if the logic grows, for example if you want to send an email notification to a newly registered user, you’ll have to remember to send it from both controllers. So if we want to keep our code DRY, we need to move it somewhere else.

A common answer to this problem is that you will find in any forum topic is : “use a service class and call it from your controller”. Ok, but how do I structure my service class ? Do I create a UserService to implement all the logic concerning users, that I will inject in everyplace it is needed ? Or something else ?

Avoiding the service god-class trap

At first, it can be tempting to create a single class and group together all the code that applies to a particular model. Something like this :

This looks pretty satisfying : we can call or user create/delete methods from any controller and then handle the result back in any way we want. So, what’s the problem with this approach ? Problem is, we rarely deal with single models in isolation.

Let’s say, when a user creates an account, a new blog is created along. If we follow our current approach, we have to create a BlogService class that will be injected as a dependency of our UserService class :

It’s easy to predict, that, when our applications grows bigger, we will end up with dozens of service classes, some of them having 5 or 6 other service classes as dependencies, ending up with that kind of spaghetti code mess we want to avoid at all costs.

Introducing the single action class

So, what if, instead of having one single service class with several method, we decide to split it up into several classes ? That’s the approach i’ve taken in every recent projects, and it turned out to work beautifully.

First, let’s ditch the service term, which is too generic and vague, and call our new classes actions, and define what they are and what they can do.

  • An action class should have a name which is self-explanatory on what it does eg: CreateOrder, ConfirmCheckout, DeleteProduct, AddProductToCart, etc…
  • It should only have one public method as an API. Ideally this should be always the same method name, like handle() or execute(). This is handy if we need to implement some kind of adapter pattern to our actions.
  • It must be request/response agnostic. It doesn’t deal with the Request class, nor does it send a Response back. This responsibility is handled by the controller.
  • It can have other actions as a dependency.
  • It must enforce business rules by throwing an Exception if anything prevent it from executing and/or returning the expected value, and leave the caller (or the laravel’s ExceptionHandler) the responsability of how to render/respond to the exception.

Creating our CreateUser action

Now, let’s take our previous example and refactor it using a single action class, which we will call CreateUser.

You’d probably wonder why the method throws an exception if the Email is already taken. Shouldn’t it be taken care by request validation ? Sure it does. However, it’s a good idea to enforce business rules inside the action class itself. It makes the logic more obvious to apprehend, and debugging simpler.

Here’s the new version of our controllers using our action classes :

Now, whatever modification we make to our user registering process will happen in both API & Web version. Nice and clean.

Embedding actions

Let’s say that we need an action to import 1000’s of users into our application. We can write an action class for this importation, which will reuse the CreateUser class :

Clean, isn’t it ? We can reuse our CreateUser code by embedding it in a Collection::map() method, then return a collection containing all our freshly created users. We could improve it by returning a Null Object when the email is a duplicate, or feeding a Log file with this information, but you get the idea.

Decorating actions

Now, let’s say we want to log every new registered users into a file. We could put this code into the action itself, but we could also use a decorator pattern.

Then, using Laravel’s IoC container, we can bind the LogCreateUser class to the CreateUser class, so the former will be injected everytime we need an instance of the latter :

AppServiceProvider.php

This make it even easier to activate/deactivate the logging with a config / environment variable :

AppServiceProvider.php

Summary

Using this approach can seems a lot of classes at first. And, of course the user registration is a simple example aimed to keep the reading short and clear. Real value starts to become clear once the complexity starts growing, because you know your code is in one place, and the boundaries are clearly defined.

The benefits of using single action classes :

  • Small unitary pieces of domain logic prevents code duplication and enhance reusability. Keep things SOLID.
  • Easy to test in isolation against a variety of scenarios.
  • Meaningful names makes it easier to navigate inside big project’s structure
  • Easy to decorate.
  • Consistency across the project : prevent code to be spread over Controllers, Models, etc..

And of course, this is my approach based on my experience with Laravel this last few years and the kind of projects I had to deal with. It really worked for me and I now even use it on small/medium projects.

I’d be really curious to read your thought on it, especially if your approach is different.

--

--