1

This is a very tricky question, because when we check the rules it's not explicit that a repository couldn't call an UseCase. However, it doesn't seem logical. Is there any definition/good practices and why it shouldn't do this?

Thanks!

  • Can you provide more context? What functionality of a use case would you want to call from a repository and why? Out of the blue I could not think of a case where this would be necessary. Repositories manage collections of aggregates and can provide specific search functionality related to business transactions. So you usually they do not coordinate workflows like use cases do. – Andreas Hütter Mar 27 '21 at 19:16

1 Answers1

4

The short answer is "No" - it shouldn't, regardless of the context (in most of all cases). As to why - the definitions, principles and good practices - it may be helpful to think in terms of clear separation of concerns across your whole Clean Architecture implementation.

Consider this illustration, as background at thinking about how one could organize the interactions (and dependencies) between main parts of a Clean Architecture.

enter image description here

The main principles illustrated are, that -

  • Through its execution, the Use Case has different "data needs" (A and B). It doesn't implement the logic to fulfill them itself (since they require some specific technology). So the Use Case declares these as two Gateway-interfaces ("ports"), in this example. And then calls them amidst its logic.

  • Both of these interfaces declare some distinct set of operations that should be provided (implemented) from "outside". The Use Case, in its logic, needs and invokes all of those A and B operations. They are separated into A and B, because they are different kinds of responsibilities - and might be implemented by different parts of the system (but not necessarily). Let's say that the Use Case needs loading of persisted domain objects (as part of A operations), but it also needs to retrieve configuration (as some key-value pairs), which are B operations. These interfaces are segregated since both sets of operations serve distinct purposes for the Use Case. Anyhow, it's important design-wise, that they both explicitly "serve" the Use Case needs - meaning, they are not generic entity-centric DAO / Repository interfaces; they ONLY have operations that the Use Case actually needs and invokes, in exactly the shape and form (parameters, return values) that the Use Case specifically needs them. They are "ports" to be "plugged into", as part of the whole Use Case.

  • The "outside" providers of these responsibilities are the Adapters (the implementers) of those needs. To fulfill them, they typically use some specific technology or framework - a database, a network call to some server, a message producer, a file operation, Spring's configuration properties, etc.

  • The Use Case is invoked (called) only by Drivers side of the architecture (that is, the initiating side). The Use Case itself, in fact, is one of the "initiators" for its further collaborating parts (eg, the Adapters).

  • On the other hand, the Use Case is "technically supported" (the declared parts of its needs "implemented") by Adapters side of the architecture.

  • Effectively, there is a clear separation of who calls what - meaning, at runtime the call stack progresses in a clear directional flow of control across this architecture.

  • The flow of control is always from Drivers towards Adapters (via the Use Case), never the other way around.

These are principles I have learned, researched, implemented and corrected purely across my career in different projects. In other words, they've been shaped by the real world in terms of what has been practical and useful - in terms of separation of concerns and clear division of responsibilities - in my experience. Yours naturally may differ, and there is no universal fit - CA is not a recipe, it is a mindset of software design, implementable in (better and worse) several ways.

Thinking simply though, I would imagine in your situation Repository is your "data storage gateway" implementation of the Use Case's (Data) Gateway. The UC needs that data from "somewhere" - without caring where it comes from or how its is stored. This is very important - the whole core domain, along with the Use Case needs to be framework and I/O agnostic.

Your Repository fulfills that need - provides persisted domain objects. But the Use Case must not call it directly, instead it declares a Gateway (in Hexagonal eg Ports & Adapters architecture, named a Port) - with needed operation(s) that your Repository needs to implement. By using some specific (DB / persistence) technology, your Repository fulfills it -it implements one of the Use Case's "ports", as an Adapter.

With the above being said - on rare occasions, some Gateway implementations may demand exceptions. They might need several back-and-forth-going interactions, even across your architecture. They are rare and indeed complex situations - likely not necessary for a Repository implementation.

But, if that is really an inevitable case - then it's best if the Use Case, when calling the Gateway, provides a callback interface as a parameter of the call. So during its processing the Gateway's implementer can call back using the operations in that interface - effectively implementing the back-and-forth necessity. In most of all cases though, this implies excessive logic and complexity at the adapters' level, which should be avoided - and serves as a strong cue that the current solution should be re-designed.

Risto Uibo
  • 118
  • 5