Domain-Controlled Architecture

Introducing Domain-Controlled Architecture (DCA) as an alternative to MVC, MVVM, and similar approaches, to simplify overcomplicated projects.


If you asked me to sum Domain-Controlled Architecture (DCA) up in one word, it would be Elegant. On its own, it might not seem like much, might not seem particularly groundbreaking, and might even seem to be a little old school. DCA wasn't created to impress anyone. It's a tool in our kit that helps us to navigate the realities of the modern technology landscape, where there is an overabundance of complexity in terms of boilerplate code, code generation hell, and endless examples of untestable, unmaintainable, and unstable software. DCA is a software development architectural pattern that is so simplified that I imagine many people will assume it is lacking, can't work in today's complex world, and that it won't be worth your consideration. Having conceptualised the pattern, and worked with it for quite some time now, I am in some ways both DCA's and my own worst critic, yet I would also put this simple design up against any other architectural model any day, and if someone told me that my life would depend on the architecture I chose, I would choose DCA every single time. Not because I defined it, but because it never lets me down. It has fulfilled every expectation, and even been adapted to systems and configurations that I had never originally intended it for.

I am not going to call DCA a silver bullet solution. No design truly is, and every architecture has its benefits and its applications. However, if any design could be said to come close to being a one size fits all solution, DCA would be a top candidate. There is one very good and simple reason for this. DCA encourages adherence to sound software engineering principles. It strips away all of the unnecessary ceremonial elements of modern architectures, relies on minimal GoF patterns inspired concepts, and defines a strict set of rules to keep your efforts focused and in line with the ideals of the architecture. It ensures your software is built such that it is deterministic and relatively easy to test and maintain, with a reduced cognitive requirement to understanding and implementation.

DCA is deceptively simple in it's design and concept. However, strict application of its design principles ensures you can create scalable, robust applications, that require minimal to zero boilerplate, are less prone to brittleness, and do not require code generation. Where some might consider this a little "old school", I would argue that DCA's back to basics approach works because it leans into the proven foundational software engineering principles that are often forgotten in today's stitch and assemble mentality.

The basic architectural layering is typical in that it aims to separate software into the GUI-Domain-Infrastructure triplet that has been a staple concept for decades now, that conceptually underpins all of the modern architectures, such as MVC, MVP, MVVM, and others. While many concepts are shared, the language is deliberately different in the DCA, such that where other architectures are predominantly UI or infrastructure focused, DCA is intentionally domain-centric, making it much simpler to align with behaviour and rules forward testing and with adherence to its design principles.

Domain Controlled Architecture - Design

Doman-Controlled Architecture (DCA) layer/OR diagram

UI Layer

UI elements are ultra thin. They are only concerned with layout, UI state, and Sending/Receiving data via a Domain Controller. There is no program or domain logic at the UI level, keeping the presentation ultra thin, and replaceable. A UI knows only about its own data, and the Domain Controller that it connects to.

Domain Control Layer (DC)

Domain controllers are also kept deliberately thin. Their purpose is to determine what it is the user is asking through the UI, and route UI signals accordingly. DCs don't change data, don't apply rules or corrections, they just glue all of the working parts of the software together. They are event-driven schedulers, coordinators, and manage all of the internal application signalling. Very little logic should be needed, except to decide how and when to handle and route event signals. They are intentionally blind to anything that happens in the other layers, and only need to know enough about the other layers to route event traffic efficiently.

Domain Layer

These classes capture the business rules and workflows of a system. They are self-contained, blind to everything else in the system, and entirely I/O driven. They are the tools occasionally called on by the DC to manage the event data being passed around by the UI and Translation layers, but should not normally need to connect to anything else to function. Some Domain objects - such as Data Transfer Objects (DTOs) may have some utility in the UI and Translation layers, but otherwise are typically only accessed by a Domain Controller.

Translation Layer

This exists for the sole purpose of creating a clear separation of responsibilities so that the BoltOn elements can be kept completely isolated, and ensure no leakage of concepts or function that do not belong innately in a DC. Translators are typically command objects, or may simply be the data manipulators that ensure their matching Bolt-Ons can be easily replaced with an alternate comparable technology with minimal. Instead of needing to vertically implement hundreds or even thousands of lines of boilerplate, a simple drop-in replacement translator can be defined instead, without affecting any other part of your software.

Bolt-On Layer

While the name sounds clunky, "Bolt-On" was chosen to identify that this is not a traditional "plug-in" type of system. While conceptually similar, Bolt-Ons are not plugins, and do not carry any of the overheads or complexity of a true plug-in system. If the DC was a signpost, the Bolt-On is an interchangeable component - like the signs that can attach to the signpost. They are not necessary for core program function, and ultimately replaceable. Bolt-Ons are entirely self-contained, and represent all of the "low-level" parts of your software that should be easy to replace with minimal coding effort, such as file handlers, databases, Comms APIs, hardware interfaces, and so on.

DCA Signalling

Doman-Controlled Architecture (DCA) signalling/sequence diagram

The sequence diagram above shows how signalling will typically be routed in a DCA application. The Domain Controllers sit at the logical heart of the application, and are themselves event drivers and event-driven. Signalling stays within the strict boundaries of the layer relationships, never crossing boundaries to shortcut any other part of the system. This encourages designs to remain loosely coupled, and highly cohesive. Decades-old concepts that have been largely forgotten in modern systems, but which ensure code is robust rather than brittle.

Keeping the signalling as flat as the architecture also makes it much easier to conceptualise where things belong, and easier to figure out how to make everything work together as a cohesive whole. Event-driven systems also work extremely well in asynchronous systems when they are used correctly. That generally means sticking to the concepts and principles already outlined above, and being disciplined enough to not allow yourself to allow code from one layer to pollute another.

Recommended Implementation Options

At Perridak Software, we exclusively design our applications using DCA. We see our open source APIs as Domain-level utilities, though they may be used at any level as they too are self-contained. For the commercial designs that we have developed privately, or that we are working towards releasing in the near future, we stick with DCA because we know that the trade-off of a few months of tough problem-solving and agonising over sticking strictly to the DCA ideals will mean accelerated dvelopment iterations later, and result in much lower maintenance overheads post release.

For our general design, we create a DC for every UI screen, and sometimes even two or three DCs for a single very busy UI if needed. Most of our Bolt-ons are created as singletons to avoid file-locking issues, and to make it easier to access them from multiple DCs without needing to explicitly pass them as variables. For databases, we often create a base translator, and derive command translators such that we only need to modify the base translator if we want to swap for another database system. We usually treat all of the UI, DC, Domain, and Translator classes as disposable, with the DC's creating what they need from the other layers when they are called on.

While the ideals of DCA may sound restrictive, working strictly DCA also tends to be a lot more flexible than at first glance. We have experimented with Monolithic classes in the UI and DC layers, applying the pattern across a variety of platforms, and even split the architecture for edge computing and in unique client-server and microservices situations. DCA has never let us down, and has kept software development fun for us as well.

Final thoughts

I deliberately did not create an example in code for you to examine, because this article was not meant to show you a single way to apply DCA. Examples often lead readers to think that there is only one recommended way to do things, and that misconception often leads to other conceptual bottlenecks later down the line. So treat this article as an introduction to the concepts and a loose guide about what you might do to solve big architectural issues simply and leanly, rather than leaping to a more complex idea that may be overkill. Even DCA will be overkill for the simplest one-off apps, where a code-in-a-RAD-event approach may actually be more suitable.

No single architecture will solve all of your problems. All of the architectures available are variations on each other, and were introduced to deal with the issues that a group of programmers were facing at the time. For me, Domain-Controlled Architecture was my way of solving that age old problem of being unable to see the forest for the trees. I wanted to simplify complex problems, not lose the elegance of the code and designs in my own applications in the overhead of somebody else's design philosophy, and most of all, I wanted to ensure that the code we create is well engineered, and won't cause us to lose momentum with endless maintenance. I feel I've achieved this with DCA, and I hope that it will either help you, or inspire you to find creative solutions to your own software engineering problems too.


Want to discuss this article? Join the conversation on GitHub: Discussion #50