ALD Design Rules

Concrete rules for designing interfaces, DTOs, and contracts in Architect-Led Development.

Interface Rules

Rule 1: One interface = one responsibility (SRP at the contract level)

Each interface should have exactly one reason to change.

  • If an interface has multiple unrelated methods, split it
  • If two interfaces always change together, consider merging
  • Test: Can you describe the interface's purpose in one sentence?

Rule 2: Role-based, not layer-based

Name by decisions/policies, not stack position.

  • Good: OrderPricingStrategy, PaymentValidator, InventoryQuery
  • Bad: OrderService, UserRepository, DataManager

Rule 3: Prefer capabilities over buckets

Instead of broad CRUD interfaces, create focused capabilities.

  • Avoid: OrderRepository with create/read/update/delete/list
  • Prefer: OrderStore, OrderLookup, OrderQuery
  • Clients depend only on what they need (ISP)

Rule 4: Interfaces should be pattern-legible

Each interface should map to a known pattern.

  • Strategy, Adapter, Decorator, Factory, Observer, etc.
  • If you can't name the pattern, question the abstraction
  • Patterns justify the interface's existence

Rule 5: Dependency injection always

Implementations are pluggable; no hidden globals.

  • All dependencies passed via constructor or method parameters
  • No static methods that hide dependencies
  • No service locators or global registries

DTO & Contract Rules

Rule 1: DTOs are immutable by default

Or at minimum, treated as immutable.

  • Prefer readonly/final fields
  • No setters
  • Create new instances for changes

Rule 2: DTOs are behavior-light

Invariants ok; avoid "smart DTOs".

  • Simple validation in constructors is acceptable
  • Avoid business logic in DTOs
  • DTOs transport data, don't make decisions

Rule 3: Contracts must define error modes

Document edge cases and invariants.

  • What happens with null/empty inputs?
  • What exceptions can be thrown?
  • What are the preconditions and postconditions?

Rule 4: Every interface gets a contract test suite

Tests define behavior; implementation follows.

  • Happy paths
  • Edge cases (boundary conditions, empty inputs)
  • Error cases (invalid inputs, precondition violations)
  • Invariants (things that must always be true)

Rule 5: Core logic is framework-agnostic

I/O stays at the edges.

  • No database imports in core domain
  • No HTTP frameworks in business logic
  • No messaging libraries in policies
  • Use ports/adapters for integration

What to Avoid

❌ God interfaces

Interfaces with many unrelated methods.

// BAD: Violates SRP and ISP
interface OrderService {
  void submit(Order o);
  Money price(Order o);
  Order findById(OrderId id);
  void cancel(OrderId id);
  List<Order> search(Criteria c);
}

✓ Single-responsibility roles

Each interface has one clear purpose.

// GOOD: Clear, focused roles
interface OrderSubmission {
  SubmissionResult submit(OrderDraft d);
}

interface OrderPricing {
  Money calculatePrice(OrderDraft d);
}

interface OrderLookup {
  Optional<Order> findById(OrderId id);
}
ALD stance on CRUD repositories: A single interface exposing broad CRUD often violates ISP by forcing consumers to depend on unused methods. Prefer capability-focused ports (OrderStore, OrderLookup, OrderQuery) and adapt them to your persistence tech.

Other things to avoid

  • Layer-based naming (Service/Repository/Manager)
  • Smart DTOs with business logic
  • Mutable shared state
  • Hidden dependencies (statics, globals)
  • Framework imports in core domain
  • Tests that couple to implementation details

Things to embrace

  • Role-based naming (Policy/Strategy/Validator)
  • Immutable DTOs
  • Explicit dependency injection
  • Port/adapter boundaries
  • Tests as specifications
  • Pattern-based abstractions