Basically, Action-Domain-Responder (ADR) is simply an improved version of the classic Model-View-Controller (MVC) pattern, nothing more.
Here are the main advantages of ADR compared to MVC:
1. Better Domain Separation and Application Structure
ADR was originally designed with Domain-Driven Design (DDD) principles in mind, which allows better structuring of the application by domains rather than by component types (controllers, models, views). As a result, the code structure becomes more logical and focused on business goals rather than technical layers.
In MVC, you often end up with many folders full of controllers and mixed responsibilities, whereas ADR suggests grouping code by meaning and purpose, which makes maintenance and scaling easier.
2. Clear Separation of Responsibilities
In ADR, each component has a strictly defined role:
- Action – handles business logic and interacts with the domain.
- Domain – the business domain model.
- Responder – builds the HTTP response (including headers and content).
In MVC, controllers often get bloated with business logic and responsibility for generating the response, which complicates maintenance and testing. In ADR, response generation is completely separated from business logic, improving readability and simplifying testing.
3. Closer Alignment with Web Architecture
ADR better reflects the real flow of web applications: a request goes to an action, the action interacts with the domain, then the response is formed. In MVC, the concept of a “view” is often mistakenly equated with a template, while in ADR the responder is responsible for the entire HTTP response.
ADR encourages the use of middleware and request interceptors, allowing authorization and other checks to be moved out of actions, further separating responsibilities.
4. Improved Maintainability and Scalability
Although ADR increases the number of classes (each action and responder is a separate class), this leads to a flatter and clearer hierarchy, making it easier to maintain and extend the application in the long term.
Example of ADR in Laravel
Action class:
<?php declare(strict_types=1);
namespace App\Account\User\Presentation\Action;
use App\Shared\Presentation\Controller;
use App\Shared\Domain\Bus\QueryBusInterface;
use App\Account\User\Application\Query\GetUsersPaginationQuery;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Route;
use Spatie\RouteAttributes\Attributes\WhereUuid;
use App\Account\User\Presentation\Responder\IndexResponder;
use App\Shared\Presentation\Response\ResourceResponse;
#[Prefix(prefix: 'v1')]
#[Middleware(middleware: 'auth:api')]
#[WhereNumber(param: 'perPage')]
final class IndexAction extends Controller
{
private QueryBusInterface $queryBus;
public function __construct(QueryBusInterface $queryBus)
{
$this->queryBus = $queryBus;
}
#[Route(methods: ['GET'], uri: '/users/{perPage?}')]
public function __invoke(int $perPage = 11): ResourceResponse
{
$query = new GetUsersPaginationQuery(perPage: $perPage);
return new IndexResponder()->respond(
data: $this->queryBus->ask(query: $query)
);
}
}
Responder class:
<?php declare(strict_types=1);
namespace App\Account\User\Presentation\Responder;
use App\Account\User\Presentation\AccountResource;
use App\Shared\Presentation\Response\ResourceResponse;
use Illuminate\Http\Response;
final readonly class IndexResponder
{
public function respond(mixed $data): ResourceResponse
{
if (!blank(value: $data)) {
return new ResourceResponse(
data: AccountResource::collection(resource: $data),
status: Response::HTTP_OK
);
}
return new ResourceResponse(
data: ['message' => __('No Users Found.')],
status: Response::HTTP_NOT_FOUND
);
}
}
Structurally, it might look like this:
Or like this:
In conclusion
ADR is an evolution of MVC, adapted specifically for the web, providing better separation of concerns, cleaner architecture, and easier work with modern web applications.
Thus, Action-Domain-Responder is better than MVC because it:
- Offers a more logical division of the application by domains rather than technical layers.
- Clearly separates business logic handling from HTTP response formation.
- Better matches the real flow of web interactions.
- Encourages the use of middleware for code separation.
- Provides simpler maintenance and scalability thanks to a clear class structure.
Top comments (7)
I don't see the benefit of having a responder and an action? If they are inseparable why split the code?
If you do almost nothing in the controller and make it a single method class, why not add a callable in the router file?
Think about all the classes you don't have to write.
How does it scale better?
Why Separating Action and Responder is Justified Even for Simple Logic?
Your approach with route closures works for small projects but becomes problematic at scale. Here are the key reasons why ADR is preferable:
#1. Single Responsibility Principle
#2. Testability
#3. Code Reusability
#4. Project Evolution
When adding features (caching, logging, custom headers), changes remain localized:
#5. Readability and Navigation
In a 50+ endpoint project:
When Is Your Approach Acceptable?
ADR shines in long-term maintainability, while closures work for short-lived or trivial projects. The more complex your domain, the more value ADR provides.
P. S. Placing this kind of logic in route files in hexagonal, layered, or clean architecture is a mortal sin. It violates the separation of concerns principle and creates tightly coupled code that becomes untestable and unmaintainable as the project grows.
I agree that closures are an extreme way of doing things. I'm sorry that made you take a sidetrack.
I just wanted to make the point that having a lot of invokable classes is not inherently better than using controllers.
Controllers can have method dependency injection, which makes it possible that the methods can be tested in isolation. It is frowned upon to add all the dependencies using the constructor.
That is also what I wanted to show in the router example. But I agree you can't test that in isolation.
Controllers don't stop you from adding middleware. Laravel comes with quite a few added middlewares out-of-the-box.
Controllers don't stop you from having a domain driven design of your application. MVC for me is a part of the presentation layer.
If clean architecture leads to classes that could be functions, I am skeptical if that is what the code should be.
As I understand it a responder needs to be made aware how to behave by passing data.
So if you need a XMl response the action needs to pass that to the responder. Which means you do need to change the action.
With a controller that is only one file that changes.
The responder returns a response, so it really depends on the response itself.
Etc.
I didn’t so much avoid the topic as just not understand the question, since my English isn’t that great.
Here’s another example I’ll give-it might make it easier to understand what’s what:
Again, everyone has their own way of doing things, but in Porto (the architectural pattern), it’s generally used like this:
Whichever way works best for you...
I appreciate the effort, but I don't think you are going to convince me it is a better pattern.
My goal was just to show how ADR can help organize code and separate concerns, especially as projects grow in complexity. But if your current approach works well for you, that’s what really matters.
I’m always open to discussing different approaches and learning from each other, so thanks for sharing your perspective!
You can use regular routes with ADR or attribute-based routes - whatever works best for you, whichever you find more convenient.