Part 3 - Extended Approach

While the simple approach is suitable for small to medium sized projects, it is not sufficient enough when we are dealing with enterprise-level applications with sophisticated business logic and complex state interactions.

In this approach, we are going to extend our layers by adding an independent business logic layer:

 Extended Approach Layers

Figure 1 - Extended Approach Layers

As you can see in the previous diagram, we extracted the business use cases (including domain models) into a separate inner layer. We protected our core business logic by removing the direct dependency on the data layer which adds additional abstraction value.

In this architecture, we also utilized the Adapter/Port pattern. Where ports are nothing more than abstract classes or interfaces implemented by the adapter services. We define two types of ports:

 Adapter/Port in layered architecture

Figure 3 -Adapter/Port in layered architecture

 Extended architecture state sharing

Figure 4 -Extended architecture state sharing

If we consider the dashboard domain again, from Part 1 example, the domain folder structure will look like this (complete code here):

dashboards/

It is worth mentioning that the extended approach adds more abstraction to our architecture which improves both the modifiability and the testability of our application but on the cost of simplicity and usability.

The main complexity arises from the need to use injection tokens to be able to inject services located in the data layer into the adapter services located in the business layer without breaking the depen dency direction rule.

If we take a closer look at the `dashboard-use-case.ts` implementation, we can see that the service injects a data port (abstract class) and implements a UI port (abstract class). This is why there is no direct dependency on the data layer anymore. Ports also improve testability because we are able now to easily mock dependencies. In the `feature-list.component.ts` which is a container component, we inject the UI port implemented by the adapter. Any change to the data layer (for example switching from API v1 implementation to API v2) won’t affect our business logic because all we have to do is switch the injected token from the old implementation of the data port to the new one.

Conclusion

In this article series, we discussed some of the architectural patterns and concepts in the frontend. We introduced layered domain driven design in simplified and extended approaches that provided different levels of abstractions and decoupling

We discussed the relation between any architectural decision and some of our focused quality attributes.

We utilized some concepts and patterns such as Adapter/Port, state sharing, and container/presentational components.