Clean Architecture and SOLID principles implementation including dependency injection, layer separation, domain-driven design, hexagonal architecture, and code quality patterns
Implements Clean Architecture and SOLID principles with dependency injection and layer separation. Use when designing new services, refactoring legacy code, or enforcing architectural boundaries.
/plugin marketplace add Lobbi-Docs/claude/plugin install jira-orchestrator@claude-orchestrationThis skill is limited to using the following tools:
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);
}
}
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.