@piggly/ddd-toolkit
v5.2.2
Published
A bunch of tools to use Model-Driven Design and Domain-Driven Design architecture in a back-end application.
Downloads
461
Maintainers
Readme
Domain-Driven Design Toolkit for NodeJS applications
An ESM/CommonJS toolkit providing comprehensive Domain-Driven Design patterns and building blocks for NodeJS applications. This library offers a complete implementation of DDD tactical patterns including Entities, Value Objects, Attributes, Domain Events, Repositories with Unit of Work, and more.
Features
Core DDD Building Blocks
- Entities & Aggregates: Rich domain entities with built-in event emission;
- Value Objects: Immutable value objects with equality comparison;
- Attributes: Mutable attributes with equality comparison;
- Domain Events: Event-driven architecture support with EventEmitter;
- Repository Pattern: Complete repository implementation with Unit of Work;
- Result Pattern: Railway-oriented programming for error handling;
- Application Services: Service layer abstractions with dependency injection.
Key Capabilities
- ✅ Full TypeScript support with comprehensive type definitions;
- ✅ Dual ESM/CommonJS module support;
- ✅ Built-in event sourcing capabilities;
- ✅ Transaction management with Unit of Work;
- ✅ Repository provider for IoC/DI;
- ✅ Result type with chaining, mapping, and error handling;
- ✅ Service provider for dependency management;
- ✅ Collection classes for entities and value objects;
- ✅ Comprehensive error handling system.
Installation
npm install @piggly/ddd-toolkitQuick Start
Entities
import { Entity, UUIDEntityId } from '@piggly/ddd-toolkit';
interface UserProps {
name: string;
email: string;
}
class User extends Entity<UserProps, UUIDEntityId> {
public static create(props: UserProps): User {
const id = UUIDEntityId.generate();
return new User(props, id);
}
get name(): string {
return this.props.name;
}
get email(): string {
return this.props.email;
}
}Value Objects
import { ValueObject } from '@piggly/ddd-toolkit';
interface EmailProps {
value: string;
}
class EmailValueObject extends ValueObject<EmailProps> {
public static create(email: string): Result<Email> {
if (!this.isValid(email)) {
return Result.fail(new Error('Invalid email'));
}
return Result.ok(new Email({ value: email }));
}
private static isValid(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
get value(): string {
return this.props.value;
}
}Attributes
import { Attribute } from '@piggly/ddd-toolkit';
interface EmailProps {
value: string;
}
class EmailAttribute extends Attribute<EmailProps> {
public static create(email: string): Result<Email> {
if (!this.isValid(email)) {
return Result.fail(new Error('Invalid email'));
}
return Result.ok(new Email({ value: email }));
}
private static isValid(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
public change(email: string): Result<Email> {
if (!this.isValid(email)) {
return Result.fail(new Error('Invalid email'));
}
this.props.value = email;
this.markAsModified();
return Result.ok(this);
}
get value(): string {
return this.props.value;
}
}Repository Pattern with Unit of Work
import {
AbstractRelationalRepository,
IUnitOfWork,
RepositoryProvider
} from '@piggly/ddd-toolkit';
class UserRepository extends AbstractRelationalRepository<User> {
async findByEmail(email: string): Promise<User | undefined> {
// Implementation
}
}
// Using with Unit of Work
const provider = new RepositoryProvider();
provider.register('users', UserRepository);
await provider.transaction(async (uow: IUnitOfWork) => {
const userRepo = uow.getRepository<UserRepository>('users');
const user = await userRepo.findById(id);
// Operations within transaction
});Result Pattern
import { Result } from '@piggly/ddd-toolkit';
const result = await Result.ok({ id: 1, name: 'John' })
.map(user => ({ ...user, processed: true }))
.chain(async user => validateUser(user))
.tap(user => console.log('User processed:', user))
.mapError(error => new CustomError(error));
if (result.isFailure()) {
console.error('Error:', result.error);
} else {
console.log('Success:', result.value());
}Domain Events
import { DomainEvent, EventEmitter } from '@piggly/ddd-toolkit';
interface UserCreatedPayload {
userId: string;
email: string;
}
class UserCreatedEvent extends DomainEvent<UserCreatedPayload> {
constructor(payload: UserCreatedPayload) {
super('UserCreated', payload);
}
}
// In your entity
class User extends Entity<UserProps, UUIDEntityId> {
public static create(props: UserProps): User {
const user = new User(props, UUIDEntityId.generate());
user.emit(new UserCreatedEvent({
userId: user.id.value,
email: props.email
}));
return user;
}
}Core Concepts
Entity IDs
The library provides three types of entity IDs:
UUIDEntityId- UUID v4 based identifiers;StringEntityId- String-based identifiers;NumberEntityId- Numeric identifiers.
Collections
Specialized collection classes for managing domain objects:
CollectionOfEntity- Manages entity collections;CollectionOfValueObjects- Manages value object collections;CollectionOfAttributes- Manages attribute collections.
Service Layer
Different service types for proper separation of concerns:
Service- Base service class;DomainService- Domain layer services;ApplicationService- Application layer services;InfraService- Infrastructure layer services.
Error Handling
Comprehensive error system with domain-specific errors:
DomainError- Base domain error;BusinessRuleViolationError- Business rule violations;RuntimeError- Runtime errors;ApplicationError- Application layer errors.
Testing
This library uses Jest for testing. Run tests with:
# Run tests once
npm run test:once
# Run tests in watch mode
npm run test
# Run tests with coverage
npm run test:coverageDevelopment
Build Commands
# Full build
npm run build
# Type checking
npm run check
# Linting
npm run lint
# Format code
npm run formatProject Structure
src/
├── core/
│ ├── entities/ # Entity classes and collections
│ ├── vos/ # Value objects and collections
│ ├── repositories/ # Repository pattern implementation
│ ├── services/ # Service layer classes
│ ├── application/ # Application layer components
│ ├── errors/ # Error classes
│ └── deprecated/ # Deprecated features
├── utils/ # Utility functions
└── index.ts # Main exportsRequirements
- Node.js >= 18
- TypeScript >= 5.0
Migration from v4.x
Version 5.0 introduces breaking changes. Key migration points:
- EventEmmiter → EventEmitter: Update event emitter imports and it cannot be used directly anymore on
EntityandAttribute; - Entity IDs: Use new ID system (
StringEntityId,NumberEntityId,UUIDEntityId); - Repository Pattern: Adopt new repository interfaces with Unit of Work;
- Collections: Collections now use
Setinternally instead ofMap.
See CHANGELOG.md for detailed migration notes.
Changelog
See the CHANGELOG file for information about all code changes.
Contributing
See the CONTRIBUTING file for information before submitting your contribution.
Credits
License
MIT License (MIT). See LICENSE.
