Skip to content

Commit

Permalink
consolidation for version 1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
manfredsteyer committed Nov 16, 2020
2 parents 3b1d5b1 + 92810f3 commit 945ba61
Show file tree
Hide file tree
Showing 43 changed files with 3,295 additions and 364 deletions.
56 changes: 17 additions & 39 deletions libs/ddd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ The generated access restrictions prevent unwanted access between libraries resp
- 🗺️ Generating domains with domain libraries including a facades, models, and data services
- ⚙️ Generating feature libraries including a feature components using the facades
- 🙅‍♂️ Adding linting rules for access restrictions between domains as proposed by Nrwl
- 🙅‍♀️ Adding linting rules for access restrictions between layers as proposed by Nrwl
- 🙅‍♀️ Adding linting rules for access restrictions between layers as proposed by Nrwl (supports tslint and eslint)
- 🔥 Optionally generates skeleton for NGRX and integrates it into the DDD design (``--ngrx`` switch)

## Usage

Expand All @@ -26,49 +27,22 @@ ng add @angular-architects/ddd
Add domains and features manually:

```
ng g @angular-architects/ddd:domain booking
ng g @angular-architects/ddd:domain boarding
ng g @angular-architects/ddd:feature search --domain booking --app flight-app --entity flight
ng g @angular-architects/ddd:feature cancel --domain booking --app flight-app
ng g @angular-architects/ddd:feature manage --domain boarding --app flight-app
ng g @angular-architects/ddd:domain booking --addApp
ng g @angular-architects/ddd:domain boarding --addApp
ng g @angular-architects/ddd:feature search --domain booking --entity flight
ng g @angular-architects/ddd:feature cancel --domain booking
ng g @angular-architects/ddd:feature manage --domain boarding
```

Add domains and features interactively:
For NGRX support, just add the ``--ngrx`` switch:

```
ng g @angular-architects/ddd:domain
> ? What is the name of the domain? booking
> ? Would you like to add an associated application? (y/N) No
ng g @angular-architects/ddd:domain
> ? What is the name of the domain? boarding
> ? Would you like to add an associated application? (y/N) No
ng g @angular-architects/ddd:feature
> ? What is the name of the library? search
> ? What is the name of the associated domain? booking
> ? Would you like to add the "feature-" prefix? (Y/n) Yes
> ? Is this feature lazy loaded? (y/N) No
> [Optional] What is the associated application? (Leave blank if none) flight-app
> [Optional] What is the name of the entity to create for this feature? (Leave blank if none) flight
ng g @angular-architects/ddd:feature
> ? What is the name of the library? cancel
> ? What is the name of the associated domain? booking
> ? Would you like to add the "feature-" prefix? (Y/n) Yes
> ? Is this feature lazy loaded? (y/N) No
> [Optional] What is the associated application? (Leave blank if none) flight-app
> [Optional] What is the name of the entity to create for this feature? (Leave blank if none)
ng g @angular-architects/ddd:feature
> ? What is the name of the library? manage
> ? What is the name of the associated domain? boarding
> ? Would you like to add the "feature-" prefix? (Y/n) Yes
> ? Is this feature lazy loaded? (y/N) No
> [Optional] What is the associated application? (Leave blank if none) flight-app
> [Optional] What is the name of the entity to create for this feature? (Leave blank if none)
ng g @angular-architects/ddd:domain booking --addApp --ngrx
ng g @angular-architects/ddd:feature search --domain booking --entity flight --ngrx
[...]
```


This example assumes that you have an app ``flight-app`` in place.

These schematics also wire up the individual libs. To see the result, create a dependency graph:
Expand Down Expand Up @@ -121,5 +95,9 @@ see https://github.com/angular-architects/ddd-demo
- [Nrwl's eBook about monorepos and best practices](https://go.nrwl.io/angular-enterprise-monorepo-patterns-new-book)
- [Recording of session about this architecture](https://www.youtube.com/watch?v=94HFD391zkE&t=1s)
- [Article series about DDD with Angular](https://www.softwarearchitekt.at/aktuelles/sustainable-angular-architectures-1/)
- [Our eBook about this architecture](https://leanpub.com/enterprise-angular)
- [Our eBook on Angular and architectures](https://leanpub.com/enterprise-angular)
- [Thomas Burlison's article about facades in Angular](https://medium.com/@thomasburlesonIA/push-based-architectures-with-rxjs-81b327d7c32d)

## More
- [Angular Architecture Workshop](https://www.angulararchitects.io/en/angular-workshops/advanced-angular-enterprise-architecture-incl-ivy/)
- [Follow us on Twitter](https://twitter.com/ManfredSteyer)
2 changes: 1 addition & 1 deletion libs/ddd/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@angular-architects/ddd",
"version": "1.0.5",
"version": "1.1.0",
"license": "MIT",
"author": "Manfred Steyer",
"description": "Nx plugin for structuring a monorepo with domains and layers",
Expand Down
63 changes: 45 additions & 18 deletions libs/ddd/src/schematics/domain/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import {
chain,
externalSchematic,
Rule,
apply,
url,
template,
move,
import {
chain,
externalSchematic,
Rule,
apply,
url,
template,
move,
mergeWith,
noop,
} from '@angular-devkit/schematics';

import { strings } from '@angular-devkit/core';
import { DomainOptions } from './schema';
import { addDomainToLintingRules } from '../utils/update-linting-rules';
import {
addDomainToLintingRules,
addNgrxImportsToApp,
addNgRxToPackageJson,
} from '../rules';

export default function(options: DomainOptions): Rule {
export default function (options: DomainOptions): Rule {
const libFolder = strings.dasherize(options.name);

const templateSource = apply(url('./files'), [
template({}),
move(`libs/${libFolder}/domain/src/lib`)
move(`libs/${libFolder}/domain/src/lib`),
]);

const appFolderName = strings.dasherize(options.name);
const appPath = `apps/${appFolderName}/src/app`;
const appModulePath = `${appPath}/app.module.ts`;

if (options.ngrx && !options.addApp) {
throw new Error(
`The 'ngrx' option may only be used when the 'addApp' option is used.`
)
}

return chain([
externalSchematic('@nrwl/angular', 'lib', {
name: 'domain',
Expand All @@ -34,12 +48,25 @@ export default function(options: DomainOptions): Rule {
}),
addDomainToLintingRules(options.name),
mergeWith(templateSource),
(!options.addApp) ?
noop() :
externalSchematic('@nrwl/angular', 'app', {
name: options.name,
tags: `domain:${options.name},type:app`,
style: 'scss',
}),
!options.addApp
? noop()
: externalSchematic('@nrwl/angular', 'app', {
name: options.name,
tags: `domain:${options.name},type:app`,
style: 'scss',
}),
options.addApp && options.ngrx
? chain([
externalSchematic('@ngrx/schematics', 'store', {
project: options.name,
root: true,
minimal: true,
module: 'app.module.ts',
name: 'state',
}),
addNgrxImportsToApp(appModulePath),
addNgRxToPackageJson(),
])
: noop(),
]);
}
11 changes: 6 additions & 5 deletions libs/ddd/src/schematics/domain/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
"x-prompt": "Would you like to add an associated application?",
"default": false
},
"ngrx": {
"type": "boolean",
"default": false,
"description": "Add ngrx for the associated app (addApp required)"
},
"type": {
"type": "string",
"enum": [
"internal",
"buildable",
"publishable"
],
"enum": ["internal", "buildable", "publishable"],
"description": "A type to determine if and how to build the library.",
"default": "buildable"
}
Expand Down
4 changes: 4 additions & 0 deletions libs/ddd/src/schematics/domain/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface DomainOptions {
* Add an app for the domain?
*/
addApp?: boolean;
/**
* Add ngrx for the associated app (addApp required)
*/
ngrx?: boolean;
/**
* A type to determine if and how to build the library.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createAction, props } from '@ngrx/store';
import { <%= classify(entity) %> } from '../../entities/<%= dasherize(entity) %>';

export const load<%= classify(entity) %> = createAction(
'[<%= classify(entity) %>] Load <%= classify(entity) %>'
);

export const load<%= classify(entity) %>Success = createAction(
'[<%= classify(entity) %>] Load <%= classify(entity) %> Success',
props<{ <%= camelize(entity) %>: <%= classify(entity) %>[] }>()
);

export const load<%= classify(entity) %>Failure = createAction(
'[<%= classify(entity) %>] Load <%= classify(entity) %> Failure',
props<{ error: any }>()
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { createEffect, Actions, ofType } from '@ngrx/effects';
import { catchError, map, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
import * as <%= classify(entity) %>Actions from './<%= dasherize(entity) %>.actions';
import { <%= classify(entity) %>DataService } from '../../infrastructure/<%= dasherize(entity) %>.data.service';

@Injectable()
export class <%= classify(entity) %>Effects {
load<%= classify(entity) %>$ = createEffect(() =>
this.actions$.pipe(
ofType(<%= classify(entity) %>Actions.load<%= classify(entity) %>),
switchMap((action) =>
this.<%=camelize(entity)%>DataService.load().pipe(
map((<%= camelize(entity) %>) =>
<%= classify(entity) %>Actions.load<%= classify(entity) %>Success({ <%= camelize(entity) %> })
),
catchError((error) =>
of(<%= classify(entity) %>Actions.load<%= classify(entity) %>Failure({ error }))
)
)
)
)
);

constructor(
private actions$: Actions,
private <%=camelize(entity)%>DataService: <%= classify(entity) %>DataService
) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createReducer, on, Action } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

import * as <%= classify(entity) %>Actions from './<%= dasherize(entity) %>.actions';
import { <%= classify(entity) %> } from '../../entities/<%= dasherize(entity) %>';

export const <%= classify(entity).toUpperCase() %>_FEATURE_KEY = '<%= camelize(entity) %>';

export interface State extends EntityState<<%= classify(entity) %>> {
selectedId ?: string | number; // which <%= classify(entity) %> record has been selected
loaded : boolean; // has the <%= classify(entity) %> list been loaded
error ?: string | null; // last known error (if any)
}

export interface <%= classify(entity) %>PartialState {
readonly [<%= classify(entity).toUpperCase() %>_FEATURE_KEY]: State;
}

export const <%= camelize(entity) %>Adapter: EntityAdapter<<%= classify(entity) %>> = createEntityAdapter<<%= classify(entity) %>>();

export const initialState: State = <%= camelize(entity) %>Adapter.getInitialState({
// set initial required properties
loaded : false
});

const <%= camelize(entity) %>Reducer = createReducer(
initialState,
on(<%= classify(entity) %>Actions.load<%= classify(entity) %>,
state => ({ ...state, loaded: false, error: null })
),
on(<%= classify(entity) %>Actions.load<%= classify(entity) %>Success,
(state, { <%= camelize(entity) %> }) => <%= camelize(entity) %>Adapter.upsertMany(<%= camelize(entity) %>, { ...state, loaded: true })
),
on(<%= classify(entity) %>Actions.load<%= classify(entity) %>Failure,
(state, { error }) => ({ ...state, error })
),
);

export function reducer(state: State | undefined, action: Action) {
return <%= camelize(entity) %>Reducer(state, action);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { <%= classify(entity).toUpperCase() %>_FEATURE_KEY, State, <%= classify(entity) %>PartialState, <%= camelize(entity) %>Adapter } from './<%= dasherize(entity) %>.reducer';

// Lookup the '<%= classify(entity) %>' feature state managed by NgRx
export const get<%= classify(entity) %>State = createFeatureSelector<<%= classify(entity) %>PartialState, State>(<%= camelize(entity).toUpperCase() %>_FEATURE_KEY);

const { selectAll, selectEntities } = <%= camelize(entity) %>Adapter.getSelectors();

export const get<%= classify(entity) %>Loaded = createSelector(
get<%= classify(entity) %>State,
(state: State) => state.loaded
);

export const get<%= classify(entity) %>Error = createSelector(
get<%= classify(entity) %>State,
(state: State) => state.error
);

export const getAll<%= classify(entity) %> = createSelector(
get<%= classify(entity) %>State,
(state: State) => selectAll(state)
);

export const get<%= classify(entity) %>Entities = createSelector(
get<%= classify(entity) %>State,
(state: State) => selectEntities(state)
);

export const getSelectedId = createSelector(
get<%= classify(entity) %>State,
(state: State) => state.selectedId
);

export const getSelected = createSelector(
get<%= classify(entity) %>Entities,
getSelectedId,
(entities, selectedId) => selectedId && entities[selectedId]
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';

import { select, Store, Action } from '@ngrx/store';

import * as from<%= classify(entity) %> from '../+state/<%= dasherize(entity) %>/<%= dasherize(entity) %>.reducer';
import * as <%= classify(entity) %>Selectors from '../+state/<%= dasherize(entity) %>/<%= dasherize(entity) %>.selectors';

@Injectable({ providedIn: 'root' })
export class <%= classify(name) %>Facade {
loaded$ = this.store.pipe(select(<%= classify(entity) %>Selectors.get<%= classify(entity) %>Loaded));
<%= camelize(entity) %>List$ = this.store.pipe(select(<%= classify(entity) %>Selectors.getAll<%= classify(entity) %>));
selected<%= classify(entity) %>$ = this.store.pipe(select(<%= classify(entity) %>Selectors.getSelected));

constructor(private store: Store<from<%= classify(entity) %>.<%= classify(entity) %>PartialState>) { }

dispatch(action: Action) {
this.store.dispatch(action);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface <%=classify(entity)%> {
id: number;
name: string;
description: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import {Observable, of} from 'rxjs';
import {<%=classify(entity)%>} from '../entities/<%=dasherize(entity)%>';

@Injectable({ providedIn: 'root' })
export class <%=classify(entity)%>DataService {

constructor(private http: HttpClient) {
}

load(): Observable<<%=classify(entity)%>[]> {

// Uncomment if needed
/*
const url = '...';
const params = new HttpParams().set('param', 'value');
const headers = new HttpHeaders().set('Accept', 'application/json');
return this.http.get<<%=classify(entity)%>[]>(url, {params, headers});
*/

return of([
{id: 1, name: 'Lorem ipsum', description: 'Lorem ipsum dolor sit amet'},
{id: 2, name: 'At vero eos', description: 'At vero eos et accusam et justo duo dolores'},
{id: 3, name: 'Duis autem', description: 'Duis autem vel eum iriure dolor in hendrerit'},
]);
}
}
Loading

0 comments on commit 945ba61

Please sign in to comment.