Jonathan VUILLEMIN
PHP, Go, mostly.
Action-Domain-Responder pattern
Jonathan VUILLEMIN
2016-09
Source : https://github.com/pmjones/adr
MVC suffered of "semantic diffusion" ... especially in web context.
To resolve this diffusion, ADR pattern description is offered as a web-specific refinement of the MVC user interface pattern.
We have to see that, in web projects:
So ADR was imagined to represent a better separation of concerns than MVC does in a web context.
If we compare to MVC, we could say:
Action is the logic that connects the Domain and Responder. It uses the request input to interact with the Domain, and passes the Domain output (payload) to the Responder.
Domain is the logic to manipulate the domain, session, application, and environment data, modifying state and persistence as needed.
Responder is the logic to build an HTTP response or response description. It deals with body content, templates and views, headers and cookies, status codes, and so on.
The web handler receives a client request and dispatches it to an Action.
The Action interacts with the Domain.
The Action feeds data to the Responder. (This may include results from the Domain interaction, data from the client request, and so on.)
The Responder builds a response using the data fed to it by the Action.
The web handler sends the response back to the client.
Model vs Domain
No significant differences there, but:
Controller vs Action
A controller defines several actions/routes, and must inejct all dependencies to handle all actions/routes (ContainerAware).
But with ADR:
View vs Responder
In an MVC system, a Controller method will usually generate body content via a View. The Controller then injects the generated body content into the response. The Controller action method will manipulate the response directly to set any needed headers.
But with ADR:
Note also that a generic Responder may be used by more than one Action. The point is that the Action leaves all header and content work to the Responder, not that there must be a different Responder for each different View. And a Responder may incorporate a Template View.
(PSR 1,2,4,7 compliant)
Input interface
<?php
namespace Equip\Adr;
use Psr\Http\Message\ServerRequestInterface;
interface InputInterface
{
/**
* Extract domain input from the request.
*
* @param ServerRequestInterface $request
*
* @return array
*/
public function __invoke(ServerRequestInterface $request);
}
Domain interface
<?php
namespace Equip\Adr;
interface DomainInterface
{
/**
* Handle domain logic for an action.
*
* @param array $input
*
* @return PayloadInterface
*/
public function __invoke(array $input);
}
Status interface
<?php
namespace Equip\Adr;
interface Status
{
const STATUS_CONTINUE = 'Continue';
const STATUS_SWITCHING_PROTOCOLS = 'Switching Protocols';
const STATUS_PROCESSING = 'Processing';
const STATUS_OK = 'OK';
const STATUS_CREATED = 'Created';
const STATUS_ACCEPTED = 'Accepted';
const STATUS_NON_AUTHORITATIVE_INFORMATION = 'Non-Authoritative Information';
const STATUS_NO_CONTENT = 'No Content';
const STATUS_RESET_CONTENT = 'Reset Content';
const STATUS_PARTIAL_CONTENT = 'Partial Content';
const STATUS_MULTI_STATUS = 'Multi-Status';
const STATUS_ALREADY_REPORTED = 'Already Reported';
const STATUS_IM_USED = 'IM Used';
const STATUS_MULTIPLE_CHOICES = 'Multiple Choices';
const STATUS_MOVED_PERMANENTLY = 'Moved Permanently';
const STATUS_FOUND = 'Found';
const STATUS_SEE_OTHER = 'See Other';
const STATUS_NOT_MODIFIED = 'Not Modified';
const STATUS_USE_PROXY = 'Use Proxy';
const STATUS_TEMPORARY_REDIRECT = 'Temporary Redirect';
const STATUS_PERMANENT_REDIRECT = 'Permanent Redirect';
const STATUS_BAD_REQUEST = 'Bad Request';
const STATUS_UNAUTHORIZED = 'Unauthorized';
const STATUS_PAYMENT_REQUIRED = 'Payment Required';
const STATUS_FORBIDDEN = 'Forbidden';
const STATUS_NOT_FOUND = 'Not Found';
const STATUS_METHOD_NOT_ALLOWED = 'Method Not Allowed';
const STATUS_NOT_ACCEPTABLE = 'Not Acceptable';
const STATUS_PROXY_AUTHENTICATION_REQUIRED = 'Proxy Authentication Required';
const STATUS_REQUEST_TIMEOUT = 'Request Timeout';
const STATUS_CONFLICT = 'Conflict';
const STATUS_GONE = 'Gone';
const STATUS_LENGTH_REQUIRED = 'Length Required';
const STATUS_PRECONDITION_FAILED = 'Precondition Failed';
const STATUS_PAYLOAD_TOO_LARGE = 'Payload Too Large';
const STATUS_URI_TOO_LONG = 'URI Too Long';
const STATUS_UNSUPPORTED_MEDIA_TYPE = 'Unsupported Media Type';
const STATUS_RANGE_NOT_SATISFIABLE = 'Range Not Satisfiable';
const STATUS_EXPECTATION_FAILED = 'Expectation Failed';
const STATUS_IM_A_TEAPOT = "I'm a teapot";
const STATUS_MISDIRECTED_REQUEST = 'Misdirected Request';
const STATUS_UNPROCESSABLE_ENTITY = 'Unprocessable Entity';
const STATUS_LOCKED = 'Locked';
const STATUS_FAILED_DEPENDENCY = 'Failed Dependency';
const STATUS_UPGRADE_REQUIRED = 'Upgrade Required';
const STATUS_PRECONDITION_REQUIRED = 'Precondition Required';
const STATUS_TOO_MANY_REQUESTS = 'Too Many Request';
const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 'Request Header Fields Too Large';
const STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 'Unavailable For Legal Reasons';
const STATUS_INTERNAL_SERVER_ERROR = 'Internal Server Error';
const STATUS_NOT_IMPLEMENTED = 'Not Implemented';
const STATUS_BAD_GATEWAY = 'Bad Gateway';
const STATUS_SERVICE_UNAVAILABLE = 'Service Unavailable';
const STATUS_GATEWAY_TIMEOUT = 'Gateway Timeout';
const STATUS_VERSION_NOT_SUPPORTED = 'HTTP Version Not Supported';
const STATUS_VARIANT_ALSO_NEGOTIATES = 'Variant Also Negotiates';
const STATUS_INSUFFICIENT_STORAGE = 'Insufficient Storage';
const STATUS_LOOP_DETECTED = 'Loop Detected';
const STATUS_NOT_EXTENDED = 'Not Extended';
const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 'Network Authentication Required';
}
Payload interface
<?php
namespace Equip\Adr;
interface PayloadInterface extends Status
{
/**
* Create a copy of the payload with the status.
*
* @see \Equip\Adr\Status
*
* @param string $status
*
* @return PayloadInterface
*/
public function withStatus($status);
/**
* Get the status of the payload.
*
* @see \Equip\Adr\Status
*
* @return string
*/
public function getStatus();
/**
* Create a copy of the payload with input array.
*
* @param array $input
*
* @return PayloadInterface
*/
public function withInput(array $input);
/**
* Get input array from the payload.
*
* @return array
*/
public function getInput();
/**
* Create a copy of the payload with output array.
*
* @param array $output
*
* @return PayloadInterface
*/
public function withOutput(array $output);
/**
* Get output array from the payload.
*
* @return array
*/
public function getOutput();
/**
* Create a copy of the payload with messages array.
*
* @param array $output
*
* @return PayloadInterface
*/
public function withMessages(array $messages);
/**
* Get messages array from the payload.
*
* @return array
*/
public function getMessages();
/**
* Create a copy of the payload with a modified setting.
*
* @param string $name
* @param mixed $value
*
* @return static
*/
public function withSetting($name, $value);
/**
* Create a copy of the payload without a setting.
*
* @param string $name
*
* @return static
*/
public function withoutSetting($name);
/**
* Get a payload setting.
*
* @param string $name
*
* @return mixed
*/
public function getSetting($name);
/**
* Get all payload settings.
*
* @return array
*/
public function getSettings();
}
Responder interface
<?php
namespace Equip\Adr;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
interface ResponderInterface
{
/**
* Handle marshalling a payload into an HTTP response.
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param PayloadInterface $payload
*
* @return ResponseInterface
*/
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
PayloadInterface $payload
);
}
Route (action) interface
<?php
namespace Equip\Adr;
interface RouteInterface
{
/**
* @return DomainInterface
*/
public function getDomain();
/**
* @return InputInterface
*/
public function getInput();
/**
* @return ResponderInterface
*/
public function getResponder();
}
Thanks !
By Jonathan VUILLEMIN
ADR pattern