0

Reading a little bit about Functional core & imperative shell pattern, I am wondering if the DI principle can be improved.

What happen if instead of having an Infrastructure layer depending on concrete domain objects, e.g.: IUserRepository.Save(User obj), we create a Core layer just with abstractions like: IAggregateRoot, IEntity, IInvariantValidation, etc. The Domain layer will implement those interfaces and will have just pure functions. Then the Infrastructure layer will depend only in the Core layer, e.g.: IRepository.Save(IAggregateRoot)

I think this way the infrastructure will remain mostly the same without major changes, and the domain that is easier to test will be very easy to evolve. Then there will be an Application layer that will depend on Domain & Infrastructure layers

Domain -> Core <- Infrastructure

Domain <- Application -> Infrastructure

This is not a concrete question but I want to know if you see some cons in this approach and if it already exists where I can find some material about it.

1
  • Abstraction doesn't mean only "an interface" or "abstract class"; the domain objects are high level abstractions of the problem domain. Yes, some of those objects would be more internal to the layer, while others would be more outward facing - like your Core interfaces. The interfaces you'd put in Core are conceptually owned by (defined to serve the needs of) the domain layer, so the situation isn't fundamentally different; your infrastructure layer still depends on the "overall domain" model, it's just that your packaging/deployment scheme separates these interfaces out. Commented Dec 12, 2020 at 13:00

2 Answers 2

2

The main critique I have of this proposal is about the general approach to design.

Architecture and design are tools for software development, not the end-result. Although it is intellectually satisfying for us software developers to come up with clever designs and it seems it is the most important point of our profession, it is actually not. Our purpose is to understand what the software should do and translate this knowledge into software.

This has a couple of consequences. First, the design should always follow the requirements. Basically there should not be a "domain", "infrastructure" and "core" as top level components/parts/layers. The organization of code, modules, layers should already tell the story of the requirements. It should be designed specifically for the case at hand.

Second, there can not be a "general" design or pattern, that is universally applicable. Since each software's design should reflect its purpose it is pretty clear that designs should only be similar if the software's purpose is similar, not when its technology is similar.

Third, instead of trying to have as generic components as possible, it is more desirable to have as specific components as possible. The more specific you design your components the more information they can communicate about intent and purpose, and the more focused and maintainable they become.

Fourth, you can not have classes like IEntity, IAggregateRoot, IInvariantValidation. These are obviously not about your requirements, so for the reasons above, they can not be part of the software, they can not refer at any point to any of the classes in the software.

0

What you're suggesting is already a thing, though you'll find that what you call "core" is often called "contracts".

There's nothing wrong with separating the interfaces from the implementations in different projects, as long as the implementation project depends on the interface project. It might cost some more effort in juggling, but if you feel the need to separate them, you'll generally already agree to that added cost.

A second cost to this approach is that your contracts must be complete. That is to say that either User has to be part of the contracts (this is acceptable for value objects and structs), or you have to abstract an IUser that lives in the contracts and that User then depends upon.
This is again a matter of more juggling, but a generally acceptable amount when you've already decided that you'd like to take this approach.

There are good reasons to do this. For example, you may want to expose access to your interfaces but not your implementations.

A good example here is game mods. You give the modders access to your interfaces, so that your game can later read those mods and work with the created content (using those interfaces), but you don't want your modders to be able to access/override your core logic.
This can be done by exposing only your interfaces, which is much easier done when they are in a separate project of their own.


However, this does not improve dependency inversion. It's a completely unrelated improvement.

Separating the interfaces from the implementations can be done regardless of whether you're using inverted dependencies or classic dependencies. This separation is not related to the inversion of the dependencies, nor does it tackle the same problem that inverted dependencies do.

4
  • I think that what I wanted to express is not about the interfaces, is about their dependency direction, the infrastructure expose some interfaces like IAggregateRoot, IEntity, etc. and the domain implements them Commented Dec 15, 2020 at 0:49
  • @LeonardoMangano "the infrastructure expose some interfaces like IAggregateRoot, IEntity, etc. and the domain implements them" If this is the case, then you are not using dependency inversion. You may be getting confused between inversion of control (i.e. "regular" dependencies) and dependency inversion (i.e. inverted dependencies). What you wrote here is a case of the former. And that's perfectly fine to implement, you don't have to invert dependencies, but then your question does not match your situation.
    – Flater
    Commented Dec 15, 2020 at 9:33
  • @Falter Dependency inversion talks about decoupling functional components so that higher level components don't depend on lower level components. I am trying to invert that and say that the lower level components shouldn't depend on higher level components Commented Dec 16, 2020 at 11:27
  • @LeonardoMangano: If you invert dependency inversion,, that's a double negation. In doing so, you end up with a classic dependency structure, what I called "regular dependencies" in my previous comment. But my point remains that what you're proposing isn't necessarily bad, but it is not an improvement to dependency inversion. It's simply a different approach, one that simply doesn't utilize dependency inversion.
    – Flater
    Commented Dec 16, 2020 at 11:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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