Domain Oriented Programming

The software that we write often invokes other software to get parts of the job done. These are known as Services or Dependencies. If Class A is making use of some Class B, then Class A depends on Class B, so Class B is a dependency of Class A. The principle of Dependency Inversion (says that a class should not contain any hard-coded direct calls to specific instances of any of its dependencies. Instead, it should receive these instances as parameters during initialization.

That's all very nice, but passing dependencies around can become quite a complicated business, and in large systems it can become a nightmare.

Various mechanisms have been devised for solving this problem. One that I know of is Service Locators (), another is Dependency Injection Frameworks (). Unfortunately, each of them has various disadvantages.

Service Locators 
  • A service locator is a mandatory global dependency. That's a bad thing to have. At some point you will want to reuse a module in a different system, and that service locator will not be available there, and you will have to start rewriting stuff. Trust me, you will sooner or later regret having it. 
  • A service locator may defer compile-time errors to run-time errors. These errors occur when a system is being wired together, but tests are usually wired up differently, so these errors cannot be detected with unit testing or integration testing, you have to do end-to-end testing in order to discover them. 
Dependency Injection Frameworks 
  • They work by magic, and I don't like magic. 
  • They tend to embrace silent failure, while I mandate hard failure. 
  • They don't have an API that you can call, so you cannot use code completion, you have to know stuff by heart. 
  • They tend to make application startup time slow as molasses, while I like application startup to be instantaneous.
In my 35 years of programming I have encountered the problem of dependency injection a lot, and in the last decade or so I have started solving it with a paradigm that I call Domain Oriented Programming

Note that Domain Oriented Programming does not have any direct relation to Domain Driven Design (), although it may be a natural paradigm to use when implementing systems designed with Domain Driven Design. 

Introducing Domain Oriented Programming (DOP)

The Domain Oriented Programming Design Pattern can be roughly described as follows: 
  • Every object ideally belongs to a Domain. In this case, it is called a Subject of that Domain. 
  • The Domain has complete control over the lifetime of each of its Subjects. This means that the Domain is the exclusive factory of its Subjects, and can also decide when and if a Subject is destroyed.
  • Domains and their subjects are closely coupled. This means that not only the Domain knows each one of its Subjects, but also, each Subject knows its Domain. This is epitomized by the fact that every Subject receives as its first constructor parameter a reference to the Domain to which it belongs.
  • The Domain contains references to all services that are used by itself and by all of its Subjects; so, when a Subject needs to use some service, it obtains the service from its Domain. Therefore, dependencies do not need to be injected into Subjects. 
  • Domains receive their dependencies as constructor parameters, or, when a Domain is in turn Subject of another Domain, by actively invoking their own Domain (SuperDomain) to get them.
There are a few things to notice here:

The Domain is to a Subject what the Object is to a Method. Hopefully a DOP oriented language will be introduced one day which realizes the DOP construct in its grammar, making the Domain reference implicit, just as the Object reference is always the implicit first parameter to every Method. Incidentally, Java is already doing something along these lines with non-static inner classes, but we do not want to have to nest the source code of each Subject within the source code of the Domain, especially since a Domain may in turn be Subject of another Domain.

Domain Oriented Programming does not require any platform or library: it is just a way of structuring code. So, with DOP, no omnipresent framework is needed for injecting dependencies, and no magic is involved in their propagation; nobody needs to query any service locators for services, (the availability of services is practically guaranteed by the compiler,) and no huge lists of dependencies are passed to constructors, either. Still, at various places where domains are constructed and wired together, all necessary services are supplied, so any one of them can be replaced with a Test Double ()

Furthermore, Domain Oriented Programming is interoperable with non-DOP systems: A group of classes making use of DOP among themselves can be introduced into a system which is already using different means of Dependency Injection.  A DOP system can have parts of it implemented using other mechanisms of Dependency Injection.

At first glance, Domain Oriented Programming can be thought of as employing something like Half-Way Dependency Injection, or Subsystem-level Dependency Injection as opposed to Class-level Dependency Injection. Dependencies are injected into the Domain, and from that moment on Subjects of the Domain can go ahead and actively fetch their dependencies from the Domain as needed, instead of having their dependencies injected into them. That's because according to Domain Oriented Programming, the individual class is a much too fine-grained unit to be applying dependency injection onto. Since Subjects and Domains are intimately familiar with each other, (closely coupled,) it is okay for each Subject to be fetching its dependencies from its Domain.

Things become even more interesting when we consider Domains that are in turn Subjects of other Domains, forming a hierarchy of Domains, where at each level we have SuperDomains and SubDomains. In this scenario, we do not exactly have Dependency Inversion going on anymore, because at each level dependencies are obtained from the level above; however, the same benefits offered by Dependency Inversion are still being enjoyed, because dependencies are still not hard-coded, they are propagated, and each Domain has control over each service that it makes available to its subjects, and may, if needed, decide the particular implementation that will offer it. 

The lesson to learn from this is that Dependency Inversion was never a goal in and of itself; the goal has been to avoid hard-coded dependencies, (Dependency Independence, if you will permit the pun,) and Dependency Inversion has been a mechanism for achieving it, but the same goal can be achieved by other means, such as Dependency Propagation, which is what Domain Oriented Programming offers.

Domain Oriented Programming is not only about Dependency Propagation. It reflects the realization that Software being created today is immensely more complex than what it used to be when Object Oriented Programming was invented, and the first Object Oriented languages were laid down. 

It used to be that all we needed was a means of coupling groups of functions with the data that they operate on, and that Inheritance, Encapsulation and Polymorphism were only necessary at the object-method level; but as we build more elaborate software, we find ourselves more and more thinking not so much in terms of classes and methods, but in therms of subsystems and classes, or systems and subsystems. Therefore, there appears to be a need for a mechanism that brings Inheritance, Encapsulation and Polymorphism one level up, to the subsystem level, and by recursive application, to the entire system. That's what Domain Oriented Programming is about.

My first public mention of this concept was in this answer of mine on Software Engineering Stack Exchange.

No comments:

Post a Comment