This lesson was translated automatically – it may contain errors.
A Controller is an object that handles system events and is not part of the user interface. It defines the methods for carrying out system operations.
Why it is needed
Without a controller the logic of a scenario spreads across button handlers and HTTP endpoints. Changing how the event arrives – adding a console interface next to the web one, say – means writing the scenario all over again.
The controller takes the event and decides who does the work. Anything can arrive from outside – an empty form, someone else’s identifier, a string where a number was expected; the controller’s job is either to return an error or to bring the data into a shape the domain is comfortable with.
«Raw data» is literally what came in over the wire. The body of a POST /checkout request
from a web form looks roughly like this:
{
"orderId": " 1042 ",
"quantity": "2 pcs",
"promo": "",
"comment": "<script>alert(1)</script>"
}
There is not a single field here you can trust:
orderId– a string with spaces instead of a number;quantity– not a number at all;promo– an empty string: does that mean «no promo code» or «an empty promo code»?comment– someone else’s markup inside the text.
From the console the same event arrives as argument strings, from a queue – as a message with the fields in a different order.
Sorting all of that out is the controller’s work: it turns the request into
Checkout(orderId: 1042, quantity: 2) or answers with an error without ever reaching the
domain. The order and the payment service get ready-made values and know nothing about
strings or HTTP.
The controller itself does nothing substantial: the moment business logic appears in it, it turns into a «god object».
// Controller: takes the system event and hands out the work
public class CheckoutController
{
private readonly IOrderRepository _orders;
private readonly IPaymentService _payments;
public CheckoutController(IOrderRepository orders, IPaymentService payments)
{
_orders = orders;
_payments = payments;
}
public void Checkout(int orderId)
{
var order = _orders.GetById(orderId);
_payments.Pay(order.Total);
order.MarkPaid();
_orders.Save(order);
}
}
// Controller: takes the system event and hands out the work
class CheckoutController {
public:
CheckoutController(IOrderRepository& orders, IPaymentService& payments)
: orders_(orders), payments_(payments) {}
void Checkout(int order_id) {
Order order = orders_.GetById(order_id);
payments_.Pay(order.Total());
order.MarkPaid();
orders_.Save(order);
}
private:
IOrderRepository& orders_;
IPaymentService& payments_;
};
// Controller: takes the system event and hands out the work
type CheckoutController struct {
orders OrderRepository
payments PaymentService
}
func NewCheckoutController(orders OrderRepository, payments PaymentService) *CheckoutController {
return &CheckoutController{orders: orders, payments: payments}
}
func (c *CheckoutController) Checkout(orderID int) error {
order, err := c.orders.GetByID(orderID)
if err != nil {
return err
}
if err := c.payments.Pay(order.Total()); err != nil {
return err
}
order.MarkPaid()
return c.orders.Save(order)
}
# Controller: takes the system event and hands out the work
class CheckoutController:
def __init__(self, orders: OrderRepository, payments: PaymentService) -> None:
self._orders = orders
self._payments = payments
def checkout(self, order_id: int) -> None:
order = self._orders.get_by_id(order_id)
self._payments.pay(order.total)
order.mark_paid()
self._orders.save(order)
// Controller: takes the system event and hands out the work
class CheckoutController {
constructor(
private readonly orders: OrderRepository,
private readonly payments: PaymentService,
) {}
checkout(orderId: number): void {
const order = this.orders.getById(orderId);
this.payments.pay(order.total);
order.markPaid();
this.orders.save(order);
}
}
// Controller: takes the system event and hands out the work
public class CheckoutController {
private final OrderRepository orders;
private final PaymentService payments;
public CheckoutController(OrderRepository orders, PaymentService payments) {
this.orders = orders;
this.payments = payments;
}
public void checkout(int orderId) {
Order order = orders.getById(orderId);
payments.pay(order.getTotal());
order.markPaid();
orders.save(order);
}
}
// Controller: takes the system event and hands out the work
class CheckoutController(
private val orders: OrderRepository,
private val payments: PaymentService,
) {
fun checkout(orderId: Int) {
val order = orders.getById(orderId)
payments.pay(order.total)
order.markPaid()
orders.save(order)
}
}
Note what happens here: the order computes the total, the payment service takes the money, and the controller only lines up the order of the steps.