```
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frameworks β
β (Express, React, Database Drivers) β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Interface Adapters β
β (Controllers, Presenters, Gateways) β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Application Layer β
β (Use Cases, Application Services) β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Domain Layer β
β (Entities, Value Objects, Domain Services) β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Dependencies point INWARD only.
Inner layers know nothing about outer layers.
```
Domain Layer (Innermost)
```typescript
// entities/User.ts
export class User {
constructor(
public readonly id: string,
public readonly email: Email,
public name: string,
private password: HashedPassword
) {}
changeName(newName: string): void {
if (newName.length < 2) {
throw new DomainError('Name too short');
}
this.name = newName;
}
verifyPassword(plaintext: string): boolean {
return this.password.verify(plaintext);
}
}
// value-objects/Email.ts
export class Email {
constructor(private readonly value: string) {
if (!this.isValid(value)) {
throw new DomainError('Invalid email');
}
}
private isValid(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
toString(): string {
return this.value;
}
}
```
Application Layer (Use Cases)
```typescript
// use-cases/CreateUser.ts
interface CreateUserInput {
email: string;
password: string;
name: string;
}
interface CreateUserOutput {
id: string;
email: string;
name: string;
}
export class CreateUserUseCase {
constructor(
private userRepository: UserRepository,
private passwordHasher: PasswordHasher,
private emailService: EmailService
) {}
async execute(input: CreateUserInput): Promise {
// Validate email uniqueness
const existing = await this.userRepository.findByEmail(input.email);
if (existing) {
throw new ApplicationError('Email already exists');
}
// Create domain object
const hashedPassword = await this.passwordHasher.hash(input.password);
const user = new User(
generateId(),
new Email(input.email),
input.name,
hashedPassword
);
// Persist
await this.userRepository.save(user);
// Side effects
await this.emailService.sendWelcome(user.email);
return {
id: user.id,
email: user.email.toString(),
name: user.name,
};
}
}
```
Interface Adapters
```typescript
// controllers/UserController.ts
export class UserController {
constructor(private createUserUseCase: CreateUserUseCase) {}
async create(req: Request, res: Response): Promise {
const result = await this.createUserUseCase.execute({
email: req.body.email,
password: req.body.password,
name: req.body.name,
});
res.status(201).json(result);
}
}
// repositories/PrismaUserRepository.ts
export class PrismaUserRepository implements UserRepository {
async save(user: User): Promise {
await prisma.user.create({
data: {
id: user.id,
email: user.email.toString(),
name: user.name,
},
});
}
async findByEmail(email: string): Promise {
const data = await prisma.user.findUnique({
where: { email },
});
return data ? this.toDomain(data) : null;
}
private toDomain(data: PrismaUser): User {
return new User(
data.id,
new Email(data.email),
data.name,
new HashedPassword(data.password)
);
}
}
```