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:
OrderRepositorywith 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