Help us improve
Share bugs, ideas, or general feedback.
From jira-orchestrator
Implements Clean Architecture layers, SOLID principles, dependency injection, DDD, hexagonal architecture, and code quality patterns. Use for new service design or refactoring legacy code.
npx claudepluginhub markus41/claude --plugin jira-orchestratorHow this skill is triggered — by the user, by Claude, or both
Slash command
/jira-orchestrator:clean-architectureThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Comprehensive guide for implementing Clean Architecture, SOLID principles, and maintainable code structures.
Implements Clean Architecture, DDD, and Hexagonal Architecture patterns in NestJS/TypeScript apps for complex backend structuring, domain layers with entities/aggregates, ports/adapters, use cases, and refactoring anemic models.
Guides applying Clean Architecture, Hexagonal Architecture, and Domain-Driven Design to structure systems with isolated business logic, layer boundaries, and dependency rules.
Implements Clean Architecture, Hexagonal Architecture, and Domain-Driven Design for backend systems. Use when architecting new systems or refactoring for maintainability.
Share bugs, ideas, or general feedback.
Comprehensive guide for implementing Clean Architecture, SOLID principles, and maintainable code structures.
Dependencies point inward. Inner layers must not know about outer layers.
┌─────────────────────────────────────────────────┐
│ External Layer (Web, CLI, GraphQL) │
│ ┌───────────────────────────────────────────┐ │
│ │ Infrastructure (Repos, Adapters, ORM) │ │
│ │ ┌───────────────────────────────────────┐ │ │
│ │ │ Application (Use Cases, Services) │ │ │
│ │ │ ┌───────────────────────────────────┐ │ │ │
│ │ │ │ Domain (Entities, VOs, Services) │ │ │ │
│ │ │ └───────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Dependencies point INWARD
Business rules isolated from technical concerns:
// Entity with behavior
export class User {
constructor(
public readonly id: UserId,
private passwordHash: PasswordHash
) {}
changePassword(newPassword: Password, hasher: PasswordHasher): void {
this.passwordHash = hasher.hash(newPassword);
}
}
// Value Object - immutable, validated
export class Email {
private constructor(private readonly value: string) {}
static create(email: string): Email {
if (!this.isValid(email)) throw new InvalidEmailError(email);
return new Email(email.toLowerCase());
}
private static isValid(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
equals(other: Email): boolean {
return this.value === other.value;
}
}
// Repository interface - defines contract
export interface UserRepository {
findById(id: UserId): Promise<User | null>;
save(user: User): Promise<void>;
}
Orchestrates domain objects for use cases:
export class CreateUserUseCase {
constructor(
private readonly userRepository: UserRepository,
private readonly passwordHasher: PasswordHasher
) {}
async execute(input: CreateUserInput): Promise<CreateUserOutput> {
const existing = await this.userRepository.findByEmail(
Email.create(input.email)
);
if (existing) throw new EmailAlreadyExistsError();
const user = new User(
UserId.generate(),
Email.create(input.email),
this.passwordHasher.hash(input.password),
new Date()
);
await this.userRepository.save(user);
return user.toDTO();
}
}
Implements interfaces from inner layers:
export class PostgreSQLUserRepository implements UserRepository {
constructor(private readonly db: Database) {}
async findById(id: UserId): Promise<User | null> {
const row = await this.db.query('SELECT * FROM users WHERE id = $1', [id.toString()]);
return row ? this.toDomain(row) : null;
}
async save(user: User): Promise<void> {
await this.db.query(
`INSERT INTO users (id, email, password_hash) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET email = $2`,
[user.id.toString(), user.email.toString(), user.passwordHash]
);
}
private toDomain(row: UserRow): User {
return new User(UserId.fromString(row.id), PasswordHash.fromString(row.password_hash));
}
}
Entry points to the application:
export class UserController {
constructor(private readonly createUserUseCase: CreateUserUseCase) {}
async create(req: Request, res: Response): Promise<void> {
try {
const result = await this.createUserUseCase.execute(req.body);
res.status(201).json(result);
} catch (error) {
if (error instanceof EmailAlreadyExistsError) {
res.status(409).json({ error: error.message });
}
}
}
}
src/
├── domain/
│ ├── entities/ (User, Order)
│ ├── value-objects/ (Email, Money, UserId)
│ ├── services/ (PricingService)
│ ├── repositories/ (Interfaces only)
│ └── errors/
├── application/
│ ├── use-cases/ (CreateUser, UpdateOrder)
│ ├── services/ (NotificationService)
│ ├── ports/ (EmailPort, PaymentPort)
│ └── dto/
├── infrastructure/
│ ├── repositories/ (PostgreSQL, MongoDB implementations)
│ ├── adapters/ (SendGrid, Stripe)
│ ├── orm/
│ └── config/
├── presentation/
│ ├── http/ (Controllers, Routes, Middleware)
│ ├── graphql/ (Resolvers)
│ └── cli/ (Commands)
├── shared/ (Utilities, Kernel helpers)
└── container/ (Dependency Injection setup)
// src/container/container.ts
import { Container } from 'inversify';
const container = new Container();
// Bind implementations to interfaces
container.bind<UserRepository>(TYPES.UserRepository)
.to(PostgreSQLUserRepository)
.inSingletonScope();
container.bind<CreateUserUseCase>(TYPES.CreateUserUseCase)
.to(CreateUserUseCase)
.inTransientScope();
container.bind<UserController>(TYPES.UserController)
.to(UserController)
.inTransientScope();
export { container };
Each layer has one reason to change:
Add features by creating new use cases, not modifying existing:
export class UpdateUserUseCase { /* ... */ }
Repository implementations are fully interchangeable:
const repo: UserRepository = new PostgreSQLUserRepository(db);
const repo: UserRepository = new MongoUserRepository(client);
// Both satisfy the contract
Use focused interfaces, not fat ones:
// Good: Segregated
interface UserCreator { create(data): User; }
interface UserDeleter { delete(id): void; }
// Bad: Fat interface
interface UserService {
create(): User;
update(): User;
delete(): void;
sendEmail(): void;
generateReport(): Report;
}
Depend on abstractions, not implementations:
// Application defines the port
export interface EmailPort {
send(to: string, subject: string, body: string): Promise<void>;
}
// Infrastructure implements
export class SendGridAdapter implements EmailPort {
async send(to: string, subject: string, body: string): Promise<void> {
await this.sendgrid.send({ to, subject, text: body });
}
}
// Use cases depend on port
export class CreateUserUseCase {
constructor(private readonly emailPort: EmailPort) {}
}
// Unit: Domain logic without infrastructure
describe('User', () => {
it('should change password', () => {
const hasher = new BCryptHasher();
const user = new User(UserId.generate(), hasher.hash('oldpass'));
user.changePassword(Password.create('newpass'), hasher);
expect(user.validatePassword(Password.create('newpass'), hasher)).toBe(true);
});
});
// Integration: Infrastructure with real DB
describe('PostgreSQLUserRepository', () => {
it('should save and retrieve user', async () => {
const repo = new PostgreSQLUserRepository(testDb);
const user = createTestUser();
await repo.save(user);
const retrieved = await repo.findById(user.id);
expect(retrieved).not.toBeNull();
});
});
// E2E: Full stack via HTTP
describe('User API', () => {
it('should create user via POST', async () => {
const response = await request(app).post('/api/users').send({
email: 'test@example.com',
password: 'secure123'
});
expect(response.status).toBe(201);
});
});
// Bad: Business logic in controller
async create(req, res) {
if (await this.db.query('SELECT * FROM users WHERE email = $1', [req.body.email])) {
return res.status(409).json({ error: 'Email exists' });
}
}
// Good: Delegate to use case
async create(req, res) {
const result = await this.createUserUseCase.execute(req.body);
res.status(201).json(result);
}
// Bad: Infrastructure leak in entity
class User {
async save() {
await prisma.user.create({ data: this });
}
}
// Good: Repository handles persistence
class User { /* pure domain */ }
class UserRepository {
async save(user: User) { await prisma.user.create(...); }
}
// Bad: Entity is just data
class User {
id: string;
password: string;
}
class UserService {
changePassword(user: User, pwd: string) {
user.password = hash(pwd); // Logic outside entity
}
}
// Good: Rich domain model
class User {
changePassword(newPassword: Password, hasher: PasswordHasher): void {
if (!newPassword.isStrong()) throw new WeakPasswordError();
this.passwordHash = hasher.hash(newPassword);
}
}