pmjones / adr Goto Github PK
View Code? Open in Web Editor NEWAction-Domain-Responder: a web-specific alternative to Model-View-Controller.
Home Page: http://pmjones.io/adr
Action-Domain-Responder: a web-specific alternative to Model-View-Controller.
Home Page: http://pmjones.io/adr
I'd be quite interested to learn your views on ADR in regards to the PSR-7 Request/Response Interfaces.
This seems the domain of the Responder but does it change/influence the mechanism outlined in your paper?
I am aware that
To stay in line with precedent, this pattern omits the incoming HTTP request.
but it would also be interesting to outline how the two (ARD & PSR-7) interact.
There is no responseView in https://github.com/pmjones/mvc-refinement/blob/master/example-code/Blog/Responder/BlogAddResponder.php or in the abstract class https://github.com/pmjones/mvc-refinement/blob/master/example-code/Blog/Responder/AbstractBlogResponder.php
I'm re-posting this here because it is the type of solution that I think can work for everyone and would work with existing code.
What I would like the ADR pattern to provide is a traversable set of callable types. During its traversal the ADR handler will determine what the next callable to use is and will return it so that the generator can either call it or create it and then call it. This would also allow callables to be skipped or jump to a different point in the queue depending on its state?
'controller' => new ControllerAction([
function(array $args = []) {
return new Model(null, ['args' => $args]); //$args are the named args
},
function(Model $model) {
$model['__CONTROLLER__'] = __FUNCTION__; //demo check that is called..
return $model;
},
function(Model $model, Response $response) {
$model[$model::TEMPLATE] = 'home';
return $model;
//or
$response->setContent($response);
return $response;
},
]),
If one of the functions returns a Response
the traversal is stopped
References:
Matthias' CQRS/event sourcing
Traversable Event
A Transformer provides a presentation and transformation layer for complex data output, the like in RESTful APIs.
Transformers contain the business logic for changing a data format to whatever you need for the output, for exmaple JSON.
To goal is to create a “barrier” between source data and output, so schema changes do not affect users.
For example Fractal is such a component that is able to transform the input data into a representation for the Response.
I am thinking about where a Transformer would be placed from the ADR point of view. According to the IPO model, the transformer should be in the domain, because it processes and generates data. So the result of an Application-Service would have to be the result of the Transformer because this data was prepared use-case specific within the Application-Service.
On the other hand, a Transformer puts the data into the right "form" for the presentation (view). The preparation of the view-specific structure would also be the task of the Responder. We already know this from HTML, where ViewData is inserted into the corresponding HTML template.
So my question is: Does a Transformer belongs to the Domain or the Responder?
I was looking on Wikipedia, and it seems there was an ADR article, but it got deleted due to copyright concerns.
https://en.wikipedia.org/wiki/Action-domain-responder
Would it be possible to state what copyright license this documentation/repo is under, and/or whether permission is granted for it to be quoted in part on Wikipedia?
Should request and action be decoupled, so that an action can be fired off from anywhere (e.g. CLI or web)?
The way I imagine it working would be that some form of DTO would be passed into the action, only specifying the data which is required, which would be generated by the caller.
Could I be misunderstanding the way ADR should be used, and instead the action should be the code which generates the DTO before calling a domain service and associated responder? Where should any non-domain stuff go such as request validation?
I'm reading a lot about the design methods proposed here, and I absolutely love it!
However I'm not sure I understand correctly what the difference is between an Application Service and an Action, or are they similar?
I view the request as separate from the rest, and Request-Action-Domain-Responder lends itself nicely to the abbreviation RADR (perhaps RADAR?).
Thoughts?
Hey @pmjones,
ADR is quite impressive, congratulations.
I especially liked the part which tries to compare ADR to other patterns.
One question came to my mind (and I am not a regular reddit reader, so I ask it here): ADR is web specific (replacement/refinement, whatever MVC). Does this also mean the responder (and routing BTW) should be HTTP specific?
Most MVC frameworks out there have HTTP specific foundation, which is fine, but in some cases it would be better to say Requests and Responses have nothing to do with HTTP. They CAN be from/sent to HTTP, but for internal requests, console requests HTTP makes no sense.
So my question: is ADR bound to HTTP?
Thanks in advance.
Hello @pmjones,
I am trying to understand the core aspects of ADR. I saw you left out the Front Controller which is fine, but I am confused about where Action dependencies should be created.
In your example, your Action classes make perfect sense except that by your logic it sounds as though the Action class itself would have no dependencies. Because each Blog Action has its own dependencies, would we not create some sort of Factory pattern to encompass all Blog Actions?
I hope that makes sense.
Is there any chance of providing an example front-controller for a few obvious use cases, with perhaps an sql script too? This would mean one could clone the repo and actually run, test and debug it. Personally, I find it so much easier to understand the control flows if I can follow it with a ide debugger.
I'm reading through the documentation, and I'm very interested! This makes so much more sense then MVC
Regarding the part where it says
"all forms of input validation, error handling, and so on, are therefore pushed out of the Action and into the Domain"
Where in the domain would that happen? Is it good practice to put the validation logic in the Service? In the Repository (seems unlikely)? Or do you create another layer? Thank you
If the responder is in charge of building the response from the return of an action how do you handle returning a 304? It should be happening before the end of the action but it still would be a response.
The website for this repository is not available.
The SSL certificate for the domain / URL https://pmjones.io/adr/ is not valid anymore.
NET::ERR_CERT_COMMON_NAME_INVALID
Subject: *.web-hosting.com
Issuer: Sectigo RSA Domain Validation Secure Server CA
Expires on: 06.04.2022
Current date: 30.09.2020
In your examples, the action calls the responder. Is there a reason for that? Shouldn't it be the routing system that does that (when using a router that can do that of course)?
Hi Paul,
I have an idea of making the AbstractResponder
getting the views, and layouts. The idea is then we can inject the layout / view dynamically.
This is what I did earlier in https://github.com/cocoframework/Cocoframework.Action_Bundle/blob/master/src/AbstractResponder.php#L19-L55
Code below for easiness.
public function __construct(Response $response, View $view, $view_names = array(), $layout_names = array())
{
$this->response = $response;
$this->view = $view;
$this->data = (object) array();
$this->view_names = $view_names;
$this->layout_names = $layout_names;
$this->setViewsRegistry();
$this->setLayoutsRegistry();
$this->init();
}
protected function setViewsRegistry()
{
if (! empty($this->view_names)) {
$view_registry = $this->view->getViewRegistry();
foreach ($this->view_names as $view_name => $view_path) {
$view_registry->set(
$view_name,
$view_path
);
}
}
}
protected function setLayoutsRegistry()
{
if (! empty($this->layout_names)) {
$layout_registry = $this->view->getLayoutRegistry();
foreach ($this->layout_names as $layout_name => $layout_path) {
$layout_registry->set(
$layout_name,
$layout_path
);
}
}
}
In my own implementation I found it convenient to decouple actions and responders. The wiring is done at the route level:
game_listing:
path: /listing
defaults:
_controller: game_listing_action # service id
_responder: game_listing_responder_html
The action adds any specific data that the responder needs to the request object then returns null. The responder then acts on the request and generates a response.
I did it this way mostly because the Symfony framework makes it easy to implement a responder listener. I can actually configure multiple responders based on the desired response format (html, json etc).
It also seems a bit more middleware-ish. The action does it's thing then moves onto the responder.
Just wondering if the notion of not having the action operate directly on the responder fits into your vision of ADR?
Hi Paul,
As I mentioned yesterday for get action and post action of a contact form page will have 2 responders.
One normal responder and the other one with session responder.
In this I have only created one responder for I didn't noticed a way to other than copying the piece of code for the view.
Do you have an idea how we could achieve without copying view, or may be we inject the view which is set with the variables as you mentioned earlier in an issue?
Eg :
use Aura\View\View;
class ContactView extends View
{
public function __construct()
{
$name_vars = array(
'contact' => array(),
);
$view_registry = $this->getViewRegistry();
foreach ($name_vars as $name => $vars) {
$view_registry->set(
$name,
__DIR__ . "/views/{$name}.html",
$vars
);
}
$layout_vars = array(
'default' => array(),
'sidebar' => array(),
);
$layout_registry = $this->getLayoutRegistry();
foreach ($layout_vars as $name => $vars) {
$layout_registry->set(
$name,
__DIR__ . "/layouts/{$name}.html",
$vars
);
}
}
}
A fundamental problem I can see with this approach (or at least your examples) is, like the pseudo-MVC implementations, there is nowhere that treats application state as its own concern and very tightly couples it to the action. In effect, the action is encapsulating the application state.
I've written extensively about why a controller as a mediator (that is, feeding data from the model to the view) is a bad idea. This implementation still contains that fundamental design flaw that exists in most "MVC" implementations and as such the same level of limited flexibility.
See my post here: https://r.je/views-are-not-templates.html
Moving that to an action does not help solve the underlying issue with pseudo-MVC. Consider the following example code using MVC, I'm struggling to see how I can achieve the same level of flexibility with ADR without a lot of repeated code, using inheritance (which is always a bad idea anyway) or doing something that feels hacky like having actions create other actions.
A table with a list of products in an admin area:
[SEARCH-input]
ID | Title
001 | Tea | Edit | Delete
002 | Coffee | Edit | Delete
Page [1] [2] [3]
Here, there are 5 potential actions:
All but one of these actions will just change the results in the table that's being displayed.. they all require the same view. Editing the product requires a different view.
//Model
class ProductsModel {
public $searchCriteria = ['deleted = 0'];
public $sortOrder = 'id DESC';
public $page = 1;
const PRODUCTS_PER_PAGE = 5;
public function getProducts() {
//Yes, SQL injectionalble... demo purposes only
return $this->db->query('SELECT * FROM products WHERE ' . implode('AND', $this->searchCriteria) . ' ORDER BY ' . $this->sortOrder . ' LIMIT ' . self::PRODUCTS_PER_PAGE . ' OFFSET ' . ($this->page-1*self::PRODUCTS_PER_PAGE));
}
public function delete($id) {
$this->db->query('UPDATE products SET deleted = 1 WHERE id = ' . $id);
}
}
//Controller
class ProductListController {
private $products;
public function __construct(ProductsModel $products) {
$this->products = $products;
}
public function search($name) {
$this->products->searchCriteria[] = 'name = ' . $name;
}
public function delete($id) {
$this->products->delete($id);
}
public function page($num) {
$this->products->page = 1;
}
public function sort($columnName, $dir) {
$this->products->sortOrder = $columnName . ' ' . $dir;
}
}
The view would then call getProducts() on the ProductsModel instance and list all the required products. (N.b. the view would work out the pagination as it's display logic)
Here I can have a router call one or more of the controller actions to chain them together and use sorted, paginated, searched results. With ADR and having the $this->view->setData() call, this isn't possible to chain the actions and affect the state of the model, because the application state is stored inside the action (The action is holding the "Current set" of records beind displayed)
Not only that, the amount of code required by each action is incredibly excessive compared with a proper MVC implementation where most controller actions just alter the model's state. The reason for that is poor separation of concerns. Your action in the BlogCreateAction has several different concerns:
This is a very inflexible approach as it essentially takes the Model and Controller and bundles them together in a single "action" heavily reducing re usability of the various concerns.
By tightly coupling application state to user input you heavily reduce the flexibility and reusability of the code. To have a sorted, paginated table as above I need a separate action for each BlogListSortedAndPaginated, BlogListSortedPaginatedAndFiltered which gets very messy. Not only that, because these are concrete classes, inheritance cannot be used to resolve some of these issues because you'll very quickly run into the diamond problem even if you could somehow share application state between the actions.
The problem this creates is that the code in the controller is not reusable. To re-use the responder with a different action, a lot of that code has to be recreated. In MVC I can swap out the controller (e.g. one that set a specific state on the model) without having to re-code the model or the view. I can re-use the same model and view with that substituted controller. Alternatively, I can substitute the view to display the same filtered/sorted/whatever data set in a different way. If I want to substitute the view, it must have the API that the responder is hoping for because the action is coupled to the responder and breaks encapsulation by doing so. The action needs to know exactly what variables are available on the responder: $this->responder->setData(array('blog' => $blog)); the action can only be used with a responder that is expecting a 'blog' variable to be set. Why does the action need to know anything about the implementation of the responder?
If I want to use a different responder I must make sure there is a 'blog' variable available. Whereas with MVC proper, the View has a contract with the model. If I want to I can sawp out both the View and the Model while reusing the controller. There may or may not be a "getProducts()" method on the model but the controller doesn't care. By making interfaces to handle the view/model contract I can use any model with the view as long as it follows the interface. Consider the following interface:
interface Listable {
public function getResults();
public function getFields();
}
I could use this in a View to draw both the rows and columns for any given model. As long as my model implemented the interface I could create a table of products/blogs/users/whatever and it could all use the same view. Whether or not I'm doing this should be entirely irrelevant to the controller.
In ADR this is not possible because the action and the responder are coupled because there is no concern for the application state (The M in MVC).
I've been resisting the upgrade to Mavericks, like others I'm sure, till the last point its available. Therefore I cannot install Keynote 6. Please provide either a version of the keynote file compatible with Keynote 5 or a PDF copy for compatibility.
In your article/ post you write
$this->responder->__invoke();
Please, just use $this->responder()
. The __invoke()
method works pretty much like __toString()
where you could echo $this->responder
and the magic method would get called.
Forgive me, but in Web\Blog\Responder shouldn't the namespace used by those files be Web\Blog\Responder to match the 'use' statements in the Web\Blog\Action files, and not Blog\Responder that they currently are?
Some quick feedback.
I would like to see more clarification on this point:
The Action interacts with the Domain in the same way a Controller interacts with a Model, but does not interact with a View or template system. It sets data on the Responder and hands over control to it.
Slim uses a View class to generate output that the Action/Controller sends to the Responder/Response. For Slim, the Responder is very much a wrapper around an HTTP response. It is not required to use a View with Slim, but it does help implement third-party templating systems to help generate the responder/response body. Could this responsibility be merged into the Responder? Perhaps, but I feel like that would violate separation of concerns and make the Responder overcomplicated. Or maybe your concept of a "responder" is more a sub-system/group of components and not a single component? Or maybe we should not liken templating systems with the term "View"?
Paul, in the actual folder structure, would you see any problem in providing some segregating the /domain/
folder. I think the principles map well to Node and I'm trying to use this, but the asynchronous nature of JavaScript as well as the absence of anything equivalent to autoloading makes for interesting times. Anyway, here's an example:
root
|- domains
|- models # (these equivalent to your 'entities' - they are loaded into an ORM)
| |- Article.js
| `- User.js
|- services # (all your fetchById type of stuff - return entities or collections of entities)
| |- ArticleService.js
| `- UserService.js
|- ArticleFactory.js
`- UserFactory.js
The problem I'm trying to solve is the fact the ORM's like you to load all your models, and then they do some async initialisation (connecting to the DB, wiring up relations, etc). So I'm rebelling against my CMS heritage to modularise the object contexts. It's easier if all the ORM models are in one folder so I can just read it.
Thanks in advance.
First off, I am really sold on this pattern, so much so that I'm doing a write up and implementation, to sell it to my colleagues for our next project.
I am really sold on the single responsibility principal of the ADR pattern; That every Action/Domain/Responder deals with one thing and one thing only. However, I am starting to get a little confused about how this would address a complex UI.
Take a single homepage of a website as an example. We all know that the URI for that resource is not going to perform a single action like save this user's details
, but rather a list like so:
So the question boils down to: How would the Single Responsibility principal of ADR deal with a scenario like this?
I "get the picture" but it would be nice to have working example, with bootstrap, routing and so on. Just to quick run, play and port this idea to another framework, use in next project with Aura, Slim, Silex or anything else. Or just to test ideas for making mvc-refinement better.
Hi, how do you deal with combining different Payloads from different services?
For example, inside one template I want to show:
Inside my action I will get payloads from different services:
$payload1 = $weatherServices->getWeather();
$payload2 = $updatesServices->getLatestUpdates();
$payload3 = $sportService->getScores();
... etc...
Should I pass all of this payloads into responder? Or instead I should implement "aggregate" service, which will return one aggregated payload?
Thanks.
What's a clean way to do the following.
My Radar/Adr Actions are presently all direct calls to my Domain classes, e.g.:
$adr->get(ListClaims::CLASS, '/list-claims', App\ListClaims::CLASS);
$adr->get('/ping', '/ping', App\Ping::CLASS)->responder(JsonResponder::class);
This has worked fine so far, as all input value checking and manipulation (e.g. range limits) have been appropriate at the Domain level. That is, all callers of the Domain, whether HTTP or CLI or what-have-you, do not care.
Now, however, I've run into a situation where only HTTP callers of the Domain do care. This is because of bandwidth and memory limitations only web browser connections have. I want to range limit my input to prevent a user from asking for 1 million rows in a table, for instance. The Domain should not need to know or care about that.
But only specific routes in my Radar/Adr dispatcher need that kind of control.
Is the correct thing to create an Action class that clearly belongs to the HTTP-aware side, manipulate the values there, and then call my Domain application service, (normally called directly from the dispatcher)?
Or is this a job for middleware, since the bandwidth/output size limits are general for all HTTP requests? But this seems to mix awareness of specific Actions inputs with the general data stream.
Hello,
Thanks for the good article.
What is license of this text?
Regards.
Hi pmjones,
in this issue you said this:
For my part, having worked with ADR for a while, it turns out that every Action ends up doing exactly the same thing: marshal input, call the domain with that input to get a result, pass that result to the responder. As such, the Action portion can be completely extracted, and all you need to do is specify an input callable, a domain callable, and a responder callable; that information can be specified very easily via the routing system.
I am facing the sampe "problem": (almost) empty Actions.
For me, your statement sounds like the Action could be entirely omitted.
I'd like to explain why:
De facto each Action has a Response. I think this is a core element of the pattern.
But not each action may have a Domain. E.g. you have only one welcome page, that does not even fetch the name from the database/session/anywhere but only sais "hi" (we all know, there are such pages out there).
So the Response is always needed, whether a Domain exists or not. And as the Action only forwards incoming parameters to the Response or forwards data from the Domain to the Response, why the Response should not marshal its data itself?
I think this would be better than having a lot of (almost) empty Actions.
But well, it would not seperate concerns in a sensible way.
Hence I am looking for a "killer argument" to not let the Responder gather its data.
I am curous what you have to say :)
best regards
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.