-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,363 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Sample workflow for building and deploying a VitePress site to GitHub Pages | ||
# | ||
name: Deploy VitePress site to Pages | ||
|
||
on: | ||
push: | ||
paths: | ||
- 'docs/**' | ||
# branches: [ main ] | ||
|
||
# Allows you to run this workflow manually from the Actions tab | ||
workflow_dispatch: | ||
|
||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages | ||
permissions: | ||
contents: read | ||
pages: write | ||
id-token: write | ||
|
||
concurrency: | ||
group: pages | ||
cancel-in-progress: false | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4 | ||
with: | ||
fetch-depth: 0 | ||
- uses: pnpm/action-setup@v3 | ||
with: | ||
version: latest | ||
- name: Setup Node | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20 | ||
cache: pnpm | ||
- name: Setup Pages | ||
uses: actions/configure-pages@v4 | ||
- name: Install dependencies | ||
run: pnpm install | ||
- name: Build with VitePress | ||
run: npm run docs:build | ||
- name: Upload artifact | ||
uses: actions/upload-pages-artifact@v3 | ||
with: | ||
path: docs/.vitepress/dist | ||
|
||
deploy: | ||
environment: | ||
name: github-pages | ||
url: ${{ steps.deployment.outputs.page_url }} | ||
needs: build | ||
runs-on: ubuntu-latest | ||
name: Deploy | ||
steps: | ||
- name: Deploy to GitHub Pages | ||
id: deployment | ||
uses: actions/deploy-pages@v4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,11 @@ | |
|
||
name: Node.js CI | ||
|
||
on: push | ||
on: | ||
push: | ||
paths: | ||
- '!docs/**' | ||
|
||
|
||
jobs: | ||
build: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { defineConfig } from 'vitepress'; | ||
|
||
// https://vitepress.dev/reference/site-config | ||
export default defineConfig({ | ||
title: 'Ddd toolkit', | ||
description: 'Simple ddd stuffs', | ||
base: '/ddd-toolkit/', | ||
head: [['link', { rel: 'icon', href: 'favicon.ico' }]], | ||
themeConfig: { | ||
// https://vitepress.dev/reference/default-theme-config | ||
logo: '/logo.jpeg', | ||
nav: [ | ||
{ text: 'Home', link: '/' }, | ||
{ text: 'Examples', link: '/examples' }, | ||
], | ||
|
||
sidebar: [ | ||
{ | ||
text: 'Getting Started', | ||
link: '/getting-started', | ||
}, | ||
{ | ||
text: 'Components', | ||
items: [ | ||
{ text: 'Aggregate repo', link: '/aggregate-repo' }, | ||
], | ||
}, | ||
], | ||
|
||
socialLinks: [ | ||
{ icon: 'github', link: 'https://github.com/fizzbuds/ddd-toolkit' }, | ||
], | ||
|
||
footer: { | ||
copyright: 'Copyright © Gabriele Toselli, Luca Giovenzana and contributors.', | ||
}, | ||
|
||
lastUpdated: { | ||
text: 'Updated at', | ||
formatOptions: { | ||
dateStyle: 'full', | ||
timeStyle: 'medium', | ||
}, | ||
}, | ||
|
||
editLink: { | ||
pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path', | ||
text: 'Edit this page on GitHub', | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# Aggregate repo | ||
|
||
## Aggregate | ||
|
||
In Domain-Driven Design (DDD), an aggregate is a cluster of domain objects that can be treated as a single unit. It acts | ||
as a transactional boundary, ensuring consistency and integrity within the domain model. Aggregates encapsulate the | ||
invariant rules that govern the interactions among their constituent objects, allowing for a clear and manageable domain | ||
structure. By defining boundaries around related entities, aggregates promote better organization, scalability, and | ||
maintainability of the domain model, facilitating easier development and evolution of complex systems. | ||
|
||
### Aggregate | ||
|
||
An aggregate is a plan javascript class. Here is an example of a MembershipFeesAggregate: | ||
|
||
```typescript | ||
class ShoppingCartItemEntity { | ||
constructor(public readonly productId: string, public readonly quantity: number) { | ||
} | ||
} | ||
|
||
class ShoppingCartAggregate { | ||
constructor( | ||
public readonly shoppingCartId: string, | ||
public readonly maxItemsAllowed: number, | ||
public readonly items: ShoppingCartItemEntity[] = [], | ||
) { | ||
} | ||
|
||
static createSmallEmpty(shoppingCartId: string): ShoppingCartAggregate { | ||
return new ShoppingCartAggregate(shoppingCartId, 5); | ||
} | ||
|
||
addItem(item: ShoppingCartItemEntity): void { | ||
const totalQuantity = this.items.reduce((acc, currentItem) => acc + currentItem.quantity, 0); | ||
if (totalQuantity + item.quantity > this.maxItemsAllowed) { | ||
throw new Error(`Exceeded maximum items allowed in the shopping cart (${this.maxItemsAllowed})`); | ||
} | ||
this.items.push(item); | ||
} | ||
|
||
removeItem(productId: string): void { | ||
const index = this.items.findIndex((item) => item.productId === productId); | ||
if (index !== -1) this.items.splice(index, 1); | ||
} | ||
} | ||
``` | ||
|
||
A few observations | ||
|
||
- Use ubiquitous language | ||
- Use static methods to create the aggregate | ||
- Use different classes for entity or value object. | ||
|
||
### Serializer | ||
|
||
The aggregate is saved on a database. The serializer converts the aggregate to its db representation. | ||
|
||
```typescript | ||
import { ISerializer } from '@fizzbuds/ddd-toolkit'; | ||
|
||
// ShoppingCartAggregateModel can be a mongoose schema or a dynamodb table | ||
type ShoppingCartAggregateModel = { | ||
shoppingCartId: string; | ||
maxItemsAllowed: number; | ||
items: { productId: string; quantity: number }[]; | ||
}; | ||
|
||
export class ShoppingCartAggregateSerializer implements ISerializer<ShoppingCartAggregate, ShoppingCartAggregateModel> { | ||
modelToAggregate(model: ShoppingCartAggregateModel): ShoppingCartAggregate { | ||
return new ShoppingCartAggregate( | ||
model.shoppingCartId, | ||
model.maxItemsAllowed, | ||
model.items.map(({ productId, quantity }) => new ShoppingCartItemEntity(productId, quantity)), | ||
); | ||
} | ||
|
||
aggregateToModel(aggregate: ShoppingCartAggregate): ShoppingCartAggregateModel { | ||
return { | ||
shoppingCartId: aggregate.shoppingCartId, | ||
maxItemsAllowed: aggregate.maxItemsAllowed, | ||
items: aggregate.items.map(({ productId, quantity }) => ({ productId, quantity })), | ||
}; | ||
} | ||
} | ||
|
||
``` | ||
|
||
### Mongo aggregate repo | ||
|
||
```typescript | ||
import { MongoAggregateRepo } from '@fizzbuds/ddd-toolkit'; | ||
|
||
export class ShoppingCartAggregateRepo extends MongoAggregateRepo< | ||
ShoppingCartAggregate, | ||
ShoppingCartAggregateModel | ||
> { | ||
private static logger = new Logger(ShoppingCartAggregateRepo.name); | ||
|
||
constructor(mongoClient: MongoClient) { | ||
super( | ||
new ShoppingCartAggregateSerializer(), | ||
mongoClient, | ||
'shopping_carts', | ||
undefined, | ||
MemberRegistrationAggregateRepo.logger, | ||
); | ||
} | ||
} | ||
``` | ||
|
||
### Usage | ||
|
||
```typescript | ||
const mongoClient = await (new MongoClient('mongodb://localhost:27017')).connect() | ||
const shoppingCartAggregateRepo = new ShoppingCartAggregateRepo(mongoClient); | ||
|
||
const shoppingCart = ShoppingCartAggregate.createSmallEmpty('foo-cart-id'); | ||
await shoppingCartAggregateRepo.save(shoppingCart); | ||
|
||
const retrievedShoppingCart = await shoppingCartAggregateRepo.getById('foo-cart-id'); | ||
retrievedShoppingCart.addItem(new ShoppingCartItemEntity('product-1', 1)); | ||
await shoppingCartAggregateRepo.save(retrievedShoppingCart); | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Examples | ||
|
||
Here are some examples of how to use the library. | ||
|
||
- [Membership fees with NestJs](https://github.com/fizzbuds/nest-ddd-toolkit) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Getting started | ||
|
||
## Domain driven design | ||
|
||
Domain-Driven Design (DDD) improves software by emphasizing understanding the problem domain and modeling it directly | ||
within the software. This approach enhances communication, promotes modular design, and encourages iterative | ||
development, leading to higher quality, more maintainable, and adaptable software systems. | ||
|
||
## Motivation | ||
|
||
For the inexperienced, building an application with DDD principles is not easy. The Web offers us many resources, and | ||
navigating through them is not so simple. | ||
We ran into complex implementations that created many problems for us and, above all, were not necessary for our | ||
situation. | ||
|
||
We decided to collect in this library the components of the tactical DDD for which we experienced a better cost-benefit | ||
ratio. | ||
|
||
We are well aware that the perfect solution does not exist, but these components provide an excellent starting point for | ||
building an application that fully respects the principles of tactical DDD. | ||
|
||
## Installation | ||
|
||
::: warning | ||
The idea is that the core package contains only the basic interfaces and implementations. | ||
|
||
Additional packages will allow to install different implementations (repo with postgres, bus with rabbit etc etc) | ||
|
||
At the current state however it contains the implementation for **mongodb**. | ||
::: | ||
|
||
```bash | ||
pnpm install @fizzbuds/ddd-toolkit mongodb@5 | ||
|
||
``` | ||
|
||
At this time, the ddd-toolkit package offers the following features out of the box: | ||
|
||
- **Aggregate repo** with _serialization_, _optimistic lock_, and optionally _outbox pattern_ | ||
- **Command bus** with in-memory implementation | ||
- **Event bus** with in-memory implementation | ||
- **Query bus** with in-memory implementation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
--- | ||
# https://vitepress.dev/reference/default-theme-home-page | ||
layout: home | ||
|
||
hero: | ||
name: "ddd-toolkit" | ||
text: "Pragmatic approach to tactical DDD patterns" | ||
image: | ||
src: /logo.jpeg | ||
alt: logo | ||
tagline: Well begun is half done 💎 | ||
actions: | ||
- theme: brand | ||
text: Getting Started | ||
link: /getting-started | ||
# - theme: alt | ||
# text: API Docs | ||
# link: /api-examples | ||
|
||
features: | ||
- title: 🧱 Aggregate | ||
details: Ready to use aggregate repo with optimistic lock. | ||
- title: 📥📤 Commands and Queries | ||
details: Ready to use buses to standardize commands and queries. | ||
- title: 🎉 Events | ||
details: As the starting point of event storming | ||
- title: 🪝 Repo hooks | ||
details: The easiest way to separate reading from writing models. | ||
--- | ||
|
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.