Back to Architecture & Design Patterns

architecture-patterns

architectureclean architecturehexagonal architecturedomain-driven designbackendsystem designsoftware patternsmaintainability
⭐ 36.8kπŸ“„ MITπŸ•’ 2026-06-16Source β†—

Install this skill

npx skills add wshobson/agents

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

Architecture patterns organize complex codebases by enforcing separation between business logic, external infrastructure, and delivery mechanisms. This skill set focuses on structural methodologies such as Clean Architecture, Hexagonal Architecture, and Domain-Driven Design (DDD). These strategies minimize coupling by ensuring the domain layer remains agnostic to databases, UI frameworks, or external APIs. By implementing layers where dependencies point strictly inward, developers create codebases that allow for independent testing and easier long-term modification. The approach relies on defining clear contracts via ports and interfaces, facilitating the swapping of technical components without disturbing core rules. This structured arrangement promotes longevity in software development, ensuring business entities stay protected from technical debt, changing vendors, or shifting infrastructure requirements throughout the software lifecycle.

When to Use This Skill

  • β€’Structuring a greenfield project to support long-term maintainability
  • β€’Refactoring a tangled monolithic service with tightly coupled dependencies
  • β€’Defining clear boundaries when decomposing a large system into microservices
  • β€’Isolating domain rules to enable unit testing without external infrastructure

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • β€œRefactor this codebase into Clean Architecture
  • β€œApply Hexagonal Architecture to my project structure
  • β€œHelp me define bounded contexts for this DDD model
  • β€œCreate an interface for this repository to allow dependency injection
  • β€œSeparate my business logic from the database layer

Pro Tips

  • πŸ’‘Start with a small, non-critical project to practice applying these patterns before integrating them into a large production system.
  • πŸ’‘Always prioritize understanding the core principles (e.g., dependency inversion, separation of concerns) over rigidly following specific pattern implementations.
  • πŸ’‘Use automated testing extensively; well-architected systems are inherently more testable, and tests reinforce good design.

What this skill does

  • β€’Defining domain-centric business logic decoupled from framework-specific code
  • β€’Implementing port-and-adapter patterns to abstract database and service interactions
  • β€’Structuring applications into isolated layers for improved testability
  • β€’Mapping complex business problems into bounded contexts and entities
  • β€’Creating dependency inversion via abstract interfaces for mockable components

When not to use it

  • βœ•Prototyping simple scripts or small CRUD applications with minimal logic
  • βœ•Projects requiring maximum execution speed without concern for future expansion
  • βœ•Early-stage hackathons where development velocity outweighs long-term architecture

Example workflow

  1. Define core business entities and value objects as POJOs or dataclasses
  2. Design abstract repository interfaces as ports for data persistence
  3. Implement use-case classes to orchestrate flow and apply business rules
  4. Create concrete infrastructure adapters that fulfill defined repository ports
  5. Wire dependencies together using dependency injection in the entry layer

Prerequisites

  • –Solid understanding of Object-Oriented Programming
  • –Familiarity with dependency inversion principles
  • –Knowledge of interface definition in your language of choice

Pitfalls & limitations

  • !Over-engineering simple applications leads to unnecessary boilerplate
  • !Deeply nested layers can obscure the code path for junior developers
  • !Misinterpreting boundaries leads to leaking domain logic into outer layers

FAQ

How does Clean Architecture differ from MVC?
MVC is a presentation pattern focusing on UI interaction, while Clean Architecture is an organizational pattern prioritizing the protection of business logic from all external concerns.
Is Hexagonal Architecture just another name for Clean Architecture?
They are related concepts often used together. Hexagonal focuses on separating the core from external ports, while Clean Architecture provides a specific layered structure to enforce that separation.
Do I need to apply these patterns to every class in my project?
No. Focus these patterns on your critical business domain. Simple CRUD operations or configuration classes often do not require full layered architecture.

How it compares

While manual coding often results in coupled spaghetti code where database logic mixes with business rules, this architecture enforces structural boundaries that make the system testable by design.

Source & trust

⭐ 37k starsπŸ“„ MITπŸ•’ Updated 2026-06-16
πŸ“„ Full skill instructions β€” original source: wshobson/agents
# Architecture Patterns

Master proven backend architecture patterns including Clean Architecture, Hexagonal Architecture, and Domain-Driven Design to build maintainable, testable, and scalable systems.

## When to Use This Skill

- Designing new backend systems from scratch
- Refactoring monolithic applications for better maintainability
- Establishing architecture standards for your team
- Migrating from tightly coupled to loosely coupled architectures
- Implementing domain-driven design principles
- Creating testable and mockable codebases
- Planning microservices decomposition

## Core Concepts

### 1. Clean Architecture (Uncle Bob)

**Layers (dependency flows inward):**

- **Entities**: Core business models
- **Use Cases**: Application business rules
- **Interface Adapters**: Controllers, presenters, gateways
- **Frameworks & Drivers**: UI, database, external services

**Key Principles:**

- Dependencies point inward
- Inner layers know nothing about outer layers
- Business logic independent of frameworks
- Testable without UI, database, or external services

### 2. Hexagonal Architecture (Ports and Adapters)

**Components:**

- **Domain Core**: Business logic
- **Ports**: Interfaces defining interactions
- **Adapters**: Implementations of ports (database, REST, message queue)

**Benefits:**

- Swap implementations easily (mock for testing)
- Technology-agnostic core
- Clear separation of concerns

### 3. Domain-Driven Design (DDD)

**Strategic Patterns:**

- **Bounded Contexts**: Separate models for different domains
- **Context Mapping**: How contexts relate
- **Ubiquitous Language**: Shared terminology

**Tactical Patterns:**

- **Entities**: Objects with identity
- **Value Objects**: Immutable objects defined by attributes
- **Aggregates**: Consistency boundaries
- **Repositories**: Data access abstraction
- **Domain Events**: Things that happened

## Clean Architecture Pattern

### Directory Structure

app/
β”œβ”€β”€ domain/ # Entities & business rules
β”‚ β”œβ”€β”€ entities/
β”‚ β”‚ β”œβ”€β”€ user.py
β”‚ β”‚ └── order.py
β”‚ β”œβ”€β”€ value_objects/
β”‚ β”‚ β”œβ”€β”€ email.py
β”‚ β”‚ └── money.py
β”‚ └── interfaces/ # Abstract interfaces
β”‚ β”œβ”€β”€ user_repository.py
β”‚ └── payment_gateway.py
β”œβ”€β”€ use_cases/ # Application business rules
β”‚ β”œβ”€β”€ create_user.py
β”‚ β”œβ”€β”€ process_order.py
β”‚ └── send_notification.py
β”œβ”€β”€ adapters/ # Interface implementations
β”‚ β”œβ”€β”€ repositories/
β”‚ β”‚ β”œβ”€β”€ postgres_user_repository.py
β”‚ β”‚ └── redis_cache_repository.py
β”‚ β”œβ”€β”€ controllers/
β”‚ β”‚ └── user_controller.py
β”‚ └── gateways/
β”‚ β”œβ”€β”€ stripe_payment_gateway.py
β”‚ └── sendgrid_email_gateway.py
└── infrastructure/ # Framework & external concerns
β”œβ”€β”€ database.py
β”œβ”€β”€ config.py
└── logging.py


### Implementation Example

# domain/entities/user.py
from dataclasses import dataclass
from datetime import datetime
from typing import Optional

@dataclass
class User:
"""Core user entity - no framework dependencies."""
id: str
email: str
name: str
created_at: datetime
is_active: bool = True

def deactivate(self):
"""Business rule: deactivating user."""
self.is_active = False

def can_place_order(self) -> bool:
"""Business rule: active users can order."""
return self.is_active

# domain/interfaces/user_repository.py
from abc import ABC, abstractmethod
from typing import Optional, List
from domain.entities.user import User

class IUserRepository(ABC):
"""Port: defines contract, no implementation."""

@abstractmethod
async def find_by_id(self, user_id: str) -> Optional[User]:
pass

@abstractmethod
async def find_by_email(self, email: str) -> Optional[User]:
pass

@abstractmethod
async def save(self, user: User) -> User:
pass

@abstractmethod
async def delete(self, user_id: str) -> bool:
pass

# use_cases/create_user.py
from domain.entities.user import User
from domain.interfaces.user_repository import IUserRepository
from dataclasses import dataclass
from datetime import datetime
import uuid

@dataclass
class CreateUserRequest:
email: str
name: str

@dataclass
class CreateUserResponse:
user: User
success: bool
error: Optional[str] = None

class CreateUserUseCase:
"""Use case: orchestrates business logic."""

def __init__(self, user_repository: IUserRepository):
self.user_repository = user_repository

async def execute(self, request: CreateUserRequest) -> CreateUserResponse:
# Business validation
existing = await self.user_repository.find_by_email(request.email)
if existing:
return CreateUserResponse(
user=None,
success=False,
error="Email already exists"
)

# Create entity
user = User(
id=str(uuid.uuid4()),
email=request.email,
name=request.name,
created_at=datetime.now(),
is_active=True
)

# Persist
saved_user = await self.user_repository.save(user)

return CreateUserResponse(
user=saved_user,
success=True
)

# adapters/repositories/postgres_user_repository.py
from domain.interfaces.user_repository import IUserRepository
from domain.entities.user import User
from typing import Optional
import asyncpg

class PostgresUserRepository(IUserRepository):
"""Adapter: PostgreSQL implementation."""

def __init__(self, pool: asyncpg.Pool):
self.pool = pool

async def find_by_id(self, user_id: str) -> Optional[User]:
async with self.pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT * FROM users WHERE id = $1", user_id
)
return self._to_entity(row) if row else None

async def find_by_email(self, email: str) -> Optional[User]:
async with self.pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT * FROM users WHERE email = $1", email
)
return self._to_entity(row) if row else None

async def save(self, user: User) -> User:
async with self.pool.acquire() as conn:
await conn.execute(
"""
INSERT INTO users (id, email, name, created_at, is_active)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (id) DO UPDATE
SET email = $2, name = $3, is_active = $5
""",
user.id, user.email, user.name, user.created_at, user.is_active
)
return user

async def delete(self, user_id: str) -> bool:
async with self.pool.acquire() as conn:
result = await conn.execute(
"DELETE FROM users WHERE id = $1", user_id
)
return result == "DELETE 1"

def _to_entity(self, row) -> User:
"""Map database row to entity."""
return User(
id=row["id"],
email=row["email"],
name=row["name"],
created_at=row["created_at"],
is_active=row["is_active"]
)

# adapters/controllers/user_controller.py
from fastapi import APIRouter, Depends, HTTPException
from use_cases.create_user import CreateUserUseCase, CreateUserRequest
from pydantic import BaseModel

router = APIRouter()

class CreateUserDTO(BaseModel):
email: str
name: str

@router.post("/users")
async def create_user(
dto: CreateUserDTO,
use_case: CreateUserUseCase = Depends(get_create_user_use_case)
):
"""Controller: handles HTTP concerns only."""
request = CreateUserRequest(email=dto.email, name=dto.name)
response = await use_case.execute(request)

if not response.success:
raise HTTPException(status_code=400, detail=response.error)

return {"user": response.user}


## Hexagonal Architecture Pattern

# Core domain (hexagon center)
class OrderService:
"""Domain service - no infrastructure dependencies."""

def __init__(
self,
order_repository: OrderRepositoryPort,
payment_gateway: PaymentGatewayPort,
notification_service: NotificationPort
):
self.orders = order_repository
self.payments = payment_gateway
self.notifications = notification_service

async def place_order(self, order: Order) -> OrderResult:
# Business logic
if not order.is_valid():
return OrderResult(success=False, error="Invalid order")

# Use ports (interfaces)
payment = await self.payments.charge(
amount=order.total,
customer=order.customer_id
)

if not payment.success:
return OrderResult(success=False, error="Payment failed")

order.mark_as_paid()
saved_order = await self.orders.save(order)

await self.notifications.send(
to=order.customer_email,
subject="Order confirmed",
body=f"Order {order.id} confirmed"
)

return OrderResult(success=True, order=saved_order)

# Ports (interfaces)
class OrderRepositoryPort(ABC):
@abstractmethod
async def save(self, order: Order) -> Order:
pass

class PaymentGatewayPort(ABC):
@abstractmethod
async def charge(self, amount: Money, customer: str) -> PaymentResult:
pass

class NotificationPort(ABC):
@abstractmethod
async def send(self, to: str, subject: str, body: str):
pass

# Adapters (implementations)
class StripePaymentAdapter(PaymentGatewayPort):
"""Primary adapter: connects to Stripe API."""

def __init__(self, api_key: str):
self.stripe = stripe
self.stripe.api_key = api_key

async def charge(self, amount: Money, customer: str) -> PaymentResult:
try:
charge = self.stripe.Charge.create(
amount=amount.cents,
currency=amount.currency,
customer=customer
)
return PaymentResult(success=True, transaction_id=charge.id)
except stripe.error.CardError as e:
return PaymentResult(success=False, error=str(e))

class MockPaymentAdapter(PaymentGatewayPort):
"""Test adapter: no external dependencies."""

async def charge(self, amount: Money, customer: str) -> PaymentResult:
return PaymentResult(success=True, transaction_id="mock-123")


## Domain-Driven Design Pattern

# Value Objects (immutable)
from dataclasses import dataclass
from typing import Optional

@dataclass(frozen=True)
class Email:
"""Value object: validated email."""
value: str

def __post_init__(self):
if "@" not in self.value:
raise ValueError("Invalid email")

@dataclass(frozen=True)
class Money:
"""Value object: amount with currency."""
amount: int # cents
currency: str

def add(self, other: "Money") -> "Money":
if self.currency != other.currency:
raise ValueError("Currency mismatch")
return Money(self.amount + other.amount, self.currency)

# Entities (with identity)
class Order:
"""Entity: has identity, mutable state."""

def __init__(self, id: str, customer: Customer):
self.id = id
self.customer = customer
self.items: List[OrderItem] = []
self.status = OrderStatus.PENDING
self._events: List[DomainEvent] = []

def add_item(self, product: Product, quantity: int):
"""Business logic in entity."""
item = OrderItem(product, quantity)
self.items.append(item)
self._events.append(ItemAddedEvent(self.id, item))

def total(self) -> Money:
"""Calculated property."""
return sum(item.subtotal() for item in self.items)

def submit(self):
"""State transition with business rules."""
if not self.items:
raise ValueError("Cannot submit empty order")
if self.status != OrderStatus.PENDING:
raise ValueError("Order already submitted")

self.status = OrderStatus.SUBMITTED
self._events.append(OrderSubmittedEvent(self.id))

# Aggregates (consistency boundary)
class Customer:
"""Aggregate root: controls access to entities."""

def __init__(self, id: str, email: Email):
self.id = id
self.email = email
self._addresses: List[Address] = []
self._orders: List[str] = [] # Order IDs, not full objects

def add_address(self, address: Address):
"""Aggregate enforces invariants."""
if len(self._addresses) >= 5:
raise ValueError("Maximum 5 addresses allowed")
self._addresses.append(address)

@property
def primary_address(self) -> Optional[Address]:
return next((a for a in self._addresses if a.is_primary), None)

# Domain Events
@dataclass
class OrderSubmittedEvent:
order_id: str
occurred_at: datetime = field(default_factory=datetime.now)

# Repository (aggregate persistence)
class OrderRepository:
"""Repository: persist/retrieve aggregates."""

async def find_by_id(self, order_id: str) -> Optional[Order]:
"""Reconstitute aggregate from storage."""
pass

async def save(self, order: Order):
"""Persist aggregate and publish events."""
await self._persist(order)
await self._publish_events(order._events)
order._events.clear()


## Resources

- **references/clean-architecture-guide.md**: Detailed layer breakdown
- **references/hexagonal-architecture-guide.md**: Ports and adapters patterns
- **references/ddd-tactical-patterns.md**: Entities, value objects, aggregates
- **assets/clean-architecture-template/**: Complete project structure
- **assets/ddd-examples/**: Domain modeling examples

## Best Practices

1. **Dependency Rule**: Dependencies always point inward
2. **Interface Segregation**: Small, focused interfaces
3. **Business Logic in Domain**: Keep frameworks out of core
4. **Test Independence**: Core testable without infrastructure
5. **Bounded Contexts**: Clear domain boundaries
6. **Ubiquitous Language**: Consistent terminology
7. **Thin Controllers**: Delegate to use cases
8. **Rich Domain Models**: Behavior with data

## Common Pitfalls

- **Anemic Domain**: Entities with only data, no behavior
- **Framework Coupling**: Business logic depends on frameworks
- **Fat Controllers**: Business logic in controllers
- **Repository Leakage**: Exposing ORM objects
- **Missing Abstractions**: Concrete dependencies in core
- **Over-Engineering**: Clean architecture for simple CRUD

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/architecture-patterns/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/wshobson/agents/architecture-patterns/SKILL.md
  • Cursor: ~/.cursor/skills/wshobson/agents/architecture-patterns/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/wshobson/agents/architecture-patterns/SKILL.md

πŸš€ Install with CLI:
npx skills add wshobson/agents

Read the Master Guide: Mastering Agent Skills β†’

Recommended Rules

View more rules β†’

Recommended Workflows

View more workflows β†’

Recommended MCP Servers

View more MCP servers β†’

Take It Further

Maximize your productivity with these powerful resources

πŸ“‹

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
πŸ“–

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid architecture & design patterns issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Architecture & Design Patterns and is published by W. Shobson, maintained in wshobson/agents.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.