Enhancing Angular Projects with Domain-Driven Design in an Nx Workspace
Introduction
As Angular/JS/Node projects grow in complexity, structuring them in an efficient and manageable way becomes inevitable. We at Cheveo are using NX for nearly every project and in my journey as an Angular Consultant I came across a lot of companies currently using Nx. Using an Nx workspace can provide a robust scaffold for structuring projects, especially when combined with Domain-Driven Design (DDD) principles as outlined by Manfred Steyer.
This post will delve into how to apply these principles within an Nx workspace to organise your code effectively, enhancing project scalability and maintainability.
Since I started this series by introducing a way to toggle features and hide/show UI elements depending on permissions via guards and directives, we will initially move the old stackblitz to an Nx Workspace that will later be available via Github.
Understanding Domain-Driven Design (DDD)
Domain-Driven Design is a software development approach focused on modeling software to match a complex business domain. Manfred Steyer’s adaptation of DDD for Angular projects emphasizes creating clear boundaries for different aspects of business logic, thus ensuring that the software structure closely mirrors business processes. This alignment helps developers understand the business logic faster, reduces dependencies, and eases both development and maintenance. In his book Angular Enterprise Architectures he also introduces ways and ideas on how to identify such domains effectively. The clear identification of a domain will help to easily structure and encapsulate code bits and will lead eventually to significant improvements in application architecture. By effectively identifying and isolating domains, organisations can ensure that their code is both highly cohesive and loosely coupled.
Enhancing Cohesion and Reducing Coupling with DDD
Cohesion refers to how closely related and focused the responsibilities of a single module are. In the context of DDD, when a domain is clearly identified and isolated, all functionalities related to that domain are encapsulated within a single library or module. This high level of cohesion ensures that the module can perform its intended functions independently without unnecessary dependencies on other parts of the application. It simplifies understanding the module’s purpose and functionality, making the code easier to manage, test, and develop further.
Loose coupling, on the other hand, describes the degree to which modules are independent of each other. In DDD, by defining clear boundaries between domains, each module interacts with others through well-defined interfaces, reducing the direct dependencies among them. This separation allows changes to be made to one module without significantly affecting others, thus enhancing the system's flexibility and reducing the risk of bugs spreading across modules. The layer that supplies external modules with functionality of another domain is the so called API layer. In the end this is the same as for example REST APIs where the basic calls don't change and any implementation details are usually abstracted away from the user/consumer.
Applying SOLID Principles
The principles of strong cohesion and loose coupling are core aspects of the SOLID principles, which are a set of design guidelines for making software designs more understandable, flexible, and maintainable. Here’s how DDD in an Angular project, as outlined by Manfred Steyer, aligns with these principles:
- Single Responsibility Principle: By isolating each domain into its own library, we ensure that each module is responsible for a single aspect of the application.
- Open/Closed Principle: Modules are open for extension but closed for modification. New functionalities can be added as new features or extensions in their respective domains without altering existing code.
- Liskov Substitution Principle: The design ensures that new derived classes (e.g., new features or services in a domain) extend the base classes without changing their behavior. (This actually does not apply too much in Angular, since it prefers composition over inheritance)
- Interface Segregation Principle: Interfaces are client-specific rather than general. Angular services and components within each library communicate via narrow, domain-specific interfaces, which prevent "interface pollution" where a single interface contains too many methods irrelevant to the implementing class.
- Dependency Inversion Principle: High-level modules do not depend on low-level modules; both depend on abstractions. In an Nx workspace, domains interact through interfaces or abstract classes, ensuring that high-level business rules are not dependent on the low-level implementations.
By embracing Manfred Steyer’s DDD approach within an Nx workspace for Angular development, projects can achieve an architecture that not only adheres to these SOLID principles but also provides a robust foundation for growth and scalability.
Setting Up Libraries in an Nx Workspace
Following Steyer's DDD approach, we structure our Angular project by dividing it into several libraries, each corresponding to distinct domains or functionalities. Here’s how we implement this:
App Configuration Library
nx g @nx/angular:library libs/app-config --tags=config --prefix=config
this library handles all configuration-related aspects, which are essential across all features but are not tied to a specific business domain. It's a strategic decision to isolate configurations, ensuring that any global settings or environment-specific details are maintained separately from business logic.
Feature Libraries for Specific Domains
nx g @nx/angular:library libs/feature-a/feature --tags=scope:client,type:feature --prefix=cl nx g @nx/angular:library libs/feature-b/feature --tags=scope:client,type:feature --prefix=cl nx g @nx/angular:library libs/home/feature --tags=scope:client,type:feature --prefix=cl
Each feature library represents a specific business domain or set of functionalities. This granular division aligns with DDD by ensuring that each library is self-contained with its domain-specific logic, components, and services. Such isolation not only simplifies the development and testing of domain-specific features but also enhances the reusability of these libraries across different parts of the application or even in different projects.
Utility Library for Authentication
nx g @nx/angular:library libs/auth/util --tags=scope:client,type:util --prefix=auth
Although not directly containing business logic, the utility library for authentication supports the authentication domain by providing necessary utility functions and services that facilitate authentication processes across the application.
Authentication Feature Library
nx g @nx/angular:library libs/auth/feature --tags=scope:client,type:feature --prefix=auth
Focused on the authentication domain, this library should encompass all authentication-related functionalities. Actually in our case it only holds a dumb component, which should reside in a ui lib, but for demonstration purposes it shall stay there. Segregating authentication into its dedicated library ensures that security concerns are centralised and can be managed more effectively.
Deep Dive into the Authentication Domain and Feature Integrations
The Authentication Domain within our project is specifically structured to encapsulate all security and user authentication mechanisms. It includes both utility and feature libraries, which not only handle the common functionalities like token management and user session storage but also enable more complex security models involving role-based access controls.
Feature Guards
Feature guards in this context act as gatekeepers in our application. Integrated within the routing configuration, these guards utilize the auth library's services to check if the current user has the necessary permissions to access certain routes. This is achieved by dynamically consulting the feature toggles tied to user roles, which are managed through our backend configurations. This dynamic capability allows us to adjust features and access controls without redeploying the application, aligning perfectly with modern application needs for flexibility and live updates.
Feature Directive
The HideFeature directive, part of our client feature libraries, leverages the same backend-driven feature toggles to control the visibility of specific UI components in real-time. By binding this directive to elements in our Angular templates, we can show or hide elements based on the user's current feature access list. This allows for a fluid and adaptable user interface that responds to changing permissions and feature sets.
Conclusion
Directory structure after applying DDD Implementing Manfred Steyer’s DDD approach in Angular projects using an Nx workspace provides a strategic advantage in managing complex project architectures. By organizing code into domain-specific libraries, projects become more intuitive, aligned with business processes, and easier to maintain as they scale. The detailed setup of the authentication domain, along with the integration of feature guards and directives, demonstrates a practical application of DDD principles to enhance security and user experience in modern web applications.
Key Benefits 🎯
- Scalability: Easily extend or modify your project while maintaining a consistent architecture.
- Maintainability: Code is logically grouped, making it easier to find, understand, and update.
- Flexibility: Dynamic feature toggles and guards provide flexible feature management.
- Alignment with Business Logic: Domain-focused libraries ensure your app reflects actual business processes.
Whether you're working on a large enterprise application or a modular project, applying these principles can markedly improve your development workflow and output quality.
Ready for the Next Step? 🚀
In the next installment, we'll delve into creating a NestJS app that returns dynamic feature configurations via an AuthController. We'll link our backend seamlessly to our Angular frontend, enabling real-time feature management. Stay tuned for Part 3! ✨
#Angular #NxWorkspace #DomainDrivenDesign #SOLID #WebDevelopment #FeatureManagement #Authentication #Security
Recap of Commands Used
Here's a quick reference for the Nx commands used to create our libraries:
-
App Configuration Library:
nx g @nx/angular:library libs/app-config --tags=config --prefix=config
-
Feature Libraries:
nx g @nx/angular:library libs/feature-a/feature --tags=scope:client,type:feature --prefix=cl nx g @nx/angular:library libs/feature-b/feature --tags=scope:client,type:feature --prefix=cl nx g @nx/angular:library libs/home/feature --tags=scope:client,type:feature --prefix=cl
-
Utility Library for Authentication:
nx g @nx/angular:library libs/auth/util --tags=scope:client,type:util --prefix=auth
-
Authentication Feature Library:
nx g @nx/angular:library libs/auth/feature --tags=scope:client,type:feature --prefix=auth
About the Author
Danyal
Iqbal
Senior Software Engineer
Who Am I?
I’m an enthusiastic web developer boasting over 6 years of experience crafting web applications using technologies such as Angular, NestJS, TypeScript, NodeJS, Golang, Amazon Web Services, and Firebase. I have a relentless drive for learning new techrelated concepts and techniques to continually enhance my engineering skills. I take pride in my strong communication abilities and enjoy engaging in discussions about effective implementations and architectural ideas.