Clean Arch is an architectural model that helps structure applications in an organized way, separating responsibilities and facilitating code maintenance. In this post, we will explore how to apply Clean Arch in laravel.
Principles of Clean Architecture
Clean Architecture proposes separating the application into well-defined layers:
- Entities: Pure and independent business rules.
- Use Cases: Implement the application logic.
- Interfaces and Adapters: Communication between use cases and infrastructure (HTTP, database, etc.).
- Frameworks & Drivers: Laravel, Eloquent, Controllers and any specific implementation technology.
This approach reduces coupling and facilitates system evolution without impacting the entire structure.
Example
To provide a clear illustration, a simple reservation system will serve as our example.
To provide a clear illustration, a simple reservation system will serve as our example.
Example: The entity represents the business rule for booking and does not depend on framework:
<?php
namespace App\Domain\Entities;
class Booking
{
public function __construct(
public int $userId,
public int $serviceId,
public string $date
) {
// Business rule can be applied here
}
}
Creating the repository interface
The repository defines persistence but does not depend on Eloquent:
<?php
namespace App\Domain\Repositories;
use App\Domain\Entities\Booking;
interface BookingRepositoryInterface
{
public function save(Booking $booking): Booking;
}
Implementing the Create Booking UseCase
The booking creation logic is isolated in a use case:
<?php
namespace App\Application\UseCases;
use App\Domain\Entities\Booking;
use App\Domain\Repositories\BookingRepositoryInterface;
class CreateBooking
{
public function __construct(private BookingRepositoryInterface $repository) {}
public function execute(array $data): Booking
{
if (strtotime(data_get($data, 'date')) < time()) {
throw new \Exception("It is not possible to book for past dates.");
}
$booking = new Booking(data_get($data, 'user_id'), data_get($data, 'service_id'), data_get($data, 'date'));
return $this->repository->save($booking);
}
}
Implementing the Eloquent Repository
The concrete repository implementation using Eloquent:
<?php
namespace App\Infrastructure\Persistence;
use App\Domain\Entities\Booking;
use App\Domain\Repositories\BookingRepositoryInterface;
use App\Models\Booking as BookingModel;
class EloquentBookingRepository implements BookingRepositoryInterface
{
public function save(Booking $booking): Booking
{
$model = BookingModel::create([
'user_id' => $booking->userId,
'service_id' => $booking->serviceId,
'date' => $booking->date,
]);
return new Booking($model->user_id, $model->service_id, $model->date);
}
}
Creating the Controller
The controller uses the use case to handle HTTP requests:
<?php
namespace App\Infrastructure\Http\Controllers;
use App\Application\UseCases\CreateBooking;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use use Illuminate\Validation\ValidationException;
use Throwable;
class BookingController
{
public function __construct(private readonly CreateBooking $createBooking) {}
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'user_id' => 'required|integer',
'service_id' => 'required|integer',
'date' => 'required|date',
]);
try {
$booking = $this->createBooking->execute($data);
return response()->json(['message' => 'Booking successfully created', 'data' => $booking], 201);
} catch (ValidationException $e) {
return response()->json(['error' => $e->errors()], 422);
}catch (Throwable $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
}
}
Connecting Everything in Laravel
To inject the correct repository into the application, configure it in AppServiceProvider:
<?php
use App\Domain\Repositories\BookingRepositoryInterface;
use App\Infrastructure\Persistence\EloquentBookingRepository;
$this->app->bind(BookingRepositoryInterface::class, EloquentBookingRepository::class);
Benefits of Clean Architecture in Laravel
Modular code:
Facilitates technology and framework changes.
Easier testing:
Use cases and entities can be tested in isolation.
Less coupling:
Allows replacing the ORM, framework, or any layer without affecting the entire application.
Conclusion
Applying Clean Arch helps create more organized, modular, and maintainable systems. In this example, we structured a booking module by separating entities, use cases, repositories, and adapters.
By adopting this approach, you gain:
- More structured and scalable projects.
- Easier maintenance and testing.
- Greater flexibility for integrating new technologies.
If you want to digging deeper into Clean Arch and laravel, here are some useful references:
Top comments (0)