├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── extensions.json ├── README.md ├── jest.config.js ├── jest.preset.js ├── nx.json ├── package-lock.json ├── package.json ├── packages ├── .gitkeep └── ddd │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── assets │ ├── app-graph.png │ ├── folder-structure.png │ ├── graph.png │ └── restrictions-error.png │ ├── generators.json │ ├── jest.config.js │ ├── package.json │ ├── project.json │ ├── src │ ├── index.ts │ └── schematics │ │ ├── add │ │ ├── index.ts │ │ ├── init-linting-rules.ts │ │ └── schema.json │ │ ├── domain │ │ ├── add-domain-to-linting-rules.ts │ │ ├── application │ │ │ ├── create-files.ts │ │ │ ├── delete-files.ts │ │ │ ├── files │ │ │ │ └── __fileName__-application.module.ts__tmpl__ │ │ │ └── index.ts │ │ ├── domain │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── infrastructure │ │ │ ├── create-files.ts │ │ │ ├── delete-files.ts │ │ │ ├── files │ │ │ │ ├── index.ts │ │ │ │ └── lib │ │ │ │ │ └── __fileName__.module.ts__tmpl__ │ │ │ └── index.ts │ │ ├── normalize-schema.ts │ │ ├── normalized-options.ts │ │ ├── schema.json │ │ └── schema.ts │ │ ├── json-schema-to-ts.js │ │ └── utils │ │ ├── check-rule-exists.ts │ │ └── update-dep-const.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.base.json └── workspace.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nrwl/nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nrwl/nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # IDE 42 | .lh -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @nestjs-architects/ddd -- DDD Plugin for Nx 2 | 3 | see [packages/ddd/readme.md](packages/ddd/README.md) 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nrwl/jest'); 2 | 3 | module.exports = { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset'); 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@nrwl/workspace/presets/npm.json", 3 | "npmScope": "nx-ddd-plugin", 4 | "affected": { 5 | "defaultBase": "main" 6 | }, 7 | "cli": { 8 | "defaultCollection": "@nrwl/workspace" 9 | }, 10 | "tasksRunnerOptions": { 11 | "default": { 12 | "runner": "@nrwl/workspace/tasks-runners/default", 13 | "options": { 14 | "cacheableOperations": [ 15 | "build", 16 | "lint", 17 | "test", 18 | "e2e" 19 | ] 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nx-ddd-plugin", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "nx": "nx", 7 | "build": "nx build" 8 | }, 9 | "private": true, 10 | "dependencies": { 11 | "tslib": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "@angular-devkit/schematics": "^12.2.11", 15 | "@nrwl/cli": "13.0.1", 16 | "@nrwl/eslint-plugin-nx": "13.0.1", 17 | "@nrwl/jest": "13.0.1", 18 | "@nrwl/linter": "13.0.1", 19 | "@nrwl/nest": "^13.0.2", 20 | "@nrwl/node": "^13.0.1", 21 | "@nrwl/tao": "13.0.1", 22 | "@nrwl/workspace": "13.0.1", 23 | "@types/jest": "27.0.2", 24 | "@types/node": "14.14.33", 25 | "@typescript-eslint/eslint-plugin": "~4.33.0", 26 | "@typescript-eslint/parser": "~4.33.0", 27 | "eslint": "7.32.0", 28 | "eslint-config-prettier": "8.1.0", 29 | "jest": "27.2.3", 30 | "json-schema-to-typescript": "^10.1.5", 31 | "prettier": "^2.3.1", 32 | "ts-jest": "27.0.5", 33 | "typescript": "~4.3.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-architects/nx-ddd-plugin/2acc9ade5ed2db106dfe3eb0f74caf2a931b82e2/packages/.gitkeep -------------------------------------------------------------------------------- /packages/ddd/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/ddd/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/ddd/README.md: -------------------------------------------------------------------------------- 1 | # @nestjs-architects/nx-ddd-plugin -- DDD Plugin for Nx 2 | 3 | This plugin installs schematics which automate slicing your Nx workspace into domains and layers according to DDD nad 4 | Onion Architecture. 5 | 6 | ![domains and layers](https://github.com/nestjs-architects/nx-ddd-plugin/blob/main/packages/ddd/assets/graph.png?raw=true) 7 | 8 | The generated access restrictions prevent unwanted access between libraries respecting layers and domains: 9 | 10 | ![access restrictions](https://github.com/nestjs-architects/nx-ddd-plugin/blob/main/packages/ddd/assets/restrictions-error.png?raw=true) 11 | 12 | ## Features 13 | 14 | - 🗺️ Generating domains with domain libraries 15 | - 🙅‍♂️ Adding linting rules for access restrictions between domains 16 | - 🙅‍♀️ Adding linting rules for access restrictions between layers as proposed by Onion Architecture 17 | 18 | ## Usage 19 | 20 | Add this plugin to an Nx workspace: 21 | 22 | ``` 23 | npm i -D @nestjs-architects/nx-ddd-plugin 24 | ``` 25 | 26 | Initialize linting rules 27 | 28 | ``` 29 | nx g @nestjs-architects/nx-ddd-plugin:add 30 | ``` 31 | 32 | Add domains: 33 | 34 | ``` 35 | nx g @nestjs-architects/nx-ddd-plugin:domain adoption 36 | ``` 37 | 38 | If you have a fullstack workspace you can separate those libraries from that for frontend by providing a grouping 39 | directory 40 | 41 | ``` 42 | nx g @nestjs-architects/nx-ddd-plugin:domain adoption --directory server 43 | ``` 44 | 45 | ## Generated Structure 46 | 47 | The included schematics generate a folder for each domain. This folder contains all necessary libs to provide your 48 | domain use cases logic. 49 | 50 | ![Folders structure](https://github.com/nestjs-architects/nx-ddd-plugin/blob/main/packages/ddd/assets/folder-structure.png?raw=true) 51 | 52 | ## How to connect it with the App? 53 | 54 | App should import an ui library as it is a way how the application provides the entrypoint to the business logic. This 55 | plugin doesn't generate ui library generator as it doesn't have to be a part of any domain. Ui library can implement a 56 | simple REST API for a specific domain, but also could represent a Backend for Frontend that orchestrate multiple domains 57 | or could produce a classic html views containing data from many domains. 58 | 59 | Furthermore, there are many options of the communication protocol you could want to use. 60 | 61 | ![Application structure](https://github.com/nestjs-architects/nx-ddd-plugin/blob/main/packages/ddd/assets/app-graph.png?raw=true) 62 | 63 | ## Contact 64 | 65 | Maciej Sikorski - [@_MaciejSikorski](https://twitter.com/_MaciejSikorski) 66 | , [LinkedIn](https://www.linkedin.com/in/maciej-sikorski-a01b26149/) 67 | 68 | ## More 69 | 70 | - [Follow us on Twitter](https://twitter.com/NestJSArchitect) 71 | 72 | ## Acknowledgments 73 | 74 | * [Kamil Gajowy](https://github.com/kgajowy) 75 | * [Dominik Ostrowski](https://github.com/Dyostiq) 76 | * [House of Angular](https://houseofangular.io) 77 | 78 | ## Credits 79 | 80 | - [Onion Architecture](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/) 81 | - [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://www.amazon.pl/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/ref=asc_df_0321125215/?tag=plshogostdde-21&linkCode=df0&hvadid=504239934199&hvpos=&hvnetw=g&hvrand=13686115728153990296&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9061063&hvtargid=pla-449269547899&psc=1) 82 | -------------------------------------------------------------------------------- /packages/ddd/assets/app-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-architects/nx-ddd-plugin/2acc9ade5ed2db106dfe3eb0f74caf2a931b82e2/packages/ddd/assets/app-graph.png -------------------------------------------------------------------------------- /packages/ddd/assets/folder-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-architects/nx-ddd-plugin/2acc9ade5ed2db106dfe3eb0f74caf2a931b82e2/packages/ddd/assets/folder-structure.png -------------------------------------------------------------------------------- /packages/ddd/assets/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-architects/nx-ddd-plugin/2acc9ade5ed2db106dfe3eb0f74caf2a931b82e2/packages/ddd/assets/graph.png -------------------------------------------------------------------------------- /packages/ddd/assets/restrictions-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-architects/nx-ddd-plugin/2acc9ade5ed2db106dfe3eb0f74caf2a931b82e2/packages/ddd/assets/restrictions-error.png -------------------------------------------------------------------------------- /packages/ddd/generators.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json", 3 | "name": "@angular-architects/ddd", 4 | "version": "0.0.1", 5 | "schematics": { 6 | "add": { 7 | "factory": "./src/schematics/add/index", 8 | "description": "adds initial access restrictions to linting rules", 9 | "schema": "./src/schematics/add/schema.json" 10 | }, 11 | "domain": { 12 | "factory": "./src/schematics/domain/index", 13 | "schema": "./src/schematics/domain/schema.json", 14 | "description": "adds a domain including a domain, application and infrastructure libs, entries in nx.json, and linting rules in eslint.json" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/ddd/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'ddd', 3 | preset: '../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | testEnvironment: 'node', 10 | transform: { 11 | '^.+\\.[tj]sx?$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 14 | coverageDirectory: '../../coverage/packages/ddd', 15 | }; 16 | -------------------------------------------------------------------------------- /packages/ddd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.1", 3 | "license": "MIT", 4 | "author": "Maciej Sikorski", 5 | "description": "Nx plugin for structuring a monorepo with domains and layers", 6 | "name": "@nestjs-architects/nx-ddd-plugin", 7 | "repository": { 8 | "type": "github", 9 | "url": "https://github.com/nestjs-architects/nx-ddd-plugin" 10 | }, 11 | "schematics": "./generators.json" 12 | } 13 | -------------------------------------------------------------------------------- /packages/ddd/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "packages/ddd", 3 | "sourceRoot": "packages/ddd/src", 4 | "projectType": "library", 5 | "targets": { 6 | "lint": { 7 | "executor": "@nrwl/linter:eslint", 8 | "outputs": ["{options.outputFile}"], 9 | "options": { 10 | "lintFilePatterns": ["packages/ddd/**/*.ts"] 11 | } 12 | }, 13 | "test": { 14 | "executor": "@nrwl/jest:jest", 15 | "outputs": ["coverage/packages/ddd"], 16 | "options": { 17 | "jestConfig": "packages/ddd/jest.config.js", 18 | "passWithNoTests": true 19 | } 20 | }, 21 | "build": { 22 | "executor": "@nrwl/node:package", 23 | "outputs": ["{options.outputPath}"], 24 | "options": { 25 | "outputPath": "dist/packages/ddd", 26 | "tsConfig": "packages/ddd/tsconfig.lib.json", 27 | "packageJson": "packages/ddd/package.json", 28 | "main": "packages/ddd/src/index.ts", 29 | "assets": [ 30 | "packages/ddd/*.md", 31 | { 32 | "glob": "src/schematics/domain/**/files/**/*", 33 | "input": "./packages/ddd", 34 | "output": "." 35 | }, 36 | "packages/ddd/generators.json" 37 | ] 38 | } 39 | } 40 | }, 41 | "tags": [] 42 | } 43 | -------------------------------------------------------------------------------- /packages/ddd/src/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-architects/nx-ddd-plugin/2acc9ade5ed2db106dfe3eb0f74caf2a931b82e2/packages/ddd/src/index.ts -------------------------------------------------------------------------------- /packages/ddd/src/schematics/add/index.ts: -------------------------------------------------------------------------------- 1 | import { initLintingRules } from './init-linting-rules'; 2 | import { convertNxGenerator, Tree } from '@nrwl/devkit'; 3 | 4 | export function addGenerator(tree: Tree): void { 5 | initLintingRules(tree); 6 | } 7 | 8 | const addSchematic = convertNxGenerator(addGenerator); 9 | 10 | export default addSchematic; 11 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/add/init-linting-rules.ts: -------------------------------------------------------------------------------- 1 | import { updateDepConst } from '../utils/update-dep-const'; 2 | import { Tree } from '@nrwl/devkit'; 3 | 4 | /** 5 | * initLintingRules 6 | * initialize the linting rules to enforce dependency constraints inside tslint.jsonk 7 | */ 8 | 9 | export function initLintingRules(tree: Tree): void { 10 | updateDepConst(tree, (depConst) => { 11 | const jokerIndex = depConst.findIndex( 12 | (entry) => 13 | entry['sourceTag'] && 14 | entry['sourceTag'] === '*' && 15 | entry['onlyDependOnLibsWithTags'] && 16 | Array.isArray(entry['onlyDependOnLibsWithTags']) && 17 | entry['onlyDependOnLibsWithTags'].length > 0 && 18 | entry['onlyDependOnLibsWithTags'][0] === '*' 19 | ); 20 | 21 | if (jokerIndex !== -1) { 22 | depConst.splice(jokerIndex, 1); 23 | } 24 | 25 | depConst.push({ 26 | sourceTag: 'type:app', 27 | onlyDependOnLibsWithTags: ['type:ui'], 28 | }); 29 | 30 | depConst.push({ 31 | sourceTag: 'type:ui', 32 | onlyDependOnLibsWithTags: [ 33 | 'type:infrastructure', 34 | 'type:domain', 35 | 'type:application', 36 | ], 37 | }); 38 | 39 | depConst.push({ 40 | sourceTag: 'type:application', 41 | onlyDependOnLibsWithTags: [ 42 | 'type:application', 43 | 'type:domain', 44 | 'type:util', 45 | ], 46 | }); 47 | 48 | depConst.push({ 49 | sourceTag: 'type:domain', 50 | onlyDependOnLibsWithTags: ['type:domain', 'type:util'], 51 | }); 52 | 53 | depConst.push({ 54 | sourceTag: 'type:infrastructure', 55 | onlyDependOnLibsWithTags: [ 56 | 'type:application', 57 | 'type:domain', 58 | 'type:infrastructure', 59 | 'type:util', 60 | ], 61 | }); 62 | 63 | depConst.push({ 64 | sourceTag: 'type:util', 65 | onlyDependOnLibsWithTags: ['type:util'], 66 | }); 67 | 68 | depConst.push({ 69 | sourceTag: 'domain:shared', 70 | onlyDependOnLibsWithTags: ['domain:shared'], 71 | }); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/add/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "domain-options", 4 | "type": "object" 5 | } 6 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/add-domain-to-linting-rules.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * addDomainToLintingRules 3 | * @param domainName name of the domain that is being included in the tslint.json 4 | */ 5 | import { updateDepConst } from '../utils/update-dep-const'; 6 | import { Tree } from '@nrwl/devkit'; 7 | 8 | export function addDomainToLintingRules(tree: Tree, domainName: string): void { 9 | updateDepConst(tree, (depConst) => { 10 | depConst.push({ 11 | sourceTag: `domain:${domainName}`, 12 | onlyDependOnLibsWithTags: [`domain:${domainName}`, 'domain:shared'], 13 | }); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/application/create-files.ts: -------------------------------------------------------------------------------- 1 | import type { Tree } from '@nrwl/devkit'; 2 | import { 3 | generateFiles, 4 | joinPathFragments, 5 | names, 6 | offsetFromRoot, 7 | } from '@nrwl/devkit'; 8 | import type { NormalizedOptions } from '../normalized-options'; 9 | 10 | export function createFiles(tree: Tree, options: NormalizedOptions): void { 11 | const substitutions = { 12 | ...options, 13 | ...names(options.projectName), 14 | tmpl: '', 15 | offsetFromRoot: offsetFromRoot(options.domainRoot), 16 | className: options.className, 17 | cqrs: options.cqrs 18 | }; 19 | generateFiles( 20 | tree, 21 | joinPathFragments(__dirname, 'files'), 22 | joinPathFragments(options.domainRoot, 'application', 'src', 'lib'), 23 | substitutions 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/application/delete-files.ts: -------------------------------------------------------------------------------- 1 | import type { Tree } from '@nrwl/devkit'; 2 | import { joinPathFragments } from '@nrwl/devkit'; 3 | import type { NormalizedOptions } from '../normalized-options'; 4 | 5 | export function deleteFiles(tree: Tree, options: NormalizedOptions): void { 6 | tree.delete( 7 | joinPathFragments( 8 | options.domainRoot, 9 | 'application', 10 | 'src', 11 | 'lib', 12 | `${options.fileName}-application.module.ts` 13 | ) 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/application/files/__fileName__-application.module.ts__tmpl__: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, ModuleMetadata } from '@nestjs/common'; 2 | <% if(cqrs) { %>import { CqrsModule } from '@nestjs/cqrs';<% } %> 3 | import { <%= className %>ApplicationService } from './<%= fileName %>-application.service'; 4 | 5 | @Module({ 6 | <% if(cqrs) { %>imports: [CqrsModule],<% } %> 7 | providers: [<%= className %>ApplicationService], 8 | exports: [<%= className %>ApplicationService], 9 | }) 10 | export class <%= className %>ApplicationModule { 11 | static withInfrastructure( 12 | infrastructure: ModuleMetadata['imports'] 13 | ): DynamicModule { 14 | infrastructure = infrastructure ?? []; 15 | return { 16 | module: <%= className %>ApplicationModule, 17 | imports: [...infrastructure], 18 | providers: [], 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/application/index.ts: -------------------------------------------------------------------------------- 1 | import { chain, externalSchematic, Rule } from '@angular-devkit/schematics'; 2 | import { NormalizedOptions } from '../normalized-options'; 3 | import { deleteFiles } from './delete-files'; 4 | import { Tree } from '@nrwl/devkit'; 5 | import { libraryGenerator } from '@nrwl/nest'; 6 | import { createFiles } from './create-files'; 7 | 8 | export async function application( 9 | tree: Tree, 10 | options: NormalizedOptions 11 | ): Promise { 12 | await libraryGenerator(tree, { 13 | name: 'application', 14 | directory: options.domainNameAndDirectory, 15 | tags: `domain:${options.domainName},type:application`, 16 | publishable: options.publishable, 17 | buildable: options.buildable, 18 | importPath: options.importPath, 19 | service: true, 20 | strict: true, 21 | }); 22 | deleteFiles(tree, options); 23 | createFiles(tree, options); 24 | } 25 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/domain/index.ts: -------------------------------------------------------------------------------- 1 | import { NormalizedOptions } from '../normalized-options'; 2 | import { Tree } from '@nrwl/devkit'; 3 | import { libraryGenerator } from '@nrwl/nest'; 4 | 5 | export async function domain( 6 | tree: Tree, 7 | options: NormalizedOptions 8 | ): Promise { 9 | await libraryGenerator(tree, { 10 | name: 'domain', 11 | directory: options.domainNameAndDirectory, 12 | tags: `domain:${options.domainName},type:domain`, 13 | publishable: options.publishable, 14 | buildable: options.buildable, 15 | importPath: options.importPath, 16 | strict: true, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/index.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from './schema'; 2 | import { convertNxGenerator, Tree } from '@nrwl/devkit'; 3 | import { normalizeSchema } from './normalize-schema'; 4 | import { application } from './application'; 5 | import { domain } from './domain'; 6 | import { infrastructure } from './infrastructure'; 7 | import { addDomainToLintingRules } from './add-domain-to-linting-rules'; 8 | import { formatFiles } from '@nrwl/workspace'; 9 | 10 | export async function domainGenerator( 11 | tree: Tree, 12 | schema: Schema 13 | ): Promise { 14 | const normalizedOptions = normalizeSchema(tree, schema); 15 | 16 | addDomainToLintingRules(tree, normalizedOptions.domainName); 17 | 18 | await application(tree, normalizedOptions); 19 | await domain(tree, normalizedOptions); 20 | await infrastructure(tree, normalizedOptions); 21 | 22 | await formatFiles(); 23 | } 24 | 25 | const domainSchematic = convertNxGenerator(domainGenerator); 26 | 27 | export default domainSchematic; 28 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/infrastructure/create-files.ts: -------------------------------------------------------------------------------- 1 | import type { Tree } from '@nrwl/devkit'; 2 | import { 3 | generateFiles, 4 | joinPathFragments, 5 | names, 6 | offsetFromRoot, 7 | } from '@nrwl/devkit'; 8 | import type { NormalizedOptions } from '../normalized-options'; 9 | 10 | export function createFiles(tree: Tree, options: NormalizedOptions): void { 11 | const substitutions = { 12 | ...options, 13 | ...names(options.projectName), 14 | tmpl: '', 15 | offsetFromRoot: offsetFromRoot(options.domainRoot), 16 | className: options.className, 17 | domainPath: `@${options.workspacePrefix}/${options.domainNameAndDirectory}`, 18 | }; 19 | generateFiles( 20 | tree, 21 | joinPathFragments(__dirname, 'files'), 22 | joinPathFragments(options.domainRoot, 'infrastructure', 'src'), 23 | substitutions 24 | ); 25 | generateFiles( 26 | tree, 27 | joinPathFragments(__dirname, 'files', 'lib'), 28 | joinPathFragments(options.domainRoot, 'infrastructure', 'src', 'lib'), 29 | substitutions 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/infrastructure/delete-files.ts: -------------------------------------------------------------------------------- 1 | import type { Tree } from '@nrwl/devkit'; 2 | import { joinPathFragments } from '@nrwl/devkit'; 3 | import type { NormalizedOptions } from '../normalized-options'; 4 | 5 | export function deleteFiles(tree: Tree, options: NormalizedOptions): void { 6 | tree.delete( 7 | joinPathFragments( 8 | options.domainRoot, 9 | 'infrastructure', 10 | 'src', 11 | `index.ts` 12 | ) 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/infrastructure/files/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/<%= fileName %>.module' -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/infrastructure/files/lib/__fileName__.module.ts__tmpl__: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { <%= className %>ApplicationModule } from '<%= domainPath %>/application'; 3 | import { <%= className %>InfrastructureModule } from './<%= fileName %>-infrastructure.module'; 4 | 5 | @Module({ 6 | imports: [<%= className %>ApplicationModule.withInfrastructure([<%= className %>InfrastructureModule])], 7 | exports: [<%= className %>ApplicationModule] 8 | }) 9 | export class <%= className %>Module {} 10 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/infrastructure/index.ts: -------------------------------------------------------------------------------- 1 | import { NormalizedOptions } from '../normalized-options'; 2 | import { Tree } from '@nrwl/devkit'; 3 | import { libraryGenerator } from '@nrwl/nest'; 4 | import { createFiles } from './create-files'; 5 | import { deleteFiles } from './delete-files'; 6 | 7 | export async function infrastructure( 8 | tree: Tree, 9 | options: NormalizedOptions 10 | ): Promise { 11 | await libraryGenerator(tree, { 12 | name: 'infrastructure', 13 | directory: options.domainNameAndDirectory, 14 | tags: `domain:${options.domainName},type:infrastructure`, 15 | publishable: options.publishable, 16 | buildable: options.buildable, 17 | importPath: options.importPath, 18 | strict: true, 19 | }); 20 | deleteFiles(tree, options); 21 | createFiles(tree, options); 22 | } 23 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/normalize-schema.ts: -------------------------------------------------------------------------------- 1 | import { NormalizedOptions } from './normalized-options'; 2 | import { Schema } from './schema'; 3 | import { strings } from '@angular-devkit/core'; 4 | import { getWorkspaceLayout, Tree } from '@nrwl/devkit'; 5 | 6 | export function normalizeSchema(tree: Tree, schema: Schema): NormalizedOptions { 7 | const domainName = strings.dasherize(schema.name); 8 | const domainNameAndDirectory = schema.directory 9 | ? `${schema.directory}/${domainName}` 10 | : domainName; 11 | const domainNameAndDirectoryDasherized = strings 12 | .dasherize(domainNameAndDirectory) 13 | .split('/') 14 | .join('-'); 15 | const libFolderPath = `libs/${domainNameAndDirectory}`; 16 | const libLibFolder = `${libFolderPath}/domain/src/lib`; 17 | const { npmScope } = getWorkspaceLayout(tree); 18 | 19 | return { 20 | projectName: domainNameAndDirectoryDasherized, 21 | domainNameAndDirectory: domainNameAndDirectory, 22 | domainName, 23 | publishable: schema.type === 'publishable', 24 | buildable: schema.type === 'buildable', 25 | importPath: schema.importPath, 26 | fileName: domainNameAndDirectoryDasherized, 27 | domainRoot: libFolderPath, 28 | className: strings.classify(domainNameAndDirectoryDasherized), 29 | workspacePrefix: npmScope, 30 | cqrs: schema.cqrs 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/normalized-options.ts: -------------------------------------------------------------------------------- 1 | export interface NormalizedOptions { 2 | workspacePrefix: string; 3 | className: string; 4 | projectName: string; 5 | domainNameAndDirectory: string; 6 | domainName: string; 7 | publishable: boolean; 8 | buildable: boolean; 9 | importPath: string; 10 | fileName: string; 11 | domainRoot: string; 12 | cqrs: boolean 13 | } 14 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "domain-options", 4 | "type": "object", 5 | "properties": { 6 | "name": { 7 | "type": "string", 8 | "description": "Grouping name for the Domain", 9 | "x-prompt": "What is the name of the domain?", 10 | "$default": { 11 | "$source": "argv", 12 | "index": 0 13 | } 14 | }, 15 | "directory": { 16 | "type": "string", 17 | "description": "Subpath of the domain within libs directory" 18 | }, 19 | "cqrs": { 20 | "type": "boolean", 21 | "description": "Use CQRS in Application Module", 22 | "default": false 23 | }, 24 | "type": { 25 | "type": "string", 26 | "enum": ["internal", "buildable", "publishable"], 27 | "description": "A type to determine if and how to build libraries.", 28 | "default": "buildable" 29 | }, 30 | "importPath": { 31 | "type": "string", 32 | "description": "For publishable libs: Official package name used in import statements" 33 | } 34 | }, 35 | "required": ["name"] 36 | } 37 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/domain/schema.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * This file was automatically generated by json-schema-to-typescript. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 5 | * and run json-schema-to-typescript to regenerate this file. 6 | */ 7 | 8 | export interface Schema { 9 | /** 10 | * Grouping name for the Domain 11 | */ 12 | name: string; 13 | /** 14 | * Subpath of the domain within libs directory 15 | */ 16 | directory?: string; 17 | /** 18 | * Use CQRS in Application Module 19 | */ 20 | cqrs?: boolean; 21 | /** 22 | * A type to determine if and how to build libraries. 23 | */ 24 | type?: "internal" | "buildable" | "publishable"; 25 | /** 26 | * For publishable libs: Official package name used in import statements 27 | */ 28 | importPath?: string; 29 | [k: string]: unknown; 30 | } 31 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/json-schema-to-ts.js: -------------------------------------------------------------------------------- 1 | const toTypeScript = require('json-schema-to-typescript'); 2 | const fs = require('fs'); 3 | 4 | toTypeScript 5 | .compileFromFile('packages/ddd/src/schematics/domain/schema.json') 6 | .then((ts) => 7 | fs.writeFileSync('packages/ddd/src/schematics/domain/schema.ts', ts) 8 | ); 9 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/utils/check-rule-exists.ts: -------------------------------------------------------------------------------- 1 | import { SchematicContext } from '@angular-devkit/schematics'; 2 | 3 | export function checkRuleExists(filePath: string, rule: string, rules: object) { 4 | if (!rules['rules']) { 5 | throw new Error(`${filePath}: rules expected`); 6 | } 7 | 8 | if (!rules['rules'][rule]) { 9 | throw new Error(`${filePath}: ${rule} expected`); 10 | } 11 | 12 | if (rules['rules'][rule]['length'] < 2) { 13 | throw new Error(`${filePath}: ${rule}.1 unexpected`); 14 | } 15 | 16 | if (!rules['rules'][rule][1]['depConstraints']) { 17 | throw new Error(`${filePath}: ${rule}.1.depConstraints expected.`); 18 | } 19 | 20 | if (!Array.isArray(rules['rules'][rule][1]['depConstraints'])) { 21 | throw new Error( 22 | `${filePath}: ${rule}.1.depConstraints expected to be an array.` 23 | ); 24 | } 25 | 26 | return true; 27 | } 28 | -------------------------------------------------------------------------------- /packages/ddd/src/schematics/utils/update-dep-const.ts: -------------------------------------------------------------------------------- 1 | import { checkRuleExists } from './check-rule-exists'; 2 | import { Tree } from '@nrwl/devkit'; 3 | 4 | export function updateDepConst( 5 | tree: Tree, 6 | update: (depConst: Array) => void 7 | ) { 8 | let filePath = 'tslint.json'; 9 | let rule = 'nx-enforce-module-boundaries'; 10 | 11 | if (!tree.exists('tslint.json')) { 12 | if (tree.exists('.eslintrc.json')) { 13 | filePath = '.eslintrc.json'; 14 | rule = '@nrwl/nx/enforce-module-boundaries'; 15 | } else if (tree.exists('.eslintrc')) { 16 | filePath = '.eslintrc'; 17 | rule = '@nrwl/nx/enforce-module-boundaries'; 18 | } else { 19 | return; 20 | } 21 | } 22 | 23 | const text = tree.read(filePath).toString(); 24 | const json = JSON.parse(text); 25 | let rules = json; 26 | if (rules['overrides']) { 27 | const overrides = rules['overrides']; 28 | rules = overrides.find( 29 | (e) => e.rules && e.rules['@nrwl/nx/enforce-module-boundaries'] 30 | ); 31 | } 32 | 33 | if (!checkRuleExists(filePath, rule, rules)) return; 34 | 35 | const depConst = rules['rules'][rule][1]['depConstraints'] as Array; 36 | update(depConst); 37 | 38 | const newText = JSON.stringify(json, undefined, 2); 39 | tree.write(filePath, newText); 40 | } 41 | -------------------------------------------------------------------------------- /packages/ddd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/ddd/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"], 8 | "resolveJsonModule": true 9 | }, 10 | "exclude": ["**/*.spec.ts", "**/files/*.ts"], 11 | "include": ["**/*.ts", "**/schema.json"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/ddd/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.spec.tsx", 11 | "**/*.spec.js", 12 | "**/*.spec.jsx", 13 | "**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nestjs-architects/nx-ddd-plugin/2acc9ade5ed2db106dfe3eb0f74caf2a931b82e2/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": false, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2020", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@nestjs-architects/nx-ddd-plugin": ["packages/ddd/src/index.ts"] 19 | } 20 | }, 21 | "exclude": ["node_modules", "tmp"] 22 | } 23 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": { 4 | "ddd": "packages/ddd" 5 | } 6 | } 7 | --------------------------------------------------------------------------------