liveBook · Manning

Controller

As explained in Unit Testing Principles, Practices, and Patterns

A controller is a software component that coordinates work between other components without itself implementing substantial complex or business-critical logic.

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

How Unit Testing Principles, Practices, and Patterns covers it

How deep it goes
Classifies controllers by complexity and collaborators, then refactors overcomplicated code into orchestration and business-logic components.
Examples
Uses fat controllers, email uniqueness, external failures, and CRM component communications to illustrate orchestration boundaries.
What it assumes
Assumes familiarity with collaborators and tests while distinguishing domain logic from orchestration responsibilities.
What you can do after
Refactor overcomplicated code, choose integration tests for controllers, and place mocks at unmanaged system boundaries.
How it connects
Connects controllers with domain models, algorithms, integration tests, the Humble Object pattern, and hexagonal and functional architectures.

Also explained in

Compare how 8 books explain controller →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Unit Testing Principles, Practices, and Patterns. The section links point to the parts of the book it draws on.