Dependency Inversion Principle and DDD

Lahiru Karunatilake
5 min readJan 29, 2020

Dependency Inversion Principle (DIP) is a technique used in Object Oriented Programing paradigm. The pattern was surfaced by Robert Martin (Uncle Bob) around 1994. Dependency Inversion is one of the five principles in SOLID. Before digging into DIP, it is good to understand what are Dependency Injection (DI) and Inversion of Control (IoC). Engineers tend to misinterpret DIP, DI and IoC as the same. DI and IoC are used in tandem with DIP.

As per Brett L. Schuchert (text)1, DI is about how one object knows about another, dependent object.” When an object needs to send an SMS after some business operation, it refers to an object capable of sending SMS by passing a message. So the process of referencing the SMS processing object is via DI. A prominent library in Java used this pattern is Spring. Spring offloads the means of injecting dependencies out of the core business logic of the application. In java, the new operation for the dependent object is managed and injected to the code by Spring. Dependency injection significantly improves code comprehension and testability of code. Spring profiles can be used to inject dependencies based on the context where the application runs. For example, the application can behave differently, while in unit testing, testing, per-production or production.

IoC is about changing the object which initiates the message. Typically, a message is sent from a dependent object to the independent object. In IoC, the message is being sent by a separate object to the dependent object. As an example, see how the Observer Pattern uses the IoC principle. The independent object (Subject) initiates the message to the dependent object (Observer). Another example is how Spring container uses IoC principle to inject dependencies. Developers annotate the bean specification(s) (Observer) in the domain object, and IoC container injects the dependency at runtime (Subject) to the domain object.

DIP is about changing the direction of source code dependency of dependent module on a independent module(s), while keep the call flow from dependent to independent. For example, an application service needing data from DB should not depend on Data Access Service (ReadRepository) of the lower layer. Instead, the application service should depend on abstraction at the same level. I.e. an Interface of Data Access Service in same Application Service. The DIP would use DI to inject the real implementation Data Access Service at the runtime without creating direct source code dependency. One can use an IoC container to ease DI process. Bob Martins SOILD principle trainings covers more about the DIP. He explains when call flow and source code dependency are directing to same side those programs become fragile, rigid and none reusable. With the help of polymorphism one can invert the direction of source code control.

Layered Architecture isolates the expression of the domain model and the business logic, and eliminate any dependency on infrastructure, user interface, or even application logic that is not business logic.

Now let’s see what Domain-Driven Design (DDD) is. DDD tries to isolate the core domain of a system into a model using a key set of abstractions like Entities, Value Objects, Aggregates, Events, Domain Services, and Bounded Contexts. The purpose of isolation of domain is to build a Ubiquitous Language between the developer and the domain expert. DDD tries to minimise the mismatch of domain understanding between a developer and a domain expert while removing the misunderstanding on software model by the same two experts. The software model here refers to the Domain Model. It is import to notice that a Domain Model does not include any none functional aspects of a system. Removal of non-functional aspects improves the ability to write more functional unit tests with ease.

Applying DIP and Layered Architecture to DDD

This is an object diagram of an application where it processes Earliest Arrival Time (ETA) of delivery packages periodically. While processing the ETA, it updates the delivery ETA in DB if the ETA is still in the future.

The above object diagram has two layers. Infrastructure which is the lower layer. Application and Domain are in a higher layer. The initial request comes to EarlierArrivalTimeService.storePresentETAs() method. This method is dependent on the same layer objects LatestETAGatewayRepository and PresentETARepository Interfaces. The implementation of these interfaces are in lower layer objects ETAGatewayRepository and ETAORMRepository. These two objects are injected at runtime to EarlierArrivalTimeService object using DI. To see how DIP is used in this example, one must recognize that lower layer ETAGatewayRepository and ETAORMRepository objects are now dependent on the higher layerLatestETAGatewayRepository and PresentETARepository Interfaces.

So what is the real use of Layered Architecture and DIP in DDD? The Layered Architecture and DIP enables to isolate the core domain of a system. DI and IoC container play a supporting role. In the above example, the core domain is simple. See below to understand how Layered Architecture and DIP is used to isolate the core domain requirements from none domain aspects of the application.

  1. Find the new ETA read time. This is the time that user invokes EarlierArrivalTimeService.storePresentETAs()method. It is part of the user input and is not part of the domain model.
  2. Use the read time to read the latest ETAs. The implementation is in the infrastructure layer ETAGatewayRepository object. The abstraction of ETAGatewayRepository the object LatestETAGatewayRepository is defined in Domain Model. So the lower layer dependent on the higher layer as per DIP. This means the ETA object is defined as a Domain Entity in Domain Model rather than a POJO in Infrastructure Layer. This isolates any business logic of ETA core domain to ETA Domain Entity. I.e. precisely to isPast()
  3. Check whether an ETA arrival time is in the past. The implementation isPast() does this validation within the ETA Domain Entity and in isolation.
  4. Filter out the past ETAs. The implementation presentETAs(List<ETA>):List<ETA> is in the ETA Domain Service. This an ETA core domain requirement to filter the past ETA from the recently found ETA list. This requirement too is isolated in ETADomainService.
  5. Save the present ETAs. The implementation is in the infrastructure layer ETAORMRepository object. The abstraction of ETAORMRepository object PresentETARepository is defined in Domain Model. So the lower layer dependent on the higher layer as per DIP. This means, the ETADomainService the object is defined as a Domain Service in Domain Model rather than the method in Application Service EarlierArrivalTimeService. This isolate any business logic of core domain object ETADomainService. I.e. precisely to presentETAs(List<ETA>):List<ETA>

The use of DIP, Layered Architecture and DI for Unit Tests

As discussed earlier when DIP and Layered Architecture is used in an application the core domain is isolated into testable units like Entities, Value Objects and Domain Services. In the example application above, the developer can independently test,

  1. ETA.isPast()
  2. ETADomainService.presentETAs(List<ETA>):List<ETA>

The DIP and Layered Architecture, further removes the core domain requirement from infrastructure code like ETAGatewayRepository and ETAORMRepository. The developer can avoid any duplicate unit tests by making the infrastructure code as a boilerplate.

Further, there are no core domain requirements in EarliestArrivalTimeService. The unit test for EarliestArrivalTimeService would be minimal and it will check only the flow of the request based on test dependencies inject via DI.

Foot Notes

  1. This text covers more of the original topic but in detail.

--

--