Clean Architecture: Combining Interactors
Asked Answered
S

6

48

I've recently stumbled upon Clean Architecture, by Uncle Bob, and I'm curious to know whether Interactors can execute other Interactors.

For example, these are my Interactors as of now: getEmptyAlbums, getOtherAlbums. Both have Callbacks that return with a list of Albums (An ArrayList of an Album model) respectively.

Am I allowed to have an Interactor called getAllAlbums that executes the previous two Interactors within it's run block?

@Override
public void run() {
    getEmptyAlbums.execute();       
}

void onEmptyAlbumsReceived(ArrayList<Album> albums){
     getOtherAlbums.execute;
}
void onOtherAlbumsReceived(ArrayList<Album> albums){
     mMainThread.post(new Runnable() {
         callback.onAlbumsReceived(albums);
     }
});
Stutz answered 5/5, 2017 at 11:20 Comment(0)
K
55

I have been pondering the same thing and after finding very little on the subject, I have come to the conclusion "Yes" it is probably the best option.

my reasoning as follows:

  1. Single Responsibility: If you can't aggregate use-cases, then each can't really be single responsibility. Without aggregation, it means domain logic ends up in the presentation layer, defeating the purpose.
  2. DRY: use cases can be shared, and should be where it makes sense. As long as the intent of the use case is identical. Obviously this should be thought through before being done. In my experience there's rarely a need for this outside of the next point.
  3. Orchestrator classes: for instance if you need to fetch multiple data sources and persist to a repository. A use case which will run all of those child use cases is required, ensuring things like order of operations and concurrency are correctly implemented. This I think is the most compelling reason for calling other use cases.

To preserve single responsibility, I would consider limiting aggregating use-cases to do only that, i.e. executing those use cases and doing any final transformations.

Given the age of this question, I'd be interested to know which way you went with this and issues you encountered.

Kaela answered 8/10, 2017 at 23:57 Comment(7)
(I just ran a search for this question) and was leaning "Yes" due to the DRY principle. I can see an Interactor object (use case) creating a new RequestModel and passing it to a separate Interactor object. However, as you said, there is "very little on the subject".Dunn
I used interactors that executed other interactors to avoid my presentation layers from being too confusing and large and haven't come across any issues as such.Stutz
I see it in the same way. For a more detailed example on this topic pls see plainionist.github.io/Implementing-Clean-Architecture-UseCasesHard
I agree, this is the place to do it. This way is far better than combining this logic in the Presentation layer.Admix
As my experience - never do that or get then spagetti-code :-) Interactors should be changed independently, cause they are different parts of application. If you gave interactor opportunity to use another interactor - something gone wrong and you should to transfer logic from interactor to entity or gateway. If we talking about this concrete question - it is fully incorrect. Why? Cause if you have adjective word to Album entity - it is property of its ownLookin
in my experience it's rarely needed or makes sense, however when it does it is the correct thing to do otherwise you'll end up with a maintenance nightmare. In his example, this makes total sense to me assuming there's some actual domain logic in both of the other use cases. If not, it makes more sense to go to the repository directly and fetch all.Kaela
I am also a fan of this approach, having use cases use other use cases indeed creates dependency between them, but if we treat these usecases as orchestrators this dependencies actually makes sense. I worked in complex project where without it we would have to move the business logic to the presentation layer, which would be obviously wrong, and create lot more problems when business logic changes. We would need then to refactor presentation layer instead, where the logic would reside.Knightly
O
38

My answer would be no. Let me explain the reasons:

  1. That would be breaking boundaries

One of the most important concepts of Clean Architecture, is boundaries. Each use case defines a boundary, a vertical layer of a system. Therefore there's no reason to let a use case know about the existence of another use case. This vertical layers, allows to get independent develop-ability and deployability of the use cases. Imagine we are working as a team, you develop GetEmptyAlbums use case, and I work on the GetAllAlbums use case. If I call your use case in my own, we are not developing independently. Neither we're achieving independent deployability. The vertical boundaries breaks. See page 152 of Clean Architecture book and chapter 16 in general, for more details on that.

  1. SRP would be broken too

Suppose GetEmptyAlbums business rules changes for any reason. You will be in need to refactor that use case. And now maybe you need to accept some input. If GetAllAlbums invokes GetEmptyAlbums, this use case must be refactored too. In other words, by coupling use cases you are adding more responsibilities. Therefore SRP breaks.

  1. DRY is still complaint

There are 2 kinds of duplication: true duplication and accidental duplication. By defining 2 or more use cases that are very similar with each other, you're getting accidental duplication. It is accidental, because in the future the will become different probably and (this is what's matters) for different reasons. See page 154 for this concepts.

  1. Tests become more fragile

Very related to SRP. If you change something on use case A, and C calls A, not only A tests will break, but C tests too.

In conclusion, the answer is no, you cannot call a use case interactor from another one. But this rule applies if you want to achieve a pure Clean Architecture approach, which not always might be the right decision.

Another thing to point out, is that use cases must declare an input and output data structures. I'm not sure if your Album class is an Entity, but if so, there's a problem there. As Uncle Bob says: "we don't want to cheat and pass Entity objects" between boundaries (page 207).

Overdo answered 25/4, 2020 at 23:16 Comment(11)
can we reuse repositories in different use cases? Or everything in feature should be independent from another feature?Automation
Each use case should have its own repository. You will generate accidental duplication. But you'll get a vertical layer with domain, data and presentation that is completely isolated. However, remember that this is not the ultimate software architecture. It's really helpful for big teams, but for a small team it can be overkill to apply it at perfection. You must ask yourself if would this repository have to change for more than one reason when reusing it in another use case? And make a decision based on that. You can (and should) always refactor anywayOverdo
If you aren't mocking your dependencies in tests, you'll be in a world of hurt. this includes other use cases. If all you are doing is fetching items, you should be fetching them from a repository. If theres business logic that needs to be processed as part of that, it's likely to be common and DRY approach makes sense.Kaela
@BenNeill I agree with you about calling the repository directly to avoid having middle man use cases. Now, when we talk about Uncle Bob's Clean Architecture, repositories should be encapsulated to be only called from their interactors. One reason is repositories return entities, and the presenters shouldn't use those (because the view won't use all data or invoke entity methods). Also, repositories should be encapsulated to avoid using them from outside the interactors. Like I said, that's what Clean Architecture establish. It does not mean is the best choice for everyone or every moment :)Overdo
By the way, regarding DRY, the 20th anniversary edition of The Pragmatic Progammer made some clarifications about that principle. It shows that "duplicated" code does not necessarily means a DRY violation. Tip 25 (Don't Repeat Yourself) on page 31.Overdo
@Overdo In regards to DRY, it definitely is a case by case basis, reuse for the sake of it can be dangerous. If you have a complex piece of business logic, it often makes sense to break it down into logical blocks to make it more testable and readable, reuse is just a bonus when it happens. I find that is the most likely reason for dependencies on other interactors, where one's job is just to orchestrate the others.Kaela
I am certainly not talking about bypassing use cases, I am saying I'd use a repository directly rather than pulling in a use case that has trivial logic, such as a 'pass through' that just fetches and transforms.Kaela
@Overdo As far as I understand Clean Architecture, use cases are all part of the same boundary so it is not possible that a use case depending on another use case would break a boundary. In an even remotely complex app it would be unreasonable to believe that every use case is independent of each other; functionally it doesn't seem realistic.Thromboplastic
@Thromboplastic Use cases are part of the same horizontal layer (Domain) but each use case is a different vertical layer. It is realistic, I've defined this architecture in many complex projects at my work, and my devs are developing independently as expected. It just requires discipline.Overdo
Won't you necessarily, at some point, end up with a use case that orchestrates and calls several other use cases? For instance creating a resource, managed by one use case, may have consequences upon another resource, managed by another use case. Is this when you duplicate code because you consider they are different use cases? Honestly I find the use case independency "rule" a little confusingThromboplastic
@Thromboplastic That's exactly the point of Clean Architecture. You don't want to modify one use case and have consequences on another one. Please read about Common Closure Principle and Single Responsibility. Also if you're thinking about DRY principle, please check the 20th anniversary edition of Pragmatic Programmer, like I said in a comment before.Overdo
I
15

Take a look at the 16th chapter of the amazing "Clean Architecture" book. Uncle Bob answered this question at the part named "Duplication". There are 2 types of duplication:

Real duplication — introducing a change affects multiple places where the duplicated code exists.

Accidental duplication — the code is similar right now, but the ideas behind it are different, and the code becomes different over time.

In case of a real duplication, you can couple use cases, though be careful because it would be much harder to split them as the software evolves in case of accidental duplication.

Isidora answered 23/2, 2021 at 17:2 Comment(1)
That's a really good point, DRY doesn't apply unless the intent is the same for both operations.Kaela
S
2

I am very new to Uncle Bob's work and I am also going through these exact same questions and problems.

My answer to maintaining the SRP and not repeating yourself (DRY) with use cases was to separate the use cases from the interactor. It's possible that this is overkill but it really worked out well for me.

I have my use cases in their own files, separated from the interactors so that all separate interactors can use whichever use cases they want and share. All the while, the interactor just "uses" (imports, depends on, etc) any use case it wants.

Doing it this way has made my interactors very simple and is really only a container for the dependency injection(s) required, and some class level member vars.

So in summary, getAllAlbums, getEmptyAlbums and getOtherAlbums use cases become their own files and follow SRP and you have an Interactor class that aggregates at will and/or stitches together use cases in sequence.

Lately I have also been making my use cases only do actual business logic and not include things from dependency injection gateways like database or network calls. I then put the code for these dependency gateway actions in the methods operating the use cases...

Now if you only have "black-boxed" business logic concepts in use cases, you can test without including the dependencies being tightly coupled. So if you we're making the game "Tic Tac Toe" for example, your use cases when (looked at in quick glance) would be talking only the language of "Tic Tac Toe" and not "save", "commit" or "fetch[X]". You can save testing those things in the interactors test or in the gateway itself.

Sinkage answered 29/3, 2020 at 20:34 Comment(4)
I've come to this conclusion in my implementation as well, great comment @goredefexTaro
isn't usecase and interactor same thing ? I think Uncle Bobo uses them interchangeablyFumble
I would be interested to see how you differentiate an interactor from a use case. Additionally, how you structure this in your codebase (i.e. folder structure) @GoreDefex. I thought they were one and the sameLightness
@Lightness From my current experience it seems to matter if you are in a strongly typed language or not. I could be wrong, but it seems to me that in scripted languages like Javascript or Python, the use case function IS the use case interactor. Where as in strongly types languages like C# or Java, a use case is an interface and the interactor is the class that extends many of these use cases and gains action(s) as a result.Sinkage
M
0

Let's refer to the primary source, the book "Clean Architecture: A Craftsman's Guide to Software Structure and Design".

A use case is an object. It has one or more functions that implement the application-specific business rules. It also has data elements that include the input data, the output data, and the references to the appropriate Entities with which it interacts.

A use case is a description of the way that an automated system is used. It specifies the input to be provided by the user, the output to be returned to the user, and the processing steps involved in producing that output.

The system's use cases will be plainly visible within the structure of that system. Those elements will be classes or functions or modules that have prominent positions within the architecture, and they will have names that clearly describe their process.

In the book, there are a couple of scenarios of how use case interactors should be used. Still, there is no mention of any scenario where this use-case interactor would need to call another use-case interactor to perform its task.

Now my humble opinion.

Use cases are on the same level, communicating with each other does not violate any principles outlined in the book.

By following this quote:

It specifies... the processing steps

in my implementation of the use case interactor, these steps were the corresponding methods from the gateways. Ideally, we would not need to use other use case interactors, if for every step we have gateway implementation, since this way we keep every Use Case encapsulated and protected from the change to another Use Case.

However, in my experience, there were a couple of moments when I used a Use Case interactor to call another Use Case when it had more common sense.

enter image description here

Martyr answered 10/6, 2023 at 1:17 Comment(0)
C
0

This thread is a bit a old, but as I am encountering the same question currently, I will share my approach.

I don't see why we should not do that. Of course, it depends on the context, and as it create dependency between several UseCase, it should be well thought, however in my case I found this approach much more reliable than putting logic on the upper layers and maybe duplicating code.

First, the dependency can be not so strong if you standardize your UseCase output, especially in term of error or "empty" response when you have complex query with some logic in it.

Secondly, I also found it valuable to separate Domain UseCase and Application UseCase. Application may need to request several sub-usecase to process their logic in order to provide a full response. In this case, it could be done directly in the controller, however, you may have some overall logic you want to keep in this layer to avoid duplication.

Finally, having a use case calling an other use case is very similar to having different services working together. If we want to really avoid all dependencies, we could use a Facade Pattern. But it really depends on the context, on mine we are not yet at that stage and having this "standardize" dependency betewen use case is completely acceptable.

[EDIT] Regarding the test question, I also don't see why it should be more fragile. Every UseCase is still tested separately, and because you standardize ouput, you can have fully reliable testing through it. Again, of course it depends on the size of the application, but if you are in the situation where you are working with a lot of different services, maybe yo should look at patterns like Facade to avoid dependencies and you are back in "standardized output" situation.

Chambermaid answered 12/7, 2023 at 17:20 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Dennadennard

© 2022 - 2024 — McMap. All rights reserved.