Using Nx Workspaces and DDD
So far, we've only used a simplified version of the architecture matrix presented. In this lab, you'll add a luggage domain with an application of its own. This domain will fully align with the ideas outlined. To prevent repeating monotonous tasks, you'll use the open source Nx plugin @angular-architects/ddd.
Create a New Application
-
Add the Nx plugin
@angular-architects/ddd:ng add @angular-architects/ddd -
This command added some linting rules to your global
.eslintrc.json. Find out which ones and what they do.Hint: This task is a bit easier, if you use your IDE or editor (e. g. Visual Studio Code) to look into the current git staging environment.
-
Add an
luggagedomain with an application of it's own:ng g @angular-architects/ddd:domain luggage --add-app -
Add an
checkinfeature:ng g @angular-architects/ddd:feature checkin --domain luggage --entity luggage --app luggage -
Create a dependency graph to find out about the generated structure:
nx dep-graphIt should show this luggage application:
-
Inspect the generated libs and the generated app. You should find the following:
-
Assigned tags within
project.json -
Further access restrictions within
.eslintrc.json. -
A luggage data service, a luggage entity, and a check-in facade within the generated domain library
-
A check-in component within the generated feature library
Hint: This task is a bit easier, if you use your IDE or editor (e. g. Visual Studio Code) to look into the current git staging environment.
-
-
Make yourself familiar with these generated building blocks.
-
Switch to your luggage app and open the file
app.component.html. Remove its whole content. As the code generator wired the apps and libs up for you, you can directly call the generatedLuggageCheckinComponent:<luggage-checkin></luggage-checkin> -
Run your luggage app:
ng serve luggage -oIt should look like this:
Add Another Feature Library
Now, let's make our luggage application look like a more typical DDD-based application by adding another feature library:
ng g @angular-architects/ddd:feature report-loss --domain luggage --entity loss-report --app luggage
Generate another dependency graph (nx dep-graph). It should look as follows:
Inspect the Generated Access Rules
Now, let's try out if the generated access rules protect our architecture.
-
Restart your IDE/ editor to make sure it reads the updated
tsconfig.json,nx.json, and.eslintrc.json. -
Switch to the library
luggage-feature-report-loss. -
Open the file
luggage-feature-report-loss.module.tsand import theLuggageFeatureCheckinModuleand theFlightLibModule.Hint: You might need to create the necessary imports by hand.
Hint: If you get linting errors in your IDE/ editor, ignore them for the time being.
Show code
[...] import { LuggageFeatureCheckinModule } from '@flight-workspace/luggage/feature-checkin'; import { FlightLibModule } from '@flight-workspace/flight-lib'; @NgModule({ imports: [ CommonModule, LuggageDomainModule, // Add this: LuggageFeatureCheckinModule, FlightLibModule, ], declarations: [ReportLossComponent], exports: [ReportLossComponent] }) export class LuggageFeatureReportLossModule {} -
Switch to the console and lint the library:
ng lint luggage-feature-report-lossYou should get the following linting errors because the previously imported two modules violate our architecture:
ERROR: D:/bak/beratung/angular2/workshops_advanced/advanced-nx-workspace/libs/luggage/feature-report-loss/src/lib/luggage-feature-report-loss.module.ts:6:1 - A project tagged with "type:feature" can only depend on libs tagged with "type:ui", "type:domain-logic", "type:util" ERROR: D:/bak/beratung/angular2/workshops_advanced/advanced-nx-workspace/libs/luggage/feature-report-loss/src/lib/luggage-feature-report-loss.module.ts:7:1 - A project tagged with "domain:luggage" can only depend on libs tagged with "do main:luggage", "domain:shared" -
Remove the above introduced imports (
LuggageFeatureCheckinModule,FlightLibModule) again, to align with our architecture. -
Now, if you run the linter (
ng lint luggage-feature-report-loss) again, you shouldn't get any linting errors - at least no errors regarding access restrictions between apps and libs.
Bonus: Route Between Feature Libs
-
Open the file
luggage-feature-checkin.module.tsand add a child route for its component:[...] import { RouterModule } from '@angular/router'; [...] @NgModule({ imports: [ CommonModule, LuggageDomainModule, // Add this: RouterModule.forChild([ { path: '', component: CheckinComponent } ]) ], declarations: [CheckinComponent], exports: [CheckinComponent] }) export class LuggageFeatureCheckinModule {}For the sake of simplicity, we only use one route per feature module.
-
Do the same in the
luggage-feature-report-loss.module.ts.Show code
[...] import { RouterModule } from '@angular/router'; [...] @NgModule({ imports: [ CommonModule, LuggageDomainModule, // Add this: RouterModule.forChild([ { path: '', component: ReportLossComponent } ]) ], declarations: [ReportLossComponent], exports: [ReportLossComponent] }) export class LuggageFeatureReportLossModule {} -
Switch to your
luggageapp and open itsapp.module.tsfile. Add the following root routes:[...] import { RouterModule } from '@angular/router'; [...] @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, // Remove this line: // LuggageFeatureCheckinModule, // Remove this line: // LuggageFeatureReportLossModule, HttpClientModule, // Add these routes: RouterModule.forRoot([ { path: '', pathMatch: 'full', redirectTo: 'check-in' }, { path: 'check-in', loadChildren: () => import('@flight-workspace/luggage/feature-checkin') .then(m => m.LuggageFeatureCheckinModule) }, { path: 'report-loss', loadChildren: () => import('@flight-workspace/luggage/feature-report-loss') .then(m => m.LuggageFeatureReportLossModule) }, ]) ], providers: [], bootstrap: [AppComponent] }) export class AppModule {}Please note that these routes implement lazy loading of feature modules. Hence, we must not import these modules.
-
Switch to your
app.component.htmland add exchange the hard coded reference to a feature component by arouter-outletand a menu pointing to both features:<ul> <li><a routerLink="check-in">Check-in</a></li> <li><a routerLink="report-loss">Report Loss</a></li> </ul> <router-outlet></router-outlet> -
In order to style your luggage app a bit, add the following styles to the global
styles.css(orstyles.scss, etc.):body { font-family: cursive; padding-top: 80px; padding-left: 10px; } ul { list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333; position: fixed; top: 0; left: 0; width: 100%; } li { float: left; } li a { display: block; color: white; text-align: center; padding: 14px 16px; text-decoration: none; cursor: pointer; } li a:hover { background-color: #111; } -
Start the luggage app and make sure the routing works.
Bonus: Detecting Affected Libraries
After performing some changes, it's important to know which apps and libs might be affected by it. To rule out all the others, Nx can dramatically speed up retesting and recompiling your system.
-
If not already done, initialize a new git repo in your workspace root:
git initWe need Git here, because Nx uses the Git history to find out what changed.
-
Open your
nx.jsonand make sure, the branch affected/defaultBase points to the name of your main branch:"affected": { "defaultBase": "main" } -
Commit all your changes:
git add * git commit -m "whatever ;-p" -
Make sure your main branch is called
mainto follow current conventions (originally it was calledmaster):git branch -M mainRemarks: Our Nx configuration is assuming that the branch is called main. You can find the setting in your
sonwithin the nodeaffected/defaultBase. -
Switch to the
luggage-feature-checkinlib and open the fileluggage-feature-checkin.module.ts. -
Add a comment at the end to change the file.
-
Run the script
affected:dep-graph.nx affected:dep-graph -
Now, you should see all affected libs and apps highlighted.
-
To get the same information on the console, execute the following scripts:
nx affected:libs nx affected:appsInfo: Please also note, that the script
affected:buildonly builds the affected apps including all the libs they depend on. Also,affected:testonly executes the unit tests of all affected libs and/or apps. The scriptsaffected:e2edo the same for end-2-end tests andaffected:lintonly executes the linter for them.Important: Before calling
affected:buildyou have to compile everything to fill the Nx cache:npx nx build luggage --with-deps.
Bonus: Implement an UI Library **
- Add an UI library providing a
LuggageCardComponent(similar to theFlightCardComponentin theflight-app)
ng g lib ui-card --directory luggage --buildable
Remarks: The publishable (also buildable) switch makes sure the library can be compiled separately which is the key for incremental compilation.
-
Now let's create a linting error: Import this module into your
luggage-feature-checkin.module.ts:import { LuggageUiCardModule } from '@flight-workspace/luggage/ui-card'; [...] @NgModule({ imports: [ CommonModule, LuggageDomainModule, // Add this: LuggageUiCardModule ], declarations: [CheckinComponent], exports: [CheckinComponent] }) export class LuggageFeatureCheckinModule {} -
Please note that the linter tells you now, that this access isn't allowed. If you don't get the linting error in your IDE, start the linter on the command line (
ng lint luggage-feature-checkin). -
Make sure, this UI library can only be accessed by the feature libraries in your domain. For this, open your
project.jsonand assign the following tags to the lib:"luggage-ui-card": { [...] "tags": ["domain:luggage", "type:ui"] }, -
Restart your IDE, as global config files like
nx.jsonare only read once during the program start. -
Make sure, you don't get this linting error anymore. Optionally, you can also start the linter on the command line (
ng lint luggage-feature-checkin).
Bonus: Configure a Shared Utility Library **
-
In
project.jsonassign some additional tags to yourlogger-libmaking sure it's part of the shared kernel's utility layerHint: In addition to the preexisting tag
shared-- which is needed for the simplified domains -- assign the tagdomain:sharedas well as the tagtype:util. -
Restart your IDE.
-
Import the
LoggerModuleinto yourluggage-feature-checkin.module.ts. -
Make sure you don't get a linting error for importing the
LoggerModule.