skip to content

Controller

Route system and UI events to a dedicated non-UI coordinator, so a use case has a home that is not the view. The failure mode to recognise is the bloated controller that quietly accumulates the domain logic instead.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

5

In the GRASP set of design principles, what does the Controller principle say about who should receive a system input event (such as a button click or an incoming HTTP request), and why?

level: juniorimportance: must knowfreq 72%

answer

  1. first non-UI receiver of a system event
  2. facade controller vs use-case controller
  3. coordinate + delegate, never compute
  4. UI knows controller; controller never knows UI
  5. testable without a screen

basics

~20 s

GRASP Controller says: don't let the UI widget itself do the work. Give the event to a separate non-UI object — a controller — that coordinates the domain objects and returns a result. This keeps screen code and business rules apart.

solid answer

~50 s

GRASP (General Responsibility Assignment Software Patterns) Controller answers: which object should be the first non-UI receiver of a system event? Answer: a dedicated coordinator object that represents either the overall system/subsystem (a facade controller) or a specific use case/session (a use-case controller). The UI element — a button handler, an HTTP endpoint, a CLI parser — only translates the raw input into a call on that controller, then renders whatever comes back. Benefits: domain logic becomes reusable across UIs (web, mobile, batch, tests); it can be unit-tested without a screen or a servlet container; the use case gets one visible entry point where transactions, authorization and validation orchestration can live. The controller itself should not implement the business rules — it delegates to domain objects and services. When it starts implementing them, it becomes a 'bloated controller', the anti-pattern the principle warns about.

code

pseudocode · 14 lines
pseudocode
// UI adapter: no business rules, only translation
onPlaceOrderClicked(form):
    cmd    = PlaceOrder(customerId: session.user, items: form.items)
    result = placeOrderHandler.handle(cmd)   // <- GRASP controller
    render(result)

// Use-case controller: coordinates, delegates, does not compute
class PlaceOrderHandler:
    handle(cmd):
        customer = customers.byId(cmd.customerId)
        order    = customer.placeOrder(cmd.items)   // rules live in the domain
        orders.save(order)
        events.publish(OrderPlaced(order.id))
        return OrderSummary(order.id, order.total)

go deeper

for a junior

Name it: the event goes to a non-UI object, not to the button handler; the controller delegates to domain objects. One concrete example is enough.

for a middle

Give both variants — facade controller and use-case (session) controller — say when each fits, and explain the reuse/testability payoff plus the bloated-controller anti-pattern.

for a senior

Connect it to layering: framework controller = adapter, GRASP controller = application/use-case layer, rules in the domain. Discuss where transactions and authorization sit, and when the extra layer isn't worth it (simple reads).

for a principal

Frame it as a boundary policy: one entry point per use case gives a consistent seam for transactions, authz, idempotency, tracing, and API versioning; explain how it maps onto hexagonal ports, CQRS handlers, and evolvability across multiple delivery channels.

## What GRASP is **GRASP** stands for *General Responsibility Assignment Software Patterns* (Craig Larman, *Applying UML and Patterns*). It is a set of nine naming-and-reasoning principles — Information Expert, Creator, **Controller**, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations — each of which answers one question of the form *"which object should have this responsibility?"* They are not code structures you download; they are decision heuristics you apply while assigning responsibilities. ## The question Controller answers > A **system event** (also called a system operation or input event) has arrived — a user pressed *Place Order*, an HTTP `POST /orders` came in, a message landed on a queue, a scheduled job fired. **Which object receives it first, on the non-UI side?** Controllers exist because the alternative is worse. Without one, the natural place to put the logic is the thing that physically caught the event: the button's click handler, the web framework's request handler, the console loop. That works exactly once — until you need a second UI, a scripted/batch path, or an automated test. ## The rule, stated Assign the responsibility for handling a system input event to a class representing **one** of: 1. **The overall system, a "root object", a device, or a major subsystem** — this variant is called a **facade controller**. Example: `OrderingSystem`, `PosSystem`, `PaymentGateway`. One object fronts a whole subsystem. 2. **A use case scenario within which the event occurs** — a **use-case controller** (Larman also calls it a *session controller*), usually named `<UseCase>Handler` or `Handle<UseCase>` or `<UseCase>Service`: `PlaceOrderHandler`, `CheckoutService`, `RegisterUserUseCase`. One object per use case (or per closely related family of use cases). Crucially, the controller is a **non-UI object**. It must not import, reference, or know about widgets, HTTP request/response types, HTML, terminal escape codes, or view models of a specific framework. It speaks in domain terms and plain data. ## What the controller actually does A well-behaved controller **coordinates and delegates**; it does not compute. A typical body: 1. Convert incoming primitive/DTO input into domain concepts (or accept an already-converted command object). 2. Load the relevant domain objects via repositories/gateways. 3. Call domain methods on those objects — the business rules live *there*, per the Information Expert principle ("give the responsibility to the class that has the data needed to fulfil it"). 4. Persist / publish results, commit the unit of work. 5. Return a plain result (a DTO, an id, a domain-level outcome) to the caller. The UI layer then decides how to *display* that result. That split is the whole point: **the controller is where the use case lives; the UI is where the pixels live.** ## Why it pays off - **Reuse across delivery mechanisms.** The same `PlaceOrderHandler` serves a web form, a mobile API, a CSV importer, and a test. - **Testability.** You can drive the use case in-process with no browser, no HTTP, no framework container. This is usually the single biggest practical win. - **A place for cross-cutting concerns.** Transactions, authorization checks, idempotency keys, audit logging, metrics — all have one obvious seam per use case. - **State across events.** A use-case controller can hold or look up the state of a multi-step scenario ("we are mid-checkout, three items scanned"), which the stateless UI cannot sensibly own. - **Protection from UI churn.** Redesigning the screen, or swapping React for something else, doesn't touch the use case. ## Costs and edge cases - **Extra indirection.** For a genuinely trivial read ("show me this record") a controller layer can be ceremony. Many teams allow simple queries to go straight from the endpoint to a query object/read model and reserve controllers for state-changing operations. - **Bloat risk.** A facade controller that fronts a large system accumulates dozens of methods and unrelated state — low cohesion. The standard remedy is to split into use-case controllers. - **Controller name collision.** "Controller" in **MVC** and "controller" in a web framework (`@RestController`, `ActionController`, ASP.NET `Controller`) are *presentation-layer adapters*. GRASP Controller sits **behind** them. A framework controller that just deserializes, calls a use-case object, and serializes the answer satisfies GRASP; one that contains the business rules does not. ## Rule of thumb If you can't run your use case from a test without starting the UI or the web framework, you don't have a GRASP controller yet.

  • Does GRASP Controller mean every system event needs its own class?
    No. It says the event must be received by a non-UI coordinator; granularity is a separate choice. A facade controller can host many related events; splitting into one class per use case is the remedy you reach for when the facade loses cohesion or accumulates unrelated state.
  • If the controller must not contain business logic, where does the logic go?
    Into the domain objects that own the data needed to decide — that is the Information Expert principle. The controller only sequences calls, handles the unit of work, and shapes the reply.
  • How do you know a class is really a controller and not just a UI handler with a different name?
    Check its dependencies. If it references request/response objects, widgets, HTML, or session cookies, it's still presentation. A controller's imports should be domain types, repositories, and plain data.

A restaurant server takes your order at the table but doesn't cook it. The server is the single point of contact for your 'order' event, translates it into instructions for the kitchen, and brings back the result. Swap the dining room for a drive-through window and the kitchen doesn't change.

saying these in an interview costs you the question

  • Saying GRASP Controller is the same thing as the 'C' in MVC or a web framework's @Controller class
  • Claiming the controller should contain the business rules because 'it handles the request'
  • Assuming every controller must be stateless — a use-case/session controller may legitimately hold scenario state
  • Believing a controller must be one-per-screen (it is per system event / use case, not per view)
  • Treating the controller as an object that talks to the UI — the dependency points the other way: UI depends on controller

context

open as a page

GRASP describes two kinds of controller: a facade controller (one object representing the whole system or a major subsystem) and a use-case controller (one object per use-case scenario). What are the trade-offs, and how do you decide which to use?

level: middleimportance: must knowfreq 55%

basics

~20 s

A facade controller is one object handling all events of a system — simple, fine for small apps. A use-case controller handles one scenario each — more classes, but each stays small and cohesive. Start with a facade; split when it grows.

open as a page

Developers often say "my HTTP endpoint class is my controller, so I already follow GRASP Controller." Explain how a web framework's controller (or MVC's 'C') relates to the GRASP Controller principle, and when that claim is wrong.

level: middleimportance: should knowfreq 48%

basics

~20 s

A web/MVC controller belongs to the presentation layer — it knows about requests, responses and views. GRASP's controller must not know any of that. The claim is only true if the endpoint just translates input, calls a use-case object, and formats the reply.

open as a page

You inherit a class named AccountService with 40 public methods, 14 injected dependencies, and several hundred lines of branching business rules that every screen and job in the system calls. Using GRASP reasoning, how do you diagnose and refactor it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

That's a bloated controller: it receives too many events, does the work itself, and holds too much. Fix it by splitting into one coordinator per use case and pushing the actual rules down into the domain objects that own the data.

open as a page

Treating the GRASP controller as the single entry point per use case makes it a natural boundary for cross-cutting policy. Which concerns belong at that boundary (transactions, authorization, idempotency, tracing, validation), and what goes wrong when they are placed above it in the delivery adapter or below it in the domain?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

The controller is the one place every caller passes through, so transaction start/commit, permission checks, idempotency, and tracing spans fit there. Put them in the HTTP layer and non-HTTP callers skip them; put them in entities and they get duplicated and tangled.

open as a page