clean-architecture-kernel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +224 -0
- package/package.json +13 -0
- package/src/core/AggregateRoot.ts +3 -0
- package/src/core/DomainEvent.ts +4 -0
- package/src/core/Entity.ts +19 -0
- package/src/core/Mediator.ts +13 -0
- package/src/core/Result.ts +29 -0
- package/src/core/ValueObject.ts +7 -0
- package/src/index.ts +6 -0
package/Readme.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Clean Architecture Kernel
|
|
2
|
+
|
|
3
|
+
> A lightweight, zero-dependency npm package for implementing Clean Architecture, Domain-Driven Design, and CQRS patterns in Node.js applications.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/clean-architecture-kernel)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Clean Architecture Kernel provides the essential building blocks for implementing Clean Architecture, Domain-Driven Design (DDD), and CQRS patterns in your application.
|
|
14
|
+
|
|
15
|
+
### Why Clean Architecture Kernel?
|
|
16
|
+
|
|
17
|
+
This package focuses on core domain patterns only, ensuring the kernel remains:
|
|
18
|
+
|
|
19
|
+
- **Small** - Minimal footprint, zero external dependencies
|
|
20
|
+
- **Fast** - No abstractions, direct pattern implementations
|
|
21
|
+
- **Framework-agnostic** - Works with any Node.js framework or runtime
|
|
22
|
+
- **Type-safe** - Built with TypeScript for complete type safety
|
|
23
|
+
|
|
24
|
+
### Universal Primitives
|
|
25
|
+
|
|
26
|
+
- `Result<T>` - Functional success/failure wrapper
|
|
27
|
+
- `Entity` - Domain entities with identity and events
|
|
28
|
+
- `AggregateRoot` - Aggregate consistency boundaries
|
|
29
|
+
- `ValueObject` - Immutable property-defined objects
|
|
30
|
+
- `DomainEvent` - Domain occurrence representations
|
|
31
|
+
- `Mediator` - In-memory command/query dispatcher
|
|
32
|
+
|
|
33
|
+
### Perfect For
|
|
34
|
+
|
|
35
|
+
- Backend services & REST APIs
|
|
36
|
+
- Microservices architectures
|
|
37
|
+
- Serverless functions
|
|
38
|
+
- Monolithic applications
|
|
39
|
+
- Event-driven systems
|
|
40
|
+
|
|
41
|
+
## Table of Contents
|
|
42
|
+
|
|
43
|
+
- [Installation](#installation)
|
|
44
|
+
- [Core Concepts](#core-concepts)
|
|
45
|
+
- [Usage Examples](#usage-examples)
|
|
46
|
+
- [Project Structure](#project-structure)
|
|
47
|
+
- [Design Principles](#design-principles)
|
|
48
|
+
- [Roadmap](#roadmap)
|
|
49
|
+
- [Contributing](#contributing)
|
|
50
|
+
- [License](#license)
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Choose your Node.js package manager:
|
|
55
|
+
|
|
56
|
+
### NPM
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install clean-architecture-kernel
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Yarn
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
yarn add clean-architecture-kernel
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### PNPM
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pnpm add clean-architecture-kernel
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Core Concepts
|
|
75
|
+
## Core Concepts
|
|
76
|
+
|
|
77
|
+
### Result<T>
|
|
78
|
+
|
|
79
|
+
A functional wrapper that encapsulates success or failure states, eliminating the need for try-catch blocks.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const result = Result.ok(user);
|
|
83
|
+
|
|
84
|
+
if (result.isFailure) {
|
|
85
|
+
console.log(result.error);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Entity
|
|
90
|
+
|
|
91
|
+
Base class for domain entities with identity and domain event support.
|
|
92
|
+
|
|
93
|
+
### AggregateRoot
|
|
94
|
+
|
|
95
|
+
Specialized entity that acts as a consistency boundary for related entities.
|
|
96
|
+
|
|
97
|
+
### ValueObject
|
|
98
|
+
|
|
99
|
+
Immutable object defined by its properties rather than identity, ensuring data integrity.
|
|
100
|
+
|
|
101
|
+
### DomainEvent
|
|
102
|
+
|
|
103
|
+
Represents something that happened inside the domain, enabling event-driven architectures.
|
|
104
|
+
|
|
105
|
+
### Mediator
|
|
106
|
+
|
|
107
|
+
Simple in-memory mediator pattern for dispatching commands and queries.
|
|
108
|
+
|
|
109
|
+
## Project Structure
|
|
110
|
+
## Project Structure
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
src/
|
|
114
|
+
├── core/
|
|
115
|
+
│ ├── Result.ts # Success/failure wrapper
|
|
116
|
+
│ ├── Entity.ts # Domain entity base class
|
|
117
|
+
│ ├── AggregateRoot.ts # Aggregate consistency boundary
|
|
118
|
+
│ ├── ValueObject.ts # Immutable value object
|
|
119
|
+
│ ├── DomainEvent.ts # Domain event base class
|
|
120
|
+
│ └── Mediator.ts # Command/query dispatcher
|
|
121
|
+
└── index.ts # Public API exports
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Usage Examples
|
|
125
|
+
|
|
126
|
+
### 1. Creating a Value Object
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
class Email extends ValueObject<{ value: string }> {
|
|
130
|
+
private constructor(props) {
|
|
131
|
+
super(props);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static create(value: string): Result<Email> {
|
|
135
|
+
if (!value.includes("@")) {
|
|
136
|
+
return Result.fail("Invalid email");
|
|
137
|
+
}
|
|
138
|
+
return Result.ok(new Email({ value }));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 2. Creating an Entity
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
class User extends AggregateRoot<string> {
|
|
147
|
+
constructor(id: string, public email: Email) {
|
|
148
|
+
super(id);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 3. Defining a Command
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
class CreateUserCommand {
|
|
157
|
+
constructor(public readonly email: string) {}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 4. Implementing a Command Handler
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
class CreateUserHandler {
|
|
165
|
+
async handle(command: CreateUserCommand) {
|
|
166
|
+
const emailResult = Email.create(command.email);
|
|
167
|
+
if (emailResult.isFailure) return emailResult;
|
|
168
|
+
|
|
169
|
+
const user = new User("123", emailResult.value);
|
|
170
|
+
return Result.ok(user);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 5. Using the Mediator
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const mediator = new Mediator();
|
|
179
|
+
mediator.register("CreateUserCommand", new CreateUserHandler());
|
|
180
|
+
|
|
181
|
+
const result = await mediator.send(new CreateUserCommand("test@mail.com"));
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Design Principles
|
|
185
|
+
## Design Principles
|
|
186
|
+
|
|
187
|
+
✓ **No Dependencies** - Pure TypeScript/JavaScript, zero external npm packages
|
|
188
|
+
✓ **Predictable Behavior** - Consistent, deterministic pattern implementations
|
|
189
|
+
✓ **Production-Ready** - Optimized for Node.js backends and microservices
|
|
190
|
+
✓ **Minimal API** - Small surface area, easy to learn and understand
|
|
191
|
+
✓ **Type-Safe** - Full TypeScript support out of the box
|
|
192
|
+
✓ **Inspired by** - Domain-Driven Design, Clean Architecture, and CQRS
|
|
193
|
+
|
|
194
|
+
## Roadmap
|
|
195
|
+
|
|
196
|
+
- [ ] Event Bus abstraction for decoupled event publishing
|
|
197
|
+
- [ ] Notification pattern implementation
|
|
198
|
+
- [ ] Pipeline behaviors (logging, validation, metrics)
|
|
199
|
+
- [ ] Async domain event dispatcher
|
|
200
|
+
- [ ] Optional integrations (Redis, Kafka, RabbitMQ)
|
|
201
|
+
- [ ] Advanced aggregate query patterns
|
|
202
|
+
- [ ] Built-in validation framework
|
|
203
|
+
|
|
204
|
+
## Contributing
|
|
205
|
+
|
|
206
|
+
Contributions are welcome! To contribute:
|
|
207
|
+
|
|
208
|
+
1. Fork the repository
|
|
209
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
210
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
211
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
212
|
+
5. Open a Pull Request
|
|
213
|
+
|
|
214
|
+
Please ensure your code follows the existing style and includes appropriate tests.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
**Made with ❤️ by Andrés Mariño**
|
|
223
|
+
|
|
224
|
+
*For developers building scalable, maintainable architectures*
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clean-architecture-kernel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "module"
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DomainEvent } from "./DomainEvent";
|
|
2
|
+
|
|
3
|
+
export abstract class Entity<TId> {
|
|
4
|
+
private _domainEvents: DomainEvent[] = [];
|
|
5
|
+
|
|
6
|
+
constructor(public readonly id: TId) {}
|
|
7
|
+
|
|
8
|
+
get domainEvents(): DomainEvent[] {
|
|
9
|
+
return this._domainEvents;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
protected addDomainEvent(event: DomainEvent): void {
|
|
13
|
+
this._domainEvents.push(event);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public clearEvents(): void {
|
|
17
|
+
this._domainEvents = [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class Mediator {
|
|
2
|
+
private handlers = new Map();
|
|
3
|
+
|
|
4
|
+
register(commandType: string, handler: any) {
|
|
5
|
+
this.handlers.set(commandType, handler);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async send(command: any) {
|
|
9
|
+
const handler = this.handlers.get(command.constructor.name);
|
|
10
|
+
if (!handler) throw new Error(`No handler for ${command.constructor.name}`);
|
|
11
|
+
return handler.handle(command);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class Result<T> {
|
|
2
|
+
private constructor(
|
|
3
|
+
public readonly isSuccess: boolean,
|
|
4
|
+
public readonly error?: string,
|
|
5
|
+
private readonly _value?: T
|
|
6
|
+
) {
|
|
7
|
+
if (isSuccess && error) {
|
|
8
|
+
throw new Error("A result cannot be successful and contain an error");
|
|
9
|
+
}
|
|
10
|
+
if (!isSuccess && !error) {
|
|
11
|
+
throw new Error("A failing result needs an error message");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get value(): T {
|
|
16
|
+
if (!this.isSuccess) {
|
|
17
|
+
throw new Error("Cannot get value of a failed result");
|
|
18
|
+
}
|
|
19
|
+
return this._value as T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static ok<U>(value?: U): Result<U> {
|
|
23
|
+
return new Result<U>(true, undefined, value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static fail<U>(error: string): Result<U> {
|
|
27
|
+
return new Result<U>(false, error);
|
|
28
|
+
}
|
|
29
|
+
}
|