P

Pro Software Architecture

Streamline your workflow with this guide, quality, focused, software. Includes structured workflows, validation checks, and reusable patterns for development.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

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

LayerCan Depend OnCannot Depend On
DomainNothing (pure business logic)Application, Infrastructure, Presentation
ApplicationDomainInfrastructure, Presentation
InfrastructureDomain, ApplicationPresentation
PresentationApplicationDomain 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

ParameterTypeDefaultDescription
architectureStylestring'clean'Style: clean, hexagonal, or onion
domainModelingstring'ddd'Domain patterns: DDD, anemic, or active record
dependencyInjectionstring'constructor'DI strategy: constructor, interface, or container
eventPatternstring'domain-events'Events: domain-events, event-sourcing, or none
testStrategystring'ports-and-adapters'Testing: mock ports, in-memory adapters
moduleStructurestring'by-feature'Organization: by-feature or by-layer

Best Practices

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates