Now with individual packages
This package provide utils file and interfaces to assistant build a complex application as domain driving design and nodeJS with typescript.
<img src="https://badgen.net/github/checks/4lessandrodev/types-ddd/main" alt="checks" style="max-width: 100%;"> <img src="https://badgen.net/github/stars/4lessandrodev/types-ddd" alt="stars" style="max-width: 100%;"> <img src="https://badgen.net/github/commits/4lessandrodev/types-ddd/main" alt="commits" style="max-width: 100%;"> <img src="https://badgen.net/github/last-commit/4lessandrodev/types-ddd/main" alt="last commit" style="max-width: 100%;"> <img src="https://badgen.net/github/license/4lessandrodev/types-ddd" alt="license" style="max-width: 100%;"> <img src="https://badgen.net/github/dependabot/4lessandrodev/types-ddd" alt="dependabot" style="max-width: 100%;"> <img src="https://badgen.net/github/tag/4lessandrodev/types-ddd" alt="tags" style="max-width: 100%;"> <img src="https://badgen.net/github/closed-issues/4lessandrodev/types-ddd" alt="issues" style="max-width: 100%;">
Install full available packages
$ npm i @type-ddd/core
# or
$ yarn add @type-ddd/core
Alternatively you can install individual packages
@type-ddd/cpf
Docs@type-ddd/cnpj
Docs@type-ddd/date
Docs@type-ddd/email
Docs@type-ddd/password
Docs@type-ddd/patterns
Docs@type-ddd/phone
Docs@type-ddd/username
Docs@type-ddd/zip-code
Docs@type-ddd/money
DocsGetHashCode()
defined by class name + id)GetHashCode()
defined by property values)FindByName()
etc.)Install individual package
@type-ddd/cpf
Docs@type-ddd/cnpj
Docs@type-ddd/date
Docs@type-ddd/email
Docs@type-ddd/password
Docs@type-ddd/patterns
Docs@type-ddd/phone
Docs@type-ddd/username
Docs@type-ddd/zip-code
Docs@type-ddd/money
DocsA value object is a small, simple object that represents a single value or characteristic, such as a monetary amount or a date. It is characterized by having no identity of its own, meaning it is equal to another value object if its values are equal, regardless of its reference. Value objects are often used in domain-driven design to represent simple entities in the system.
import { ValueObject, Ok, Fail, Result } from '@type-ddd/core';
interface Props {
amount: number;
}
// simple example as monetary value object business behavior
export default class Money extends ValueObject<Props> {
// private constructor. Avoid public new.
private constructor(props: Props) {
super(props);
}
// any business rule behavior. Check.
public isGt(x: Money): boolean {
const { number: Check } = this.validator;
const xValue = x.get('amount');
const currentValue = this.get('amount');
return Check(xValue).isGreaterThan(currentValue);
}
// any business rule behavior. Calc.
public sum(x: Money): Money {
const { number: Calc } = this.util;
const value = x.get('amount');
const current = this.get('amount');
const amount = Calc(current).sum(value);
return new Money({ amount });
}
// any business rule behavior. Calc.
public subtract(x: Money): Money {
const { number: Calc } = this.util;
const value = x.get('amount');
const current = this.get('amount');
const amount = Calc(current).subtract(value);
return new Money({ amount });
}
// any business rule to validate state.
public static isValidProps({ amount }: Props): boolean {
const { number: Check } = this.validator;
return Check(amount).isPositive();
}
// shortcut to create a zero value
public static zero(): Money {
return new Money({ amount: 0 });
}
// factory method to create an instance and validate value.
public static create(amount: number): Result<Money> {
const isValid = this.isValidProps({ amount });
if(!isValid) return Fail("Invalid amount for money");
return Ok(new Money({ amount }));
}
}
How to use value object instance
// operation result
const resA = Money.create(500);
// check if provided a valid value
console.log(resA.isOk());
// > true
// money instance
const moneyA = resA.value();
moneyA.get("amount");
// 500
// using methods
moneyA.isGt(Money.zero());
// > true
const moneyB = Money.create(100).value();
const moneyC = moneyA.sum(moneyB);
const value = moneyC.get('amount');
console.log(value);
// > 600
An entity in domain-driven design is an object that represents a concept in the real world and has a unique identity and attributes. It is a fundamental building block used to model complex business domains.
import { Entity, Ok, Fail, Result, UID } from '@type-ddd/core';
interface Props {
id?: UID;
total: Money;
discount: Money;
fees: Money;
}
// simple example as payment entity using money value object
export default class Payment extends Entity<Props> {
// private constructor
private constructor(props: Props){
super(props);
}
// any business rule behavior. Update total.
public applyFees(fees: Money): Payment {
const props = this.props;
const total = props.total.sum(fees);
return new Payment({ ...props, total, fees });
}
// any business rule behavior. Discount must be less or equal total.
public applyDiscount(discount: Money): Payment {
const props = this.props;
const total = props.total.subtract(discount);
return new Payment({ ...props, total, discount });
}
// factory method to create a instance. Value must be positive.
public static create(props: Props): Result<Payment> {
return Ok(new Payment(props));
}
}
How to use entity instance
// operation result
const total = Money.create(500).value();
const discount = Money.zero();
const fees = Money.zero();
// create a payment
const payment = Payment.create({ total, discount, fees }).value();
// create fee and discount
const fee = Money.create(17.50).value();
const disc = Money.create(170.50).value();
// apply fee and discount
const result = payment.applyFees(fee).applyDiscount(disc);
// get object from domain entity
console.log(result.toObject());
{
"id": "d7fc98f5-9711-4ad8-aa16-70cb8a52244a",
"total": {
"amount": 347
},
"discount": {
"amount": 170.50
},
"fees": {
"amount": 17.50
},
"createdAt":"2023-01-30T23:11:17.815Z",
"updatedAt":"2023-01-30T23:11:17.815Z"
}
Encapsulate and are composed of entity classes and value objects that change together in a business transaction
In my example, let's use the context of payment. All payment transactions are encapsulated by an order (payment order) that represents a user's purchasing context.
import { Aggregate, Ok, Fail, Result, UID, EventHandler } from '@type-ddd/core';
// Entities and VO that encapsulate context.
interface Props {
id?: UID;
payment: Payment;
items: List<Item>;
status: OrderStatus;
customer: Customer;
}
// Simple example of an order aggregate encapsulating entities and
// value objects for context.
export default class Order extends Aggregate<Props> {
// Private constructor to ensure instances creation through static methods.
private constructor(props: Props){
super(props);
}
// Static method to begin a new order.
// Takes a customer as parameter and returns an instance of Order.
public static begin(customer: Customer): Order {
// Initialize the status of the order as "begin".
const status = OrderStatus.begin();
// Initialize the list of items as empty.
const items: List<Item> = List.empty();
// Initialize the payment as zero, since the order hasn't been paid yet.
const payment = Payment.none();
// Create a new instance of Order with the provided parameters.
const order = new Order({ status, payment, items, customer });
// Add an event to indicate that the order has begun.
order.addEvent('ORDER_HAS_BEGUN', (order) => {
// Perform some important operation when the order begins.
console.log('Do something important...');
});
// Alternatively, add an event by creating an
// instance of a class that extends EventHandler.
order.addEvent(new OrderBeganEventHandler());
// Return the created order instance.
return order;
}
// Method to add an item to the order.
// Takes an item as parameter and returns the Order instance.
addItem(item: Item): Order {
// Add the item to the order's items list.
this.props.items.add(item);
// Sum item price to payment amount
this.props.payment.sum(item.price);
// Return the Order instance itself to allow chained calls.
return this;
}
// Method to perform the payment of the order.
// Takes a payment object as parameter.
pay(payment: Payment): Order {
// Set the status of the order to "paid".
this.props.status = OrderStatus.paid();
// Set the provided payment object.
this.props.payment = payment;
// Add an event to indicate that the order has been paid.
// Assuming OrderPaidEvent is a class representing
// the event of order payment.
this.addEvent(new OrderPaidEventHandler());
return this;
}
// Static method to create an instance of Order.
// Returns a Result, which can be Ok (success) or Fail (failure).
// The value of the Result is an instance of Order,
// if creation is successful.
public static create(props: Props): Result<Order> {
return Ok(new Order(props));
}
}
Event Handler
import { Context, EventHandler } from '@type-ddd/core';
class OrderCreatedEvent extends EventHandler<Order> {
constructor() {
super({ eventName: 'OrderCreated' });
}
dispatch(order: Order): void {
// dispatch event to another context
order.context().dispatchEvent('Context:Event', order.toObject());
};
}
Aggregates domain events
order.addEvent('Event', (...args) => {
console.log(args);
});
// Or add an EventHandler instance
order.addEvent(new OrderCreatedEvent());
order.dispatchEvent('OrderBegun');
// dispatch with args
order.dispatchEvent('Event', { info: 'custom_args' });
// OR call all added events
await order.dispatchAll();
import { Context } from '@type-ddd/core';
const context = Context.events();
context.subscribe('Context:Event', (event) => {
const [model] = event.detail;
console.log(model);
});
// dispatch an event to a context with args
context.dispatchEvent('Context:Event', { name: 'Jane' });
// Dispatching events to specific contexts
// Dispatches the SIGNUP event to Context-X
context.dispatchEvent('Context-X:Signup');
// Dispatches the SIGNUP event to all contexts
context.dispatchEvent('*:Signup');
// Dispatches all events to all contexts. Not recommended
context.dispatchEvent('*:*');
// Dispatches all events under Context-Y
context.dispatchEvent('Context-Y:*');