Pro Software Architecture
Streamline your workflow with this guide, quality, focused, software. Includes structured workflows, validation checks, and reusable patterns for development.
Pro Software Architecture
A foundational skill for software architecture based on Clean Architecture and Domain-Driven Design principles. Covers layered architecture, dependency inversion, domain modeling, bounded contexts, and quality-focused development practices.
When to Use This Skill
Choose this skill when:
- Designing application architecture with clear separation of concerns
- Implementing Clean Architecture layers (domain, application, infrastructure)
- Modeling business domains with DDD patterns (entities, value objects, aggregates)
- Establishing dependency rules between architectural layers
- Refactoring monolithic code into well-structured, maintainable modules
Consider alternatives when:
- Making system-level design decisions ā use a senior architect skill
- Working on microservice boundaries ā use a microservices skill
- Setting up infrastructure ā use a DevOps or cloud skill
- Need specific framework patterns ā use that framework's skill
Quick Start
# Clean Architecture Layer Structure
src/
āāā domain/ # Core business logic (no dependencies)
ā āāā entities/ # Business objects with identity
ā āāā values/ # Immutable value objects
ā āāā events/ # Domain events
ā āāā ports/ # Interfaces (repository, service contracts)
āāā application/ # Use cases (depends only on domain)
ā āāā commands/ # Write operations
ā āāā queries/ # Read operations
ā āāā services/ # Application services / orchestration
āāā infrastructure/ # External concerns (depends on domain + application)
ā āāā persistence/ # Database implementations
ā āāā messaging/ # Event bus, queues
ā āāā external/ # Third-party API clients
āāā presentation/ # UI or API layer (depends on application)
āāā api/ # REST/GraphQL controllers
āāā web/ # Frontend rendering
Core Concepts
Dependency Rule
| Layer | Can Depend On | Cannot Depend On |
|---|---|---|
| Domain | Nothing (pure business logic) | Application, Infrastructure, Presentation |
| Application | Domain | Infrastructure, Presentation |
| Infrastructure | Domain, Application | Presentation |
| Presentation | Application | Domain directly, Infrastructure |
Domain Model Patterns
// Entity ā has identity, mutable state class Order { private constructor( public readonly id: OrderId, private items: OrderItem[], private status: OrderStatus, ) {} static create(customerId: CustomerId): Order { return new Order(OrderId.generate(), [], OrderStatus.Draft); } addItem(product: ProductId, quantity: number, price: Money): void { if (this.status !== OrderStatus.Draft) { throw new DomainError('Cannot modify a submitted order'); } this.items.push(new OrderItem(product, quantity, price)); } submit(): DomainEvent[] { if (this.items.length === 0) { throw new DomainError('Cannot submit an empty order'); } this.status = OrderStatus.Submitted; return [new OrderSubmitted(this.id, this.total())]; } total(): Money { return this.items.reduce((sum, item) => sum.add(item.subtotal()), Money.zero()); } } // Value Object ā immutable, equality by value class Money { constructor( public readonly amount: number, public readonly currency: string, ) { if (amount < 0) throw new DomainError('Money cannot be negative'); } add(other: Money): Money { if (this.currency !== other.currency) throw new DomainError('Currency mismatch'); return new Money(this.amount + other.amount, this.currency); } equals(other: Money): boolean { return this.amount === other.amount && this.currency === other.currency; } static zero(currency = 'USD'): Money { return new Money(0, currency); } } // Port ā interface defined in domain, implemented in infrastructure interface OrderRepository { findById(id: OrderId): Promise<Order | null>; save(order: Order): Promise<void>; nextId(): OrderId; }
Use Case Pattern
// Application layer ā orchestrates domain objects class SubmitOrderUseCase { constructor( private orders: OrderRepository, private eventBus: EventBus, ) {} async execute(command: SubmitOrderCommand): Promise<void> { const order = await this.orders.findById(command.orderId); if (!order) throw new NotFoundError('Order', command.orderId); const events = order.submit(); await this.orders.save(order); for (const event of events) { await this.eventBus.publish(event); } } }
Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
architectureStyle | string | 'clean' | Style: clean, hexagonal, or onion |
domainModeling | string | 'ddd' | Domain patterns: DDD, anemic, or active record |
dependencyInjection | string | 'constructor' | DI strategy: constructor, interface, or container |
eventPattern | string | 'domain-events' | Events: domain-events, event-sourcing, or none |
testStrategy | string | 'ports-and-adapters' | Testing: mock ports, in-memory adapters |
moduleStructure | string | 'by-feature' | Organization: by-feature or by-layer |
Best Practices
-
The domain layer has zero external dependencies ā No framework imports, no database drivers, no HTTP libraries. Domain logic is pure business rules expressed in the language of the domain. This makes it testable without mocking infrastructure.
-
Define interfaces (ports) in the domain, implement in infrastructure ā The repository interface lives in the domain layer. The PostgreSQL implementation lives in infrastructure. The domain declares what it needs; infrastructure provides how.
-
Use value objects for concepts that have no identity ā Money, EmailAddress, DateRange, and Coordinates are value objects: immutable, compared by value, self-validating. They prevent primitive obsession and encapsulate validation rules.
-
Organize code by feature (bounded context), not by layer ā A directory per feature (orders/, customers/, payments/) with subdirectories for layers is more navigable than global directories per layer. Related code lives together.
-
Keep use cases thin: orchestrate, don't implement ā Use cases coordinate domain objects and infrastructure services. Business rules belong in entities and value objects. If a use case contains complex logic, it's missing a domain concept.
Common Issues
Domain layer accumulates infrastructure dependencies ā Over time, developers add database-specific annotations, framework decorators, or HTTP-related types to domain entities. Enforce the dependency rule in code reviews and consider using architecture linting tools like ArchUnit.
Anemic domain models with all logic in services ā Entities that are just data containers with getters and setters push all behavior into service classes. Move behavior to entities: order.submit() instead of orderService.submitOrder(order).
Over-engineering simple CRUD operations with full Clean Architecture ā Not every feature needs entities, value objects, use cases, and ports. Simple CRUD with no business rules can use a thinner architecture. Apply Clean Architecture where domain complexity justifies it.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.