Orphail / laravel-ddd

Yet another Laravel 10 DDD interpretation
332 stars 62 forks source link
clean-architecture ddd ddd-architecture laravel

Yet another Laravel 10 DDD interpretation

Laravel 10 PHP 8.1 GithubActions

πŸš€ Current features

πŸ“˜ Introduction

I want to share with you yet another Laravel DDD interpretation, my approach to what could be a clean architecture design without having to give away most of the features we love from Laravel.

Some of my inspirations have been these remarkable articles:

First, I want to say that this is my own personal interpretation, which is also very open to suggestions and opinions. One of the things I have learned along this time is that what we call clean architectures, DDD, hexagonal architectures, etc. is interpreted differently from author to author although most of them refer to Uncle Bob, Martin Fowler, Eric Evans and Vaughn Vernon as the most influential on this topic. So, if you want to delve into it, I recommend reading about them.

πŸ€” Why use this approach?

Okay, let’s suppose that you want to program an app with Laravel that you expect to be mid-to-large size. You may have been working on some of these big projects but dealt with bloated controllers, monstrous models, etc. So for this one, you want to keep your sanity.

You hear about clean architecture and would like to try it, but its practices kind of break with the Laravel Wayβ„’ of building things, so either you have to stick with Laravel or create almost all the core functionalities with that permanent feeling of reinventing the wheel at every step.

I don’t have a perfect solution for this, and I haven’t heard of anyone having it, but I found a way that allows me to build things by having a more controlled planning and structure of my project despite having to deal with some extra boilerplate.

πŸ“— First steps

  1. composer install
  2. cp .env.example .env
  3. php artisan key:generate
  4. php artisan jwt:secret
  5. php artisan test
  6. For new domains, use this command: php artisan make:domain {Bounded Context} {Domain} (e.g. php artisan make:domain Blog Post)
  7. (optional) Set database connection in the .env variables that start with DB_* and run php artisan migrate

πŸ“ Structure particularities

Inside the "src/" directory we will keep our Bounded Contexts, which are the delimitations of around a set of domains that share functionalities and the same ubiquitous language. There is a special Bounded Context that I have named β€œCommon”, where I keep the resources that I will be sharing with many domains, and where I will keep our Laravel-specific logic as an infrastructure detail.

By the way, the "src/" directory is somewhat the "app/" directory we are used to in Laravel. I changed its name because I do not feel comfortable working with a folder that shares the name of a conventional Laravel app when this is not.

Also, I prefer to group the directory structure by domain, contrary to many examples I saw where some authors prefer grouping by layer. For example, a typical structure would be:

...
β”œβ”€β”€ Domain
β”‚   β”œβ”€β”€ User
β”‚   └── Post
β”œβ”€β”€ Application
β”‚   β”œβ”€β”€ UserRepository
β”‚   β”œβ”€β”€ PostRepository
β”‚   └── ...
β”œβ”€β”€ Presentation
β”‚   β”œβ”€β”€ UserController
β”‚   β”œβ”€β”€ PostController
β”‚   └── ...
β”œβ”€β”€ Infrastructure
β”‚   β”œβ”€β”€ UserEloquent
β”‚   β”œβ”€β”€ PostEloquent
β”‚   └── ...

While I prefer:

...
β”œβ”€β”€ User
β”‚   β”œβ”€β”€ Domain
β”‚   β”œβ”€β”€ Application
β”‚   β”œβ”€β”€ Presentation
β”‚   └── Infrastructure
β”œβ”€β”€ Company
β”‚   β”œβ”€β”€ Domain
β”‚   β”œβ”€β”€ Application
β”‚   β”œβ”€β”€ Presentation
β”‚   └── Infrastructure
...

I find it cleaner this way, although it may have repeated directories, I think it is more readable and is better for large applications.

Another aspect of this approach is that we have to break with the MVC pattern. Our controllers will be at the Presentation layer, directly attending to the requests and passing the data to an inner layer, receiving a response and giving it back to the client. The views can be rendered from the controller, as always, although I prefer using a separate frontend app (like Vue, React, Svelte, etc).

This approach also needs to define a specific ServiceProvider for each domain to bind out abstractions to the implementations. These providers will need to be registered in the "config/app.php" file to work.

Finally, what we consider a model in Laravel has been changed to what I called an EloquentModel, which will be an infrastructure detail. This is because Laravel uses an active record pattern for its models, and we want our models to be decoupled from the database, so we cannot put them in the domain. We can still benefit from Eloquent and other features using it inside the Application/Repository/Eloquent implementation while keeping it out of our domain.

🧐 What's inside each layer?

Here below will be a brief explanation of what each layer is for and what it should contain. Disclaimer: not every layer should contain all of these concepts, they should have just the ones that are needed.

🎯 Domain

This is where we will keep our entities, value objects, exceptions, interfaces, etc. This is the core of our application, and it should be independent of any other layer. It should not know anything about the Application layer, the Presentation layer or the Infrastructure layer. It should only know about itself and the business rules concerning itself.

πŸ“¦ Application

In this layer will reside our use cases, our repository implementations and other adapters. Classes and methods on this layer will be called from the Presentation layer, and they will be the ones that will interact with the Domain layer and the Infrastructure layer.

πŸ–₯ Presentation

This layer is responsible to attend to the requests from any interface and return a response to the client, and therefore it is the entry point of the domain from an external point of view.

πŸ—„ Infrastructure

Lastly, our Infrastructure layer will contain all the details related to the database, the framework, and any other external service we may use.

Other considerations

As stated before, this repository has been created and made public for anyone who can find it practical and inspiring. It is a very early concept, so any contributions to improve it are more than welcome.