Closed mengqing closed 7 years ago
We have some specific flows in our codebase as well and interactors help with a lot of them. There are however some operations which are not that obvious and one of them is recursion.
For your example, it's apparent that tasks B+C+D form a logical unit. I assume they're complex enough that it makes sense to make them separate interactors instead of a single one which would encapsulate the if-else logic and the recursion. Therefore I'd group them in an organizer.
Going from there, I don't see any easy way to avoid independence of the individual interactors. I'd simply write a value into the context in B and check for that value in C and D. You can still test them separately nicely (because you control the context) but it's dangerous because there's no protection of the context in between - someone can later come, put a new interactor in the middle and that might overwrite the value, so be aware of that.
For recursion you'll need another interactor in the process, which will call the B+C+D organizer again. There two key questions here: is the context the same or does it change? and how granular are any data written to the context? For example in our application, we have a tree structure of objects and need to bubble some operations from a leaf to the root. In that case, it's good, that we change the context every time. In your case, you might need to run everything in the same context and that will be harder (but I can't imagine when this would happen and if interactors are good fit for that problem). The interactor in charge of recursion needs to handle the output written to the context. Generally we don't use it very often, but you'll probably want to propagate at least failure of the interactors and possibly pass some errors up. This, however, heavily depends on your usecase and the granularity of the data.
Personally, I don't feel really good about passing flags or data controlling the flow through the context, but it definitely beats some other options we're getting away from. Maybe the authors of the library or someone else might have a better and cleaner suggestion.
tl;dr vol. 1: not all logic needs to live in an interactor
If you, or at this point some other developer - as this issue is 5 months old, comes across this sort of problem and determine that recursion is the right approach, keep in mind that you don't need to store all actual business logic in the Interactor
/ Organizer
structure and thus solely rely on them alone.
For example, In your case you might try encapsulating all of the recursive business in a handful of smaller service classes, which are then coordinated by either another model, service, or interactor. The point is, you don't have to do all business logic control flow in the Interactor / Organizer pattern. Just the bits that are pragmatically useful and structurally reasonable.
tl;dr vol. 2: recursion is hard.
I would be curious what the specific case being solved is. More specifically, is this a case where recursion is actually the best (or even a good) approach to a solution. Much less meta-programming and regex, using these tools in cases where they aren't clearly and defensibly the best solution (not just 'a' solution) leads to a world of pain for not only the current developer but all those who come down the line later only to discover, Here There Be :dragon:.
Pedantry aside, a recursive organizer structure seems a little practically pragmatic in this case. For instance, @Mingan mentions error message handing, but there is also I think a larger consideration of the (very handy) transactional nature of Interactor
s and Organizer
s. If some recursive branch reaches an exception or failure state, there is no clear way for this recursive tree to unwind itself and undo everything that should be undone. This is most especially true if you are over-riding state. Going a step further, I'm not even sure it would programmatically feasible to do this as you would have un-reasonable number data points to store while maintaining a recursive tree of unknown length.
Hope this helps.
The pending Interactor 4.0 roadmap includes the ability to pass options to the organize
method which could allow you to build all sorts of conditional branching structures if it suits your use case. Closing in anticipation of those additions.
How would I organize organizers if my business logic is as follows