The Laravel default structure makes it really easy to start development. But if you got 250+ endpoints, different data sources for reading and combining data it starts to be hard for maintaining. The CQRS pattern could be the answer for that kind of problems. Maybe you already use this sort of things in your project, but you did not know that it is called CQRS. There is also something similar called GraphQL - it is a very similar concept, but it is already a concrete implementation.
If you have been doing software development for a while you probably hear about CQRS. It stands for Command and Query Responsibility Segregation. CQRS is an architectural pattern which advocates splitting the model of information into two parts, namely Command and Query. The motivation behind CQRS is to be able to scale out read and write independently even using different technologies.
You can have a software architecture that has one database for reading data and another one for writing data through commands. CQRS enhances this concept because it allows you to access both databases (for reading and writing) with one language like SQL or MongoDB or other languages like Java or Python. But if your application does not need a high performance database you can start with just one database (most popular case for small and mid projects).
To summarize Commands are responsible for writing (mutation) and Queries are responsible for reading. By default in Laravel you probably use just Eloquent model for both purposes.
Before we start
You need to know that CQRS is a very simple pattern and there is no magic behind. But it also has some disadvantages. Especially because it is a kind of layered (or onion) architectural pattern.
In this example we will create 4 files:
- Command
- CommandHandler
- Query
- CommandBus, it is an abstraction to simplify calling the handlers. We will built one in the end of that article.
Command
It is a Plain Old PHP Object (without any relation to the framework). One of the benefits is the naming convention - you know exactly what that object is doing. By type declarations you know what you can expect from the request. In some terms it is just immutable DTO (Data Transfer Object) from one layer to another one.
<?php
namespace App\Commands;
class CreateProductCommand
{
private $name;
private $price;
public function __construct($name, $price)
{
$this->name = $name;
$this->price = $price;
}
public function getName(): string
{
return $this->name;
}
public function getPrice(): float
{
return $this->price;
}
}
CommandHandler
Handler is responsible for processing the Command. There is one main rule, one Command must have exactly one CommandHandler. In our example we must create Commands and Handlers in the same namespace.
<?php
namespace App\Commands;
use App\Models\Product;
class CreateProductHandler
{
public function __invoke(CreateProductCommand $command)
{
$product = new Product();
$product->name = $command->getName();
$product->price = $command->getPrice();
$product->save();
// other logic, eg queue slack notification, dispatch event etc.
}
}
Query
Queries are responsible for reading data from your application. Here you can connect to multiple data sources such as databases, APIs, and finally combine them and build a response for the data client (Blade view, API response, etc).
As for the format of the returned data from Queries, some sources say that you should create dedicated objects (DTO) that will be read by other layers. However, in my experience it introduces a lot of boilerplate, which still generates another problem - serialization of this data. Therefore, the easiest way to use (in the case of PHP) is just any "Arrayable" type structure - the simplest arrays.
Arrays will make it easier for you to return data from controllers or accessing via Blade. Beside that accessing arrays in Laravel is quite pleasant - thanks to the data_get
method, e.g. data_get($array, 'any.path.you.want')
. Therefore, in the example below, we simply return an array.
<?php
namespace App\Queries;
use App\Models\Product;
class ProductSimpleQuery
{
private $productId;
public function __construct(int $productId)
{
$this->productId = $productId;
}
public function getData(): array
{
$product = Product::query()->findOrFail($this->productId);
return [
'name' => $product->name,
'price' => $product->price,
];
}
}
CommandBus
CommandBus has a single responsibility which is to execute all Commands in the system. It could be injected in your controllers or any other place you want to dispatch commands. It's a good idea to use dependency injection whenever you want to use it. Laravel supports auto wiring, so it should work out-of-the-box.
<?php
namespace App;
use Illuminate\Support\Facades\App;
use ReflectionClass;
class CommandBus
{
public function handle($command)
{
// resolve handler
$reflection = new ReflectionClass($command);
$handlerName = str_replace("Command", "Handler", $reflection->getShortName());
$handlerName = str_replace($reflection->getShortName(), $handlerName, $reflection->getName());
$handler = App::make($handlerName);
// invoke handler
$handler($command);
}
}
Usage
<?php
namespace App\Http\Controllers;
use App\CommandBus;
use App\Commands\CreateProductCommand;
use App\Queries\ProductSimpleQuery;
class ProductController extends Controller
{
private $commandBus;
public function __construct(CommandBus $commandBus)
{
$this->commandBus = $commandBus;
}
public function details(int $id)
{
// $this->authorize('...')
$query = new ProductSimpleQuery($id);
return $query->getData();
}
public function create()
{
// $this->authorize('...')
$name = request()->query('name');
$price = request()->query('price');
// validation goes here ...
$command = new CreateProductCommand($name, $price);
$this->commandBus->handle($command);
return response()->json([
'message' => 'success',
]);
}
}
Summary
As you can see it is very simple. With about 100 lines of code we built up a generic way of triggering Commands in the Laravel application. By using that pattern you can improve your project and make it easy to understand for new developers who will join your team.
What could we do to enhance that implementation:
- Logging mechanism in CommandBus - it would be helpful even for business purpose to see some statistics of application usage.
- Create database transactions support. It is very helpful for protecting your data flow.
- handleMultiple method - sometimes you want to trigger more than one command (e.g. Edit and Submit)
- and more…
Hope I have fully explained the theory of this pattern and the content has been valuable to you. As you can imagine there are many ways of implementing CQRS, but the concept stays same. All depends on your expectations and needs.
➡️ Part 2: Laravel Advanced CQRS ⬅️
If you like the whole idea about CQRS and you want to learn more about that pattern I strongly recommend that article on the Microsoft Tech Blog (CQRS pattern).