Controller in Clean Architecture

5.7k Views Asked by At

I'm trying to apply Clean Architecture from uncle Bob in Laravel application.

What i'm concerning is: As uncle Bob describe, the Controller should belongs to third circle: Interface Adapters (from inside-out). It means the Controller only depends on Use Case Circle (2nd), and should not know anything about the framework in 4th circle.

But controller in some frameworks has to extends a base class (for example, an AbstractController class), It also needs to receive an Request object and sometimes return a Response object, so this kinda break the dependency rule of Clean Architecture, as it knows about the framework in the outer circle.

Do i misunderstand? If not is there any solution to not break the dependency rule?

My controller is looking like this:

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use User\UseCase\FetchUsers;
use User\UseCase\FetchUsersRequest;

class UserController extends Controller
{
    public function index(Request $request, FetchUsers $fetchUsersUseCase)
    {
        $useCaseRequest = new FetchUsersRequest(
            // extract data from Request
        );

        $useCaseResponse = $fetchUsersUseCase->handle($useCaseRequest);

        return [
            'users' => $useCaseResponse->users,
        ];
    }
}
3

There are 3 best solutions below

0
On
  1. The Controller should belong to the third circle: Interface Adapters

Correct.

  1. It means the Controller only depends on Use Case Circle (2nd), and should not know anything about the framework in the 4th circle.

Correct.

  1. But controller in some frameworks has to extend a base class (for example, an AbstractController class)

I did not get a problem with that. Just use the "AbstractController class" in the Interface Adapters layer, as well as other controllers.

  1. It (Controller) also needs to receive a Request object and sometimes return a Response object, so this kinda breaks the dependency rule of Clean Architecture, as it knows about the framework in the outer circle.

This is an interesting statement. To clear things up let's refer to the primary source, the book "Clean Architecture: A Craftsman's Guide to Software Structure and Design":

The presenters, views, and controllers all belong in the interface adapters layer.

No code inward of this circle should know anything at all about the database.

The software in the interface adapters layer is a set of adapters that convert data from the format most convenient for the use cases and entities to the form most convenient for some external agencies such as the database or the web.

Note the flow of control: It begins in the controller, moves through the use case, and then winds up executing in the presenter.

For example, suppose the use case needs to call the presenter. This call must not be direct because that would violate the Dependency Rule: No name in an outer circle can be mentioned by an inner circle. So we have the use case call an interface in the inner circle, and have the presenter in the outer circle implement it.

The same technique is used to cross all the boundaries in the architecture. We take advantage of dynamic polymorphism to create source code dependencies that oppose the flow of control so that we can conform to the Dependency Rule, no matter which direction the flow of control travels.

Input occurs at the controllers, and that input is processed into a result by the interactors. The presenters then format the results, and the views display those presentations.

The frameworks and drivers layer is where all the details go. The web is a detail. The database is a detail. We keep these things on the outside where they can do little harm. Between the use case interactors and the database are the database gateways. These gateways are polymorphic interfaces that contain methods for every create, read, update, or delete operation that can be performed by the application on the database.

WHICH DATA CROSSES THE BOUNDARIES

Typically the data that crosses the boundaries consists of simple data structures. You can use basic structs or simple data transfer objects if you like. Or the data can simply be arguments in function calls. Or you can pack it into a hashmap, or construct it into an object. The important thing is that isolated, simple data structures are passed across the boundaries. We don’t want to cheat and pass Entity objects or database rows. We don’t want the data structures to have any kind of dependency that violates the Dependency Rule.

enter image description here

I think I collected all the most important information from the book, related to the "controller" topic. Now let's use this guidance to analyze your question.

As you can see in the clean architecture controller does not have to "receive a Request object" or "return a Response object". It can define its own request model that represents the user input in a simple and framework-agnostic way. The controller can then convert the Request object from the framework into its own request model and pass it to the use case interactor, which belongs to the use cases circle. The use case interactor can then perform the business logic and return a response model, which is another simple and framework-agnostic data structure. The controller can then convert the response model into a Response object that is compatible with the framework and return it to the user. This way, the controller acts as an adapter between the framework and the use case interactor, translating the data and requests between them without creating any dependency on the framework.

So what I think you missed is that in the interface adapters layer, there are gateways interfaces, which are used to communicate with the outer layer, to avoid coupling between layers.

In short, by using simple data objects, abstract interfaces and their implementations in outer layers you can achieve a clean architecture without breaking dependency rules.

1
On

Kinda too late, but you can create an interface to a particular controller in the use-cases circle(this would be called as input port in clean architecture).

You can then implement the interface in the third circle, like

// In the use-cases circle
interface UserControllerInterface(){

    public function index(Request $request, FetchUsers $fetchUsersUseCase);

}

// In the third circle
class UserController extends Controller implements UserControllerInterface{

    public function index(Request $request, FetchUsers $fetchUsersUseCase){
        $useCaseRequest = new FetchUsersRequest(
            // extract data from Request
        );

        $useCaseResponse = $fetchUsersUseCase->handle($useCaseRequest);

        return [
            'users' => $useCaseResponse->users,
        ];
    }
}

In this way you won't violate the discipline.

4
On

AbstractController is belong to the third circle. So you don't break any dependency. And if you have data transfer objects(DTO) at the Use case circle for transmit data to the third circle you don't break any dependency.

In order to make this happen you should create DTOs for all requests and responses, map your entities to DTOs and share DTOs instead of entities.

For example: you have an User entity with a string variable named Name. You have a controller that is going to fetch user from the use-cases circle.

Solution: Create a DTO named UserDto with a string variable(you can call it Name). Controller knows the UserDto but not the User entity