Skip to content

Latest commit

 

History

History
226 lines (114 loc) · 19.3 KB

IMPLEMENTATION.md

File metadata and controls

226 lines (114 loc) · 19.3 KB

Implementation Notes

Action Domain Responder is a user interface pattern. It is not an entire application architecture in itself. With that in mind, this section describes other components, collaborations, and patterns that might be used in conjunction with ADR within an application architecture, along with notes and suggestions from ADR implementors.

Action

Action responsibilities are intentionally very limited. It only collects input from the HTTP Request, passes that input to the Domain, then hands control over to the Responder. You should handle business logic only in the Domain and presentation logic only in the Responder.

One key heuristic is this: if an Action contains any if/then blocks, try/catch blocks, loops, etc., then the Action is doing too much. The only exception here is that the Action may provide default values for user inputs when they are not present in the HTTP Request; this is easily handled via ternaries rather than if/then blocks.

How Should the Action Receive the HTTP Request?

The HTTP Request might be injected into the Action constructor, or it might be passed as a method argument invoking the Action logic. Each is a valid way to pass the HTTP Request, and each has its own tradeoffs.

Should the Action Validate Input?

A user interface component should not perform domain logic input validations.

It is better to pass the user input to the Domain, and let the domain logic perform the validation. The Domain can then report back if the inputs are invalid, perhaps with domain-specific messages.

Can the Action Build a DTO as the Domain Input?

Yes. However, note that building the Data Transfer Object must be completed without conditionals. If building the DTO can cause exceptions or errors, it is better to pass the necessary inputs to the Domain, then let the Domain build the DTO, and then pass that DTO to the necessary domain logic.

Can the Action Use a Command Bus?

Command Bus is a domain logic pattern, not a user interface pattern, and should be used in the Domain, not in the Action. For a longer discussion of this, see Command Bus and Action-Domain-Responder.

Should the Action Handle Domain Exceptions?

No. A user interface element should not be in charge of handling domain logic exceptions. The Domain should handle its own exceptions, and report the handling results back the Action, perhaps as part of a Domain Payload.

Can the Action Manipulate the Responder Before Invoking It?

Some Responder implementations may require the Action set data elements into it individually, or require other method calls before it can be invoked.

For example, instead of this:

return $this->responder->createResponse($request, $payload);

The following is also reasonable:

$this->responder->setRequest($request);
$this->responder->setPayload($payload);
return $this->responder->createResponse();

However, take care that no conditional logic is required. All presentation logic should go in the Responder; the Action should only pass values to it and then invoke it.

Can the Action Return the Responder Instead of Invoking It?

An earlier draft of this pattern noted, "the Action may return a Responder, which is then invoked to return a response, which is then invoked to send itself."

On further consideration, though, why would any code calling the Action need back anything other than a response? If there is any logic to modifying how the Responder builds the HTTP Response, it would best be incorporated or composed into the Responder.

As such, returning the Responder to be invoked by something else to create the response (instead of returning a response directly) still separates concerns properly, but should be considered an inferior form of the pattern.

Can There Be a Single Action for the Entire Interface?

Because Action responsbilities are purposely very limited, it is possible to create a single generic Action class that handles all user interface interactions. However, doing so might preclude the use of dependency injection systems. In turn, the implementor will have to figure out how to give the Action access to the necessary the Domain and Responder logic, and perhaps how to collect input in different cases.

One such implementation is Arbiter. In short, a router or other web handler component builds an Action description composed of an input-collection callable, a domain-logic callable, and a response-building callable. The Action handler then invokes the callables in the proper order, resolving object instances as needed along the way.

Domain

Remember that the Domain in ADR is an entry point into domain logic. The domain logic itself might be as simple as a single infrastructure interaction, or it might be a complex layer of interconnected services and objects notifying and observing each other before returning their final status.

Neither the Action nor the Responder is concerned about the internal workings of the Domain. ADR is concerned only that the Domain can be invoked by the Action, and that the Domain results can be presented by the Responder.

What Goes in the Domain?

Recall that ADR is a user interface pattern. Anything that has to do with reading the HTTP Request goes in the Action; anything that has to do with building the HTTP Response goes in the Responder. Everything else, then, must go in the Domain.

One easy heuristic to remember is this: "If it touches storage, it goes in the Domain." (Storage includes any infrastructure or external resource: database, cache, filesystem, network, etc.)

Now, what if the storage interaction is to retrieve only presentation values; for example, translations for template text, which require no business logic at all? It may be reasonable for the Responder to retrieve such values itself.

Even so, for the sake of a consistent heuristic, I opine that it would be better for the Action to pass to the Domain (as part of the Domain input) an instruction to return those values as part of its returned payload.

Proper Separation From User Interface

The Domain should be separated completely from any HTTP-specific dependencies. Although it is reasonable for the Action and Responder user interface code to depend on the Domain, it is not reasonable for the Domain code to depend on any particular user interface.

As a test for this, try to find out how well the Domain entry point would work with a command line interface instead of an HTTP interface. If the answer is "not easily" then the Domain is probably too dependent on HTTP.

How Should the Domain Receive Input?

The signature of the Domain entry point into the domain layer may be anything its logic requires from the user input: one or more separate arguments (type-hinted or not), a Data Transfer Object, even a catch-all array of all possible inputs from the HTTP Request. What it should not receive is the HTTP Request itself.

What Should the Domain Return?

It depends on the specifics of the domain logic, and on what the application requires for output to the user. This is necessarily particular to the core application concerns, and should not be dictated by the user interface.

The Domain might not return anything at all in some cases. In others, it might return a simple value or a single object. In yet others, it might return a complex collection of objects and values. Any or all of these might further be wrapped in a Domain Payload, which can simplify the transfer and interpretation of domain results and status across the user interface boundary.

It is then up to the Responder to figure out how to present the Domain results as an HTTP Response.

Responder

How Does the Responder Create the HTTP Response?

The Responder may create and return an HTTP Response object of its own, whether by new or by an injected factory.

Alternatively, the Responder may receive an HTTP Response object injected as a constructor parameter. The Responder can then modify the injected Response object before returning it to the Action. In fact, if the HTTP Response object is shared throughout the existing system, the Responder might not have to return the HTTP Response object at all, since the web handler may already have access to the shared object.

Each of these approaches has its own tradeoffs and is a valid implementation of the Responder.

Generic or Parent Responders

It may be that response-building logic is so straightforward that a single Responder can handle all response-building work for the entire user interface. Likewise, it may be that a parent Responder with base functionality could be extended by a child Responder with added or modified functionality.

These are both acceptable implementations for ADR. The important point is not the simplicity or complexity of the response-building work, but that such work is fully separated from the Action and the Domain.

Templates and Transformations

The use of Template View, Two Step View, and Transform View implementations within a Responder is perfectly reasonable for building the HTTP Response content.

Widgets and Panels

Some presentations may have several different panels, content areas, or subsections that have different data sources. These may be handled through a template system or other presentation subsystem, but they must not be in charge of retrieving their own data from the Domain. Instead, the Domain should provide all the needed data for these widgets when the Action invokes it.

For a beginning example of how to do so, see Solving The “Widget Problem” In ADR.

Other Topics

Content Negotiation

Content negotiation, since it deals with how to present the body content of the HTTP Response, most properly belongs in the Responder. For example, a Responder may negotiate the content type of the HTTP Response body to be presented based on the HTTP Request Accept header.

However, it would be inefficient for an HTTP Request to pass through the web handler, Action, Domain (possibly involving expensive resource usage), then finally to the Responder, only for the Responder to determine it cannot fulfill any of the acceptable content types.

As such, it may be reasonable for the web handler, after routing a request, to look ahead to the Responder for the routed Action and determine if the Responder can provide any of the acceptable content types in the request.

This would not be a negotiation per se, merely a check to see if the Accepts header contains any of the types that the Responder states it can handle. Since the web handler and the Responder all exist in the user interface layer, this does not represent an inappropriate dependency.

For an example, see the Radar framework; specifically:

  • The default Responder class, which reports the content types it can respond to (code).

  • The route class, which associates a Responder with a route (code).

  • The router rule which tests for acceptability (code).

Sessions

Recall the above Domain heuristic: "If it touches storage, it belongs in the Domain." Sessions read and write from storage (whether filesystem, database, or cache). Therefore, all session work should be done in the Domain. For example:

  • An Action can read the incoming session ID (if any) and pass it as an input to the Domain.

  • The Domain can then use that ID to read and write to a session store (or create a new one), and later return the session ID and related data as part of its results, perhaps in a Domain Payload.

  • The Responder can then read the session ID and data from the domain result and set a cookie based on it.

However, some session implementations may be so thoroughly intertwined in a language or framework as to make them unsuitable for pure Domain work. For example, the PHP session extension combines multiple concerns:

  • session_start() reads the session ID from the incoming request data directly, then reads the $_SESSION superglobal data from storage itself. This combines the concerns of input collection and infrastructure interactions.

  • session_commit() writes the $_SESSION superglobal data back to storage, and writes a cookie header directly to the outgoing response buffer. The combines the concerns of infrastructure interactions and presentations.

These kinds of situations make it difficult to intercept the automatic input collection process with an HTTP Request object, the automatic output process with an HTTP Response object, and the infrastructure and domain logic concerns.

This is an issue not with ADR per se, but with how PHP session functions combine request-reading, storage-interaction, and response-sending. It becomes a problem only when using HTTP Request/Response objects that are not hooked into automated PHP behaviors.

If you can find a way around it, you should. One way is to disable some elements of automatic session handling while leaving others in place. Another may be to avoid automatic session handling entirely, in favor of a domain-logic-friendly solution such as the one presented here.

Unfortunately, while there ought to a clean separation of input collection, reading from and writing to storage, and output presentation, doing so might not be practical under some language and framework constraints. In those cases, session work with HTTP Request and Response objects might have to be done in a way which is not as clean as we might prefer.

Authentication

As with sessions, authentication work should be done in the Domain, since it is likely to touch storage at some point. For example:

  • The Action can collect credentials (including tokens or session IDs) from the HTTP Request and pass them to the Domain as input.

  • The Domain can interact with a storage system to check credentials for validity, expiration, and so on, and load up any user-specific data tied to those credentials.

  • Based on authentication state, the Domain may return early for anonymous or invalid users, or it may continue on to other domain logic, later returning the authentication state (or lack thereof) as part of its results, perhaps as part of a Domain Payload.

  • The Responder can then inspect user information in the domain results to present them appropriately.

Routing

Often, developers will want to restrict some routes to authenticated users only. Does that mean the router, a user interface component, must interact with the domain layer where authentication logic resides?

The answer is, "maybe not." If the route conditions are based on something like "Is the user authenticated at all, regardless of who it is?", then the answer is to check not for authentication but for anonymity.

That is, if the incoming HTTP Request has no credentials or tokens associated with it, then the request is anonymous, and can be routed appropriately. Authentication work involving credential storage and validation can then be removed from the router and placed into the Domain.

Applicability

There is a case to be made that, because authentication identifies and manages a user interaction, it belongs in the user interface code. I am intuitively opposed to doing so, but it is a supportable point of view.

The problem then becomes: how is the user to be represented to the domain logic? We want to avoid the domain logic being dependent on user interface code, so the User component must not be provided by the user interface layer.

One solution here is for the user interface code to create a User component provided by the domain layer. Te User component can then be passed into the Action, whether as a constructor parameter, as an added HTTP Request parameter, or in some other way. The Action can then treat the User component as input to the Domain, and everything proceeds from there.

While I can imagine problems with that approach, it may be that no other is possible, given the constraints of the user interface framework presenting the results of the Domain work.

Authorization

Whereas authentication identifies a user, authorization controls what the user is allowed to do. Authorization work definitely belongs in the Domain.

As with authentication, how is one to do routing (a user interface concern) if the router cannot tell whether the user is allowed to be dispatched along a particular route? The answer is to realize that authorization is not over particular routes, but over the particular Domain functionality to which those routes lead. It might also be over functionality regarding a specific resource within the Domain, in which case the resource must be loaded (in part or in whole) by the Domain as part of the authorization check.

Thus, it is the Domain that should check if the user is allowed to perform a particular function. If so, the Domain logic continues on; but if not, the Domain can report back appropriately. This keeps the Domain, instead of a user interface component, as the proper authority over domain layer functionality.

For an extended conversation about this, see this discussion on Auth Gates and User Roles.

Client-Side Use

The ADR pattern is not intended for client-side use. On the client side, there are many perfectly good pre-existing user interface patterns, such as the original Model View Controller, Model View Presenter, and so on.

Command Line Use

Although ADR is envisioned as a user interface pattern for server-side applications, it can work as a user interface pattern for non-interactive command line applications. That is, if the command can be completed with only the values provided at the moment it is invoked, then:

  • the Action collects input from arguments, flags, and options as passed via the command line, invokes the Domain to get back a result, and invokes to the Responder to generate output (if any);

  • the Domain remains the same;

  • the Responder, instead of generating an HTTP Response, uses the Domain result to write to STDOUT and STDERR.

The Domain, in this case, may use a logging or notifier/observer system that writes to STDOUT and STDERR as well, to provide continuous output back to the user. This is not necessarily a violation of ADR, since the logging and notification are incidental to the operation of the Domain, but it does show how the pattern is not necessarily well-suited the environment.

For one example of this kind of non-interactive use, see Cadre CliAdr.

The ADR pattern will not work well with interactive command line applications which require additional user input after the command has been invoked.