feat(hub-guide): expand plugin with 26 best-practice skills, hooks, and references
Transform hub-guide from a single-skill Hub monorepo guide into a comprehensive programming best-practice plugin covering all situations. Skills (26): - Core: engineering-principles, clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance - Languages: typescript, python, rust, go - Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs - Infrastructure: docker, ci-cd, monitoring - Monorepo: monorepo, hub-guide (existing) Hooks: - SessionStart: auto-detect project type and activate relevant skills - PreToolUse (Write|Edit): inject language-specific rules per file type Reference files for deep dives: - clean-architecture/references/solid.md (SOLID + component principles) - design-patterns/references/catalog.md (full GoF catalog with examples) - testing/references/mocks.md (test double taxonomy) Restructure plugin to modern skills/ directory format.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
---
|
||||
name: clean-architecture
|
||||
description: Apply Clean Architecture, hexagonal architecture, and SOLID principles when designing system boundaries, modules, or microservices. Use when structuring a new service, deciding what a component should own, untangling framework coupling, or whenever the user mentions "clean architecture," "hexagonal architecture," "onion architecture," "ports and adapters," "SOLID," "Dependency Rule," or "architecture boundaries."
|
||||
---
|
||||
|
||||
# Clean Architecture
|
||||
|
||||
Keep business rules independent of frameworks, databases, and UI.
|
||||
|
||||
## The Dependency Rule
|
||||
|
||||
**Source code dependencies must point inward.** Nothing in an inner circle can know about something in an outer circle.
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ Framework / DB / UI / IO │ ← outer: frameworks, drivers, devices
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ Interface Adapters │ │ ← presenters, controllers, gateways
|
||||
│ │ ┌──────────────────────┐ │ │
|
||||
│ │ │ Application (Use Cases) │ │ ← orchestrate business flows
|
||||
│ │ │ ┌──────────────────┐ │ │ │
|
||||
│ │ │ │ Domain / Entities│ │ │ │ ← pure business rules, no deps
|
||||
│ │ │ └──────────────────┘ │ │ │
|
||||
│ │ └──────────────────────┘ │ │
|
||||
│ └──────────────────────────┘ │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Rules
|
||||
|
||||
1. **Domain layer** contains business entities and value objects. Zero framework imports. Zero database imports. Pure types and functions.
|
||||
2. **Application layer** contains use cases — orchestrate domain objects to fulfill business flows. Depends only on domain. Declares ports (interfaces) for IO.
|
||||
3. **Interface adapters** translate between use cases and the outside world — controllers, presenters, gateways. Depends on application layer + frameworks.
|
||||
4. **Infrastructure/Framework layer** implements the ports declared by the application layer — database repos, HTTP clients, message queues.
|
||||
5. **Screaming Architecture:** the project structure should scream "this is a [domain context]" — not "this is a Spring/Next.js/Django project."
|
||||
|
||||
## How to Check
|
||||
|
||||
- Can you swap the database without changing business logic? If not, boundary is violated.
|
||||
- Can you unit-test a use case without spinning up a framework? If not, your use case depends on infrastructure.
|
||||
- Do business entities import anything from the web framework or ORM? If so, revert that dependency.
|
||||
|
||||
## Practical Patterns
|
||||
|
||||
### Port-Adapter
|
||||
```typescript
|
||||
// Domain/Application port (declared here, implemented outside)
|
||||
interface UserRepository {
|
||||
findById(id: string): Promise<User | null>;
|
||||
}
|
||||
// Infrastructure adapter (implemented in infra layer)
|
||||
class PostgresUserRepository implements UserRepository { ... }
|
||||
```
|
||||
|
||||
### Use Case
|
||||
```typescript
|
||||
class CreateOrderUseCase {
|
||||
constructor(private readonly repo: OrderRepository) {}
|
||||
async execute(input: CreateOrderInput): Promise<Order> {
|
||||
const order = Order.create(input.items, input.customerId);
|
||||
return this.repo.save(order);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
Wire dependencies at the composition root — never in use cases or domain.
|
||||
```typescript
|
||||
// Composition Root
|
||||
const orderRepo = new PostgresOrderRepository(db);
|
||||
const createOrder = new CreateOrderUseCase(orderRepo);
|
||||
```
|
||||
|
||||
## SOLID (Quick Ref)
|
||||
|
||||
- **SRP:** A class has one reason to change (one actor).
|
||||
- **OCP:** Open for extension, closed for modification (polymorphism + strategy).
|
||||
- **LSP:** Subtypes must be substitutable for their base types.
|
||||
- **ISP:** Don't depend on interfaces you don't use (keep interfaces focused).
|
||||
- **DIP:** Depend on abstractions, not concretions. Business rules don't import frameworks.
|
||||
|
||||
## Deeper Reference
|
||||
|
||||
When the task calls for it, load:
|
||||
|
||||
- **[references/solid.md](references/solid.md)** — Full SOLID treatment (SRP, OCP, LSP, ISP, DIP). Component principles (REP, CCP, CRP, ADP, SDP, SAP). Examples for each principle, historical evolution, and practical tests for violations.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- ❌ Business logic in route handlers or controllers
|
||||
- ❌ ORM entities directly exposed to the UI
|
||||
- ❌ Database queries mixed into use cases
|
||||
- ❌ Framework decorators on domain entities
|
||||
- ❌ "Everything is a CRUD" — missing use case layer
|
||||
@@ -0,0 +1,166 @@
|
||||
# SOLID Principles — Deep Reference
|
||||
|
||||
## Single Responsibility Principle (SRP)
|
||||
|
||||
> "A class should have one, and only one, reason to change." — Robert C. Martin
|
||||
|
||||
**Evolution of the definition:**
|
||||
- 2000 (PPP): "one reason to change"
|
||||
- 2008 (Clean Code): class does one thing
|
||||
- 2017 (Clean Architecture): "responsible to one, and only one, **actor**" — where actor is a person or tightly coupled group (e.g., accounting dept, HR dept, DevOps team)
|
||||
|
||||
### Practical Test
|
||||
If you cannot describe a module's responsibility in one sentence without "and," it violates SRP.
|
||||
|
||||
```typescript
|
||||
// ❌ Two actors: Accounting (calculatePay) + HR (save)
|
||||
class Employee {
|
||||
calculatePay(): Money { ... }
|
||||
save(): void { ... }
|
||||
}
|
||||
|
||||
// ✅ Separated by actor
|
||||
class EmployeePaymentCalc { calculatePay(emp: Employee): Money }
|
||||
class EmployeeRepository { save(emp: Employee): void }
|
||||
```
|
||||
|
||||
### When SRP Is Violated
|
||||
- Mixed persistence + business logic in the same class
|
||||
- A controller that validates, orchestrates, AND formats the response
|
||||
- A module that imports from both `domain/` and `infra/` packages
|
||||
|
||||
---
|
||||
|
||||
## Open/Closed Principle (OCP)
|
||||
|
||||
> "Software entities should be open for extension, closed for modification." — Bertrand Meyer
|
||||
|
||||
New behavior is added through **new code** (new classes, new modules), not by **editing existing, tested code**.
|
||||
|
||||
### Strategy Pattern (canonical OCP)
|
||||
```typescript
|
||||
// ❌ Closed for extension without modification
|
||||
function calculateDiscount(type: string, amount: number) {
|
||||
if (type === 'none') return 0;
|
||||
if (type === 'seasonal') return amount * 0.1;
|
||||
if (type === 'loyalty') return amount * 0.2;
|
||||
}
|
||||
|
||||
// ✅ Open for extension — add new strategy, never touch this code
|
||||
interface DiscountStrategy { apply(amount: number): number;
|
||||
class SeasonalDiscount implements DiscountStrategy { apply(a) { return a * 0.1 } }
|
||||
class LoyaltyDiscount implements DiscountStrategy { apply(a) { return a * 0.2 } }
|
||||
class DiscountCalculator {
|
||||
constructor(private strategies: DiscountStrategy[]) {}
|
||||
calculate(amount: number) { return this.strategies.reduce((acc, s) => acc + s.apply(amount), 0); }
|
||||
}
|
||||
```
|
||||
|
||||
### OCP Warning Signs
|
||||
- `if/else` or `switch` chains on a type/enum field
|
||||
- Feature toggles mixed into business logic (use plugin architecture)
|
||||
- Every new feature touches 5+ existing files
|
||||
|
||||
---
|
||||
|
||||
## Liskov Substitution Principle (LSP)
|
||||
|
||||
> "Objects of a superclass shall be replaceable with objects of its subclasses without breaking the system." — Barbara Liskov (1987)
|
||||
|
||||
**Revised (2020s):** "Subtypes must be substitutable for their base types." — applies to interfaces, protocols, and type parameters, not just class inheritance.
|
||||
|
||||
### The Square-Rectangle Problem (classic violation)
|
||||
```typescript
|
||||
class Rectangle { setWidth(w): void; setHeight(h): void }
|
||||
class Square extends Rectangle {
|
||||
setWidth(w) { super.setWidth(w); super.setHeight(w); } // Breaks caller's expectation
|
||||
}
|
||||
```
|
||||
|
||||
### Rules for Substitutability
|
||||
1. **Preconditions cannot be strengthened** in the subtype — subtype must accept everything the base accepts.
|
||||
2. **Postconditions cannot be weakened** — subtype must guarantee at least what the base guarantees.
|
||||
3. **Invariants must be preserved** — the base class's invariants must hold in the subtype.
|
||||
4. **History constraint** (Meyer): subtype methods cannot introduce state changes the base type wouldn't allow.
|
||||
|
||||
### LSP in Practice
|
||||
```typescript
|
||||
// Violation: PostgresUserRepo expects a table name, InMemoryUserRepo doesn't — not substitutable
|
||||
interface UserRepository {
|
||||
find(id: string): User;
|
||||
}
|
||||
class PostgresUserRepo implements UserRepository {
|
||||
constructor(private table: string) {} // extra constraint
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Interface Segregation Principle (ISP)
|
||||
|
||||
> "No client should be forced to depend on methods it does not use." — Robert C. Martin
|
||||
|
||||
Fat interfaces force implementors to stub out methods they don't need.
|
||||
|
||||
```typescript
|
||||
// ❌ Fat interface — forces every worker to implement onError, even if they never fail
|
||||
interface Worker { work(): void; eat(): void; onError(e: Error): void }
|
||||
|
||||
// ✅ Segregated — each interface has one job
|
||||
interface Workable { work(): void }
|
||||
interface Eatable { eat(): void }
|
||||
interface ErrorHandler { onError(e: Error): void }
|
||||
```
|
||||
|
||||
### When ISP Is Violated
|
||||
- A single interface has methods from different concerns (CRUD + reporting + admin)
|
||||
- Classes implement interface methods as `throw new UnsupportedOperationException`
|
||||
- Interface methods are unused in 80% of callers (consider splitting input vs output ports)
|
||||
|
||||
---
|
||||
|
||||
## Dependency Inversion Principle (DIP)
|
||||
|
||||
> "Abstractions should not depend on details. Details should depend on abstractions." — Robert C. Martin
|
||||
|
||||
**Not to be confused with Dependency Injection** (which is one way to implement DIP).
|
||||
|
||||
### High-level policy should not import low-level detail
|
||||
```typescript
|
||||
// ❌ High-level module depends on low-level detail
|
||||
class CreateOrderUseCase {
|
||||
private db = new PostgresConnection(); // violates DIP
|
||||
}
|
||||
|
||||
// ✅ Both depend on abstraction
|
||||
interface OrderRepository { save(order: Order): Promise<void> }
|
||||
class CreateOrderUseCase {
|
||||
constructor(private repo: OrderRepository) {} // depends on abstraction
|
||||
}
|
||||
class PostgresOrderRepo implements OrderRepository {} // detail depends on abstraction
|
||||
```
|
||||
|
||||
### The Dependency Rule (Clean Architecture)
|
||||
Source code dependencies point **inward** — nothing in an inner circle knows about something in an outer circle:
|
||||
- Domain → no imports from framework/infra/db
|
||||
- Application → imports domain, declares ports (interfaces)
|
||||
- Infrastructure → implements ports
|
||||
- Framework → wires everything at the composition root
|
||||
|
||||
---
|
||||
|
||||
## Component Principles (for larger systems)
|
||||
|
||||
### Cohesion Principles
|
||||
| Principle | Statement |
|
||||
|-----------|-----------|
|
||||
| **REP** (Reuse-Release Equivalence) | The unit of reuse is the unit of release |
|
||||
| **CCP** (Common Closure Principle) | Classes that change together belong together |
|
||||
| **CRP** (Common Reuse Principle) | Don't depend on things you don't use |
|
||||
|
||||
### Coupling Principles
|
||||
| Principle | Statement |
|
||||
|-----------|-----------|
|
||||
| **ADP** (Acyclic Dependencies Principle) | No cycles in the dependency graph |
|
||||
| **SDP** (Stable Dependencies Principle) | Depend in the direction of stability |
|
||||
| **SAP** (Stable Abstractions Principle) | Stable components should be abstract |
|
||||
Reference in New Issue
Block a user