Course program← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF
7Методические материалы

Controller

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.

User Controller Domain form, HTTP, CLI checks, parsing order, payment raw data valid command the error goes out, the domain never sees it

«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.