Pergunta

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?

Foi útil?

Solução

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.

Outras dicas

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

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
Licenciado em: CC-BY-SA com atribuição
scroll top