pmjones / adr

Action-Domain-Responder: a web-specific alternative to Model-View-Controller.
http://pmjones.io/adr
1.12k stars 64 forks source link

Question about complex user interfaces #35

Closed designermonkey closed 3 years ago

designermonkey commented 9 years ago

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:

  1. Get the header region content.
  2. Get the main article.
  3. Get a short list of other articles.
  4. Ad infinitum.

So the question boils down to: How would the Single Responsibility principal of ADR deal with a scenario like this?

designermonkey commented 9 years ago

Here's my repo if your interested: https://github.com/embarknow/adr

pmjones commented 9 years ago

This sounds like something where a Domain Payload will really help. Domain Payload is just a fancy name for a group of "things" emitted from the domain; aggregates, entities, whatever. They don't have to be strictly related to each other, they just have to be batch of information needed for the Responder to do its work. One example is Aura.Payload. When your Action asks the Domain for its results, the Domain should hand back a Payload.

In turn, your Domain call should perform whatever the "main" action is, then collect all the "other" information needed for the Responder to do its work. In the case you mention, it sounds like the Domain call should return:

You probably want each of those separate elements to have their own domain calls, so that you can test them individually. Your Action would not call them individually, though; instead, your action would call a Domain element that makes the individual calls for you and wraps up their results into a Payload, and then hands the Payload back to the Action (which in turn hands it to the Responder).

/me wipes brow

Does that begin to make sense at all?

pmjones commented 9 years ago

Regarding EmbarkNow -- dude, very nice! I have to say, though, that you may want to look at this conversation regarding a middleware system called Relay and two ADR frameworks called Radar and Spark. The interfaces you've come up with are remarkably similar to the ones proposed there; note also that Arbiter (linked near the end of that conversation) encapsulates much of the same stuff. In short, it sounds like great minds are thinking alike on this subject, and combining efforts might be useful.

designermonkey commented 9 years ago

Thanks for getting back so fast!

I guessed you'd explain it that way, and I'm glad you did, as that is what I was thinking, but wanted to check from a theory perspective before I did it. The Domain callable to me is a hook to a much larger tree of interaction, where other more specific classes handle all the legwork.

I have a Payload class in the theory work, but I was on the mindset that a Payload only handled one repositories worth of entities, so I still have to do a little thinking along the lines of how to remedy that train of thought; Do I send a Payload of Payloads back to the Action, a generic one containing many smaller ones with entity collections? I think this may be the answer, so I'll continue working on that (I have an implementation branch in flux at the moment before I publish it).

Working towards Domain driven design, the question of how to structure all this with the ADR pattern still swims about. Domains to me are separate namespaces of functionality specific to a bucket of information in the persistence, so I guess (hopefully right, I'm obsessive about correctness) that the ADR Domain Interface implementors would be structured separately from the domain real where the meat is.

I liked the idea of Spark at first, and yes have probably taken some inspiration from there, but my main concern was the coupling of the router to the concept of ADR, which to me is a separate concern. Where Spark has the router register the domain callable with the route, as well as the action callable, I feel that the pattern is best served when the action is aware of the domain and responder on it's own. Only the action should be registered with the route and should instantiate it's domain callable and responder on it's own.

Also (long post again), I agree that there are like minds thinking about this stuff, and will definitely get involved in conversations. I like to implement things my own way to start with to make a workable interface, and then use adaption patterns to break out into tested proven modules, like Relay (which is on my list already). I've started using Auryn\Injector and made an adapter for that, as it solves the problem of how to instantiate classes automatically with dependencies, instead of just a simple container.

Anyway, I'll keep you posted about progress, and try to wade in on relevant conversations. Always up for collaboration and merging of concepts. ADR definitely solves that age old problem of MVC in web for me :+1:

pmjones commented 9 years ago

The Domain callable to me is a hook to a much larger tree of interaction, where other more specific classes handle all the legwork.

Yes, exactly.

I have a Payload class in the theory work, but I was on the mindset that a Payload only handled one repositories worth of entities, so I still have to do a little thinking along the lines of how to remedy that train of thought;

/me shakes head

As far as I can tell, a Domain Payload can be composed of any number and type of elements from the Domain. It need not be restricted to one type of entity.

Do I send a Payload of Payloads back to the Action, a generic one containing many smaller ones with entity collections?

I assert that what you would want is one Payload only, and it can have whatever you want in it from the Domain. The Payload is for transferring stuff out of the Domain; internally, the Domain would not communicate in terms of Payloads, but in terms of Domain objects directly.

Working towards Domain driven design, the question of how to structure all this with the ADR pattern still swims about. Domains to me are separate namespaces of functionality specific to a bucket of information in the persistence, so I guess that the ADR Domain Interface implementors would be structured separately from the domain real where the meat is.

I think the answer is for the Domain callables to be Application Services. The Action calls a Domain-level Application Service, which coordinates the Domain activity and returns a Payload. Thus, the Application Service is still part of the Domain, but is at the upper-most (outer-most?) layer of the Domain.

(hopefully right, I'm obsessive about correctness)

Yes, ADR does seem to appeal to a particular kind of obsessive. ;-)

I liked the idea of Spark at first, and yes have probably taken some inspiration from there, but my main concern was the coupling of the router to the concept of ADR, which to me is a separate concern. Where Spark has the router register the domain callable with the route, as well as the action callable, I feel that the pattern is best served when the action is aware of the domain and responder on it's own. Only the action should be registered with the route and should instantiate it's domain callable and responder on it's own.

I get that. 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. That's what led first to Radar, and then (via Spark) to Arbiter.

I've started using Auryn\Injector and made an adapter for that, as it solves the problem of how to instantiate classes automatically with dependencies, instead of just a simple container.

You may wish to examine Aura.Di as well.

Anyway, I'll keep you posted about progress, and try to wade in on relevant conversations. Always up for collaboration and merging of concepts. ADR definitely solves that age old problem of MVC in web for me

Please do!

garrettw commented 9 years ago

(not to mention Dice) :smile:

shadowhand commented 9 years ago

I liked the idea of Spark at first, and yes have probably taken some inspiration from there, but my main concern was the coupling of the router to the concept of ADR, which to me is a separate concern. Where Spark has the router register the domain callable with the route, as well as the action callable, I feel that the pattern is best served when the action is aware of the domain and responder on it's own.

Hello, @sparkphp contributor here. Could you explain a little more what this looks like? We're still a very new project and your critique is interesting to me and might help us improve Spark and make it useful to you.

designermonkey commented 9 years ago

Hi @shadowhand of course!

I'm heading down the road of having an action instance for every action, and passing the domain and responder in to the constructor. Using an Injector, this is programmed so that like you guys are doing, I can just pass a spec to the router for a single callable (the action). Once the router matches, the application class is given just the spec, and it is therefore responsible for instantiating the classes. IMO it's a better separation of concern, and less passing the injector around. Also, if another router was used, which required instances, then passing it a single callable instance is easier.

I spent time working on Slim3 early on, and will be using it for an upcoming project, and ADR used with the action being aware of the domain and responder is quite a nice fit into the routing and middleware of Slim.

ConstantineYurevich commented 9 years ago

On my opinion it is not completely correct for domain layer to "Butch" all things up. First of all because having header info, list of articles, main article, ads in one template is part of presentation.

Shouldn't domain layer return everything separately? And then let action or responder aggregate everything in one template?

Main question is: who is responsible for aggregation for certain template/theme?

On my opinion if I change theme/layout/template/widgets - it should not require from me to make any changes inside domain (even in upper-most). All this changes should be handled inside template/action/responder.

What do you think?

designermonkey commented 9 years ago

The way I understand it now is that the 'domain' callable for the ADR pattern is an aggregate object. It is the connection between ADR and the domain proper, which of course using Domain Driven Design, should use Payloads to return data.

In that aggregate domain callable, I can call many separate domain objects, and then group the results together as I see fit for my responder class to utilise properly.

The ADR domain object is basically saying "Here is all the data requested by the Action, that I have collected from wherever in the real Domain, that the Responder requires to properly create output."

If one was to go a really strict route (which funnily enough I might be), then a DomainAggregate and PayloadAggregate class should be made to really show that there is structured data being returned. The PayloadAggregate class could contain keyed arrays of Payload objects that way. It may even have different status codes to say 'some data found' for instance...

designermonkey commented 9 years ago

@yurevichcv your idea is definitely valid though IMO, having a group of Domain objects return a group of Payloads to the Action to pass to the Responder.

I may think on that for a while.

pmjones commented 9 years ago

@designermonkey :

having a group of Domain objects return a group of Payloads to the Action to pass to the Responder.

(/me ponders)

It strikes me that inside the domain, there's no need to transfer Payloads around. Inside the domain, you only need the Domain objects. When passing from the Domain back to the Action, that's when you need a Payload to carry the Domain objects. So passing back one Payload, perhaps a custom one that is intended to carry different kinds of Domain objects (each with their own special information) seems more reasonable to me.

pmjones commented 9 years ago

@yurevichcv --

if I change theme/layout/template/widgets - it should not require from me to make any changes inside domain

If you change the data your presentation needs, then you have to change the data your domain delivers. Adding a data requirement in the presentation layer means you need to get the data from the domain. Removing a data requirement in the presentation means you don't need to get that data from the domain any more.