A controller coordinates work among domain classes, algorithms, and external applications without performing substantial complex or business-critical logic itself. It is intentionally thin: its responsibility is orchestration, while deeper decisions belong in domain logic. section 7.1 section 7.6
Use the controller classification to guide design and testing:
- Keep complexity or domain significance in domain models and algorithms, where unit tests provide high regression protection at lower maintenance cost. section 7.1
- Treat low-complexity orchestration with many collaborators as controller code, and cover it briefly with integration tests rather than extensive unit tests. section 7.1 section 7.6
- Avoid fat controllers: split code that is both complex and collaborator-heavy into a controller and separate business logic. section 7.1 section 7.6
- Mock only unmanaged dependencies at the system’s edges when testing controllers; avoid interaction-focused mocks for neighboring domain classes. section 7.5 section 9.3
Refactoring a fat controller
When a component both coordinates collaborators and performs business decisions, extract the decision-making into a separate domain class. Leave the original component as a humble wrapper that invokes the domain logic and communicates with external dependencies. For cases such as checking email uniqueness or responding to external failures, some business logic may remain in the controller and should be covered with integration tests. section 7.5 section 7.6
Figure 7.6. Code depth versus code width is a useful metaphor to apply when you think of the separation between the business logic and orchestration responsibilities. Controllers orchestrate many dependencies (represented as arrows in the figure) but aren’t complex on their own (complexity is represented as block height). Domain classes are the opposite of that.
Use code depth versus code width to distinguish complex business logic from broad orchestration across many dependencies. section 7.1







