Angular is an opinioned framework which means it is shipped with design patterns and concepts that make our life easier. Modularization and dependency injections are good examples.
Although these concepts are great, they are not sufficient in huge enterprise level applications. How should I organize my code? How can I protect against circular dependencies? These questions among many others impose a challenge that every engineer and developer must face.
Clean architecture concepts and their implementations come to the rescue. But we cannot simply apply all these concepts together. This would introduce more problems than it would solve. We should understand which quality attributes we would like to target, based on our underlying architecture problems. This means that a problem leads to architectural decisions which in turn introduce one or more quality attributes.
Figure 1 - Problem Quality Decision
As a project gets bigger and bigger with incremental features, problems such as complexity and high coupling arise. Before we start refactoring by throwing in some clean architecture design patterns, we should decide on which quality attribute(s) we want to tackle. Here it becomes a bit tricky because focusing on one attribute might have a negative impact on other attributes. For example, focusing only on the modifiability might reduce the testability and the reusability attributes. So the best way is to balance a group of quality attributes by introducing the right pattern(s) which will eventually solve our architecture problem(s).
In this article series, we are going to focus on the attributes, shown in Figure 1, by introducing a simplified
layered domain driven design architecture in
Part 2.
And then extend this architecture to use more patterns in
Part 3.
All introduced ideas and concepts can be applied to any chosen frontend technology and not just Angular.
Why layered domain driven design? Because DDD is an important building block in clean architecture that will allow us to tackle all our target quality attributes at the same time.
DDD centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain. The main goal of DDD strategic design is to define Bounded contexts, Ubiquitous Language and Context Maps along with the entire project team while the tactical design models the building blocks which are Entities, Services, Repositories, Value Objects, Aggregates, Events, Modules, Factories, etc.
Combined with layered architecture, It can separate elements of design to help developers manage the complexity in the code base. Adapting the strategic design introduced by Eric Evans, we can define four main layers:
And the most important concept in this layered architecture is the inward dependency direction. From the outer layer to the inner layer. Form the infrastructure layer to the domain layer.
Figure 2 - Frontend DDD layers
Well first, we should define our domains and then for each domain we build these layers. Let's assume that we want to build a simple content management and viewer application. It consists of two parts: A left sidebar and a viewer. In the sidebar there are two sections: A tree view section that shows our folder hierarchy with their files content and the second section is a list that shows available dashboards. Once you click on any item in the sidebar, its content appears in the viewer. We can also manage folders, files, and dashboards by right clicking on any item to see available actions in a context menu
If we consider the sidebar only, how many domains can we extract here?
Let’s see, we have the sidebar itself, a tree view for folders (let's call it spaces), dashboards list, and settings context menu. This would be my view of available domains:
After extracting domains, we create a grouping folder for each one. I normally use Nx because these domain folders go naturally into the libs folder introduced by it. Within each domain folder, we create a folder structure that reflects our DDD layers as we will see in the next sections.
Now the golden question: How does this layered domain driven design affect our targeted quality attributes?
Modifiability:The layers encapsulate the complexity and the responsibility and define clear rules related to data flow and dependency direction. They separate the concerns into independent parts where different teams can work simultaneously without interfering with one another. Features and updates would be easy and safe to implement because touching on part of the software will not affect the other parts.
Testability: When every software component is abstracted from its dependencies, we won’t have complex interconnectable pieces that are impossible to mock. We can isolate and test it easily with clear contexts.
Performance: Having small concern driven lazy loaded bundles would definitely make our application faster.
In Part 2, we are going to start with a simple layered domain driven design. And introduce additional design patterns and concepts along the way.