Frage

I'm trying to understand how do controller and presenters work in clean architecture projects and could use some help. Specifically about generating multiple outputs from the use case.

In the Clean Architecture book there is an image that shows different outputs and a single controller for the same use case: Financial Report.

So my first question is how does the controller handle different inputs from the user ?. If the call for the financial report comes from the web view then the parameters received by the Financial Report Controller (for example date) are probably in JSON format. If it comes from a print instruction on a CLI application then it could be plain text. Do I need a controller for each use case ? Do I need a controller for each input type ?

enter image description here

My second question is about the presenters. There is a Screen Presenter and a Print Presenter. If the controller is called to get a financial report, how does it know at the end which presenter to use ? The screen presenter would probably parse the Financial Report Response to html and the print presenter to a pdf file but how do we tell the controller (or the use case interactor) to call a specific presenter?

This is probably just an implementation detail and not really covered by the definition of clean architecture, but anyway, if you had to add a new presenter to this design, for example the company now wants to get financial reports in audio format so you can listen to it if you are blind or can't use a screen, how would you do it ? And what about controllers if the input is now in voice format too ?

War es hilfreich?

Lösung

"If the call for the financial report comes from the web view then the parameters received by the Financial Report Controller (for example date) are probably in JSON format. If it comes from a print instruction on a CLI application then it could be plain text."

You don't pass in either of these to the interactor.

Note the Financial Report Request element; it has a <DS> tag on it; that's short for a data structure. It's either an actual data structure, or a just parameter list that a method on the Financial Report Generator has. The choice there is yours; creating an actual data structure creates better separation, is more explicit and more flexible, but it's also more work.

So, what you pass in is the data in the format specified by the request model. You don't pass in either of the source formats to the interactor, as these are external formats (unless you have purposefully adopted one of those formats as your request model, and accepted the tradeoffs that come with that). The controller module will convert the data to a Financial Report Request (it will either initialize the data structure, or if it's just parameters, it will just extract the values from the source data, and pas them as parameters), and then it will call a method on the interactor (Financial Report Generator) with that data.

"My second question is about the presenters. There is a Screen Presenter and a Print Presenter. If the controller is called to get a financial report, how does it know at the end which presenter to use ?"

This is an inheritance diagram; this is not depicting an instance of a Screen Presenter and an instance of a Print Presenter floating around. In the setup depicted, at runtime, there's just one or the other. The controller doesn't know which kind of presenter it's connected to, and it shouldn't - that's the whole point of dependency inversion. It only has a member variable that has the type of the Financial Report Presenter interface.

Some other code that knows about both (what's called the composition root, e.g., main, or a dependency injection container) will pick a concrete presenter and inject that instance in the controller. Then later on, when the controller has to use it, it will take the Financial Report Response returned by the interactor, and will use it to call some method (or maybe several methods) provided by the Financial Report Presenter interface; this will polymorphically invoke a method on the concrete presenter that has been "plugged in".

Depending on how your application is structured, if the business logic of the interactor (report generator) doesn't require the same instance being shared, in your composition root, you might simply create one instance preconfigured to use the screen presenter to be used for screen-based tasks, and another instance preconfigured with a print presenter to be used for printing.

Or you might use the Composite pattern to combine several presenters into one (derive a Composite Presenter from the Financial Report Presenter interface, wrap several existing presenters). Your composite presenter could have some logic that allows it to switch to which of the child presenters it forwards to. Or, if you wanted to have the ability to output to all presenters simultaneously, you'd indiscriminately forward all the calls to all of the child presenters.

Or, a method on your controller could accept a derivative of the Financial Report Presenter interface as a parameter, so every time you call that method, you can pass in a different presenter.

The setup depicted is pretty flexible, but no design works well for every imaginable set of constraints, so depending on the details of the actual application and on it's change patterns, you might adjust some elements of the design.

for example the company now wants to get financial reports in audio format so you can listen to it if you are blind or can't use a screen, how would you do it ? And what about controllers if the input is now in voice format too ?

Well, I understand this is hypothetical, but, using voice recognition for input brings it's own set of problems beyond just having to deal with another format. However, suppose you already have a module (or even another application) that handles those problems for you - that module (or application) would ultimately output commands in a form of some data structure, that would then be converted to the Financial Report Request, which would then be passed to the report generator. On the other side of things, you'd have an Audio Presenter and an Audio View that would probably include a voice generator, that would accept it's own input (as plain text or some other format). This data would be generated based on the output of the interactor (the Financial Report Response data structure).

"If I have 10 types of reports, does that mean that the Screen Presenter and Print Presenter have to implement 10 interfaces?"

I wouldn't do that, because that will likely lead to a big tangled mess of a class. Don't forget the single responsibility principle (SRP). (BTW, let go of the idea that you "have to" do things in a certain way - you (and your team) are the one(s) making design decisions, you can do whatever makes sense for your specific set of problems, nobody is going to grade you on this. Think of Clean Architecture as of a map, not a prescription.)

How you'd go about this completely depends on the details of your application & business logic. E.g. if some of your reports use essentially the same data, but just show it in different ways (either visually, or maybe aggregate it along different axes, showing different types of summaries), you might opt to have a presenter for each kind of report, and have them all implement the same presenter interface. It's this interface that defines the input for the presenters, but note that, in this design, it's owned by the controller (the controller says "I provide these kinds of outputs, and these are the output interfaces I support; if you want an output from me, implement the corresponding interface"). So you might also choose to have a couple of controllers (e.g., if the way users interact with the system is sufficiently different in different scenarios). Or you might have methods on your controller that accept different kinds of top-level presenter interfaces as a parameter. Or a couple of controllers with such methods. Again, depends on the actual application, and on how far you want to go in adhering to separation of concerns.

You might be thinking that's too much work, but remember, these classes are supposed to be fairly small for the most part, and focused on a fairly narrow task. Another point, Clean Architecture is about structuring the dependencies; it doesn't really matter if all of its elements are represented by classes/objects - in principle, you can do it all with functions. Also, you don't have to get it all right at the very beginning, start with a best guess then evolve over time (that's why I said it's a map). See my answer here to see how you might arrive at a clean architecture–like dependency structure, starting from a typical function with an input and an output.

For reports that are fundamentally different, you might introduce a different presenter interface, and a different set of presenters. You'd also have to consider (or check by experimentation) how feasible your approach is - maybe one of the reports that's based on the same data takes too long to generate, but the others work fine, so you'd treat that one differently (adding a new presenter interface for it). You might also find that some reports involve barely any business logic, so you may decide to bypass the interactor completely and go directly to the database, where you'd rely on an SQL query to transform the data into the shape you need (note that this doesn't break the dependency rule).

Also, you don't have to follow the depicted design to a tee; in particular, if you have functionality that repeats over and over, you can extract/encapsulate it into a new class that you can then reuse. So feel free to introduce new elements into the design, or to split things, etc. (And in fact, there are probably elements that are omitted on the diagram you've posted.)

So, to find the answer, you have to think about what kinds or reports you are going to support, and what's the data you'll be working with, what modules you want to keep independent, etc. And you might not have all the information until the project has lived for some time; if you keep the project evolvable, you'll be able to reshape parts of the dependency structure as the need arises. Also note that not all parts of the codebase change at the same rate; those parts that barely ever change will derive little benefit from a full blown application of design principles; it's enough to keep them decoupled from parts that do change at the boundary where they interact.

Andere Tipps

Nothing in the book tells you how to build this. You can write projects that follow this but a lot is left up to you. Both construction and input are missing here. So we're left making it up.

So my first question is how does the controller handle different inputs from the user ?. If the call for the financial report comes from the web view then the parameters received by the Financial Report Controller (for example date) are probably in JSON format. If it comes from a print instruction on a CLI application then it could be plain text. Do I need a controller for each use case ? Do I need a controller for each input type ?

All you need is the ability to convert the input into a Financial Report Request. Input isn't diagrammed here so that's up in the air. That might be work the Financial Report Controller does or it could farm that out to other objects or it could just get handed the Financial Report Request. There might be other Financial Report Controllers for this or there might just be one.

The Controller's* job is to pass the Request to the Generator, get the Response and send it to whatever Presenter it happens to have.

* Sorry, I'm sick of saying Financial Report

My second question is about the presenters. There is a Screen Presenter and a Print Presenter. If the controller is called to get a financial report, how does it know at the end which presenter to use ? The screen presenter would probably parse the Financial Report Response to html and the print presenter to a pdf file but how do we tell the controller (or the use case interactor) to call a specific presenter?

The Controller doesn't have to know (statically) which Presenter it's talking to. This decision might have been made when the Controller was constructed or it may have been set later. It may even have a collection of Presenters to send the report to. Though that would usually show up more in the diagram. Anyway, this design carefully ensures that the Controller doesn't have to know what it's talking to.

This is probably just an implementation detail and not really covered by the definition of clean architecture, but anyway, if you had to add a new presenter to this design, for example the company now wants to get financial reports in audio format so you can listen to it if you are blind or can't use a screen, how would you do it ? And what about controllers if the input is now in voice format too ?

You just pass the new Presenter to the Controller. Maybe when you construct it. Controller don't care.

Lizenziert unter: CC-BY-SA mit Zuschreibung
scroll top