pcah/python-clean-architecture

Architecture: should Interactor and Presenter be coupled?

lhaze opened this issue · 6 comments

lhaze commented

Reference [2] (The Source of our Principles and Assumptions) presents Interactor component as defining two interfaces:

  • InputPort aka InputBoundary, which is owned and implemented by the Interactor; this is an obvious part of the pattern and it's easy to implement.
  • OutputPort aka OutputBoundary, which is owned by the Interactor, but implemented by the Presenter, outside of the application layer.

I'm fine with the design, but I find it tricky to meet all the assumptions in the latter boundary. The key question is: should Interactor and Presenter be coupled? The [1] delves into details and I just sumarize it below.

References:

  1. SO question: Clean Architecture: Use case containing the presenter or returning data?
  2. The Uncle Bob: The Clean Architecture (figures)
lhaze commented

An Interactor which returns response model instance

First solution: an Interactor which returns response model instance.

def controller(some_data):
    request_model = RequestModel(some_data)
    interactor = Interactor(some_dependency, other_dependency)
    # ... and here's the application layer call
    response_model = interactor(request_model)
    presenter = Presenter()
    return presenter.present(response_model)

Benefits:

  • an obvious pattern in many frameworks
  • interactor is functional and somewhat easy testable: introduce mocks as his dependencies, put the request into him and check response and calls on the dependencies

Drawbacks:

  • This is NOT what Uncle Bob said. If you look carefully, there's no dependency between the Controller and the Presenter. He is very specific about that: there should be no reason to modify Controller just because details of presentation have changed.
  • "whatever knows how to ask for the data has to also be the thing that accepts the data. Before the Controller could call the Usecase Interactor blissfully unaware of what the Response Model would look like, where it should go, and, heh, how to present it." [1]
lhaze commented

An Interactor which calls the presenter

The second solution: an Interactor which calls a Presenter.

def controller(presenter, some_data):
    request_model = RequestModel(some_data)
    interactor = Interactor(presenter, some_dependency, other_dependency)
    # ... and here's the application layer call
    interactor(request_model)

Benefits:

  • No close coupling between controllers and presenters. Building request models and orchestrating components by the controller has nothing to do with presentation process.

Drawbacks:

  • This is not functional at all. Any test on the controller has to check side effects made on the presenter. I don't find it clean and easy to read.
  • All web frameworks I know implement "return the response" pattern for their controllers. They don't print anything like CLI frameworks, send any data via message broker like microservices used to do. Should we align to this imperative pattern?
lhaze commented

@jakos, would you like to state your opinion about this topic? I recommend reading [2] as it gives quite nice analysis.

lhaze commented

Lately I've heard an argument that makes me almost convinced to design nr 2 (an Interactor which calls the presenter). It goes like this:

We [web developers] are quite familiar and used to a pattern where an Interactor just returns data and respective Controller just passes them somewhere (i.e. to a renderer, a View, etc). This is a simple case due to synchronous and stateless nature of HTTP protocol. The rest of the world does not have such simplicity: desktop or mobile applications, asynchronous microservices or any interactive interface have to support concurrent and/or distributed request processing. It may mean that the Presenter may work on a different thread (e.g. UI thread) or publish to a different machine than the Controller works. Orchestrating the Presenter and the Controller as independent Components contributes to The Clean Architecture design at the layer of integration Interactors into different application environments.

jakos commented

@lhaze
After reading discussion [1] I'm for the second solution (An Interactor which calls the presenter). I think i'ts exactly what Uncle Bob said:

For example, consider that the use case needs to call the presenter. However, this call must not be direct because that would violate The Dependency Rule: No name in an outer circle can be mentioned by an inner circle. So we have the use case call an interface (Shown here as Use Case Output Port) in the inner circle, and have the presenter in the outer circle implement it.

But what about the drawbacks? Testing is very important for me and I can't just ignore this issue. Maybe Interactor could just return the Presenter? Then it could be tested or maybe even used to return response like in classic web framework. That would be only an option, because like you stated in last comment Presenter could do something totally independent - publish to different machine etc. We could just examine it's state during tests.

lhaze commented

Ok, I think I'm convinced to solution # 2. Adequate pull request is welcomed, maybe it is my responsibility though.

I think clean design of test process would lead to TestPresenter implementation, which would just collect response data. It would have test helper methods as well. It could be not do bad.