1

Suppose we have a layered architecture like onion architecture or clean architecture with three modules representing each layer:

domain.py
usecase.py
databaseadapter.py

The domain layer contains the business logic, so i have functions, which transforms data:

in usecase.py we have:

domain.call_business_logic()
domain.call_business_logic()

There comes a point, when the business logic needs to do I/O and hence has to call an adapter. I could now either inject the adapter into other domain in order to call it return the data, handle the adapter in the usecase and continue the logic.

  1. variant: injection

in usecase.py:

domain.business_logic(database_adapter)

in the domain.py call data_adapter to save in database

  1. variant: return to usecase everytime i do I/O:

in usecase.py:

value = domain.business_logic()

value_2 = database_adapter.save(value)

domain.business_logic2(value_2)

What is a better way? I would argue there is no problem with injecting adapters into the domain layer, but i would like to hear arguments against it and if its better to handle all I/O in the usecase and why?

2

Let us reword your question a little bit, so it becomes clearer: what you are asking here is if

 value = domain.business_logic1()
 value_2 = database_adapter.save(value)
 domain.business_logic2(value_2)

should be part of domain.py (implemented in some method domain.business_logic) or part of usecase.py.

Following Bob Martin's Clean Architecture, both approaches are valid, since both follow the "dependency rule".

However, the answer to this question depends on the responsibilites you intend to place in domain.py or usecase.py. If domain.py shall have the "Enterprise wide business rules" or "Use case independent business rules" (like the Entities layer in Clean Architecture), and usecase.py the "Application specific business rules", (like the Use Case layer in Clean Architecture), then you have evaluate what kind of business logic goes on in the code section above:

  • are those three code lines "use-case independent" logic? Potentially reusable in more than one use case? Then place them in domain.py.

  • or do those three code lines represent use-case specific logic? Not expected to be reused somewhere else? Then place them in usecase.py.

When in doubt, I would place it at the use case layer first. When it turns out later there is reusage possible, I would refactor the logic to domain.py.

As often, you cannot make such a decision just by looking at the structure of the code or the flow of control, but by looking at the semantics of the code and the intended responsibilities.

| improve this answer | |
2

The reason we apply layering in software development is to separate different concerns.

The domain layer is all about business rules. It is the heart of your software, it's why the software exists in the first place. It should be as clean as possible. It should not know or care what happens with the data.

The application service layer is therefor the correct place to persist the changes. The responsibility for this layer is:

  • Retrieve data from persistence
  • Invoke domain logic based on the request received
  • Persist new state and/or emit events
| improve this answer | |
  • what do you have to say about the constant "interruption" of the data flow – David Jul 31 at 8:59
  • I don't see an interruption. If you use a document db you can fetch an entire aggregate, call the required method on the aggregate and store the new state. If you have a relational db, with the help of an ORM you can just as easily fetch and store an aggregate. – Rik D Jul 31 at 9:17
  • is this a general statement on layered architectures? or is it specific to Clean Architecture ? - quote by Uncle Bob, its inventor: “Use Cases: The software in this layer contains application specific business rules – Christophe Jul 31 at 9:27
  • @Christophe this answer is not talking specifically about Clean Architecture. I didn't mention use cases, but said application service layer. Also note there is no single thruth to all of this. – Rik D Jul 31 at 9:35
  • @RikD I/O is much more than just a database call. that's why i brought up the term interruption. I would constantly have to go to the surface or rethink the way i am structuring it – David Jul 31 at 20:58
2

From the terminology you use, we can infer that you’re implementing Uncle Bob’s clean architecture .

In concentric architectures such as Cockburn’s hexagonal architecture (the forerunner of them all), Palermo’s onion architecture and finally clean architecture, the dependencies work inside out: the outer core is dependent on the inner:

  • The entity “layer” is at the core, and should encapsulate any entity and aggregate-level business logic to provide the building blocs for more complex business logic.
  • The use-case “layer” (in fact, it’s a core), should do business logic that does not fit into any single entity. It uses and composes behaviors of the domain entities and aggregates. It therefore depends on the inner core. It is not at this level that you should manage persistence.
  • The presenters and controllers should know about use-cases and entities, in order to provide the appropriate presentation and interactions to the outer layer.
  • The UI in the outer core should know all this in order to compose all the available behavior the things to the user

Whenever you’d need to go the other way round, you‘d use dependency inversion, using a well defined interface.

This is why you should go for option 1, preferring dependency injection over a lot of calls and returns, which might make all this less flexible and less readable.

Clarifications in view of the comments:

Since we are not fully using the clean architecture terminology of entity/use-case/interface adapters/framewor&drivers, and at the same time not fully use onion terminology of domain model/domain services/Application services/Adapters, and considering the many variants of terminologies, it’s necessary to clarify that:

  • Entity core is not only about entities but also related domain logic, such as for example repositories
  • entity+use-case still include domain logic, whereas controllers, presenters and gateway is more application logic. But one has to be careful with such mappings, because we move in different dimensions with different definition of what “application” means.
  • That use-cases is in principle not dealing with the persistence of entities
| improve this answer | |
  • i agree, that it is better for not interrupting the "flow". any disadvantages you see in comaparison to variant 2? – David Jul 31 at 8:55
  • The use-cases should not do business logic, that should be encapsulated within the domain entities. At least if you follow DDD principles. – Rik D Jul 31 at 9:03
  • @David one of the disadvantages would be the need to mock the database_adapter for every single unit test. – Rik D Jul 31 at 9:05
  • @RikD can you please provide evidence that support this statement from R.C.Martin’s book on clean architecture? – Christophe Jul 31 at 9:19
  • 1
    @Christophe Giving an aggregate (root entity) the knowledge how to persist itself, either directly or via IOC, isn't that basically the Active Record (anti) Pattern? – Rik D Jul 31 at 9:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.