SoC и SOLID на практике
Через несколько месяцев расчёт заказа уже не игрушечный. Маркетинг регулярно меняет скидки, логистика подключает перевозчиков, финансы требуют единое округление. Теперь разделение ответственности опирается на факты, а не на предчувствие - и границы можно провести уверенно.
function calculateOrder(order, customer, deliveryPolicy) {
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, customer);
const delivery = deliveryPolicy.calculate({
subtotal,
address: order.address,
});
return roundMoney(subtotal - discount + delivery);
}Мы не обязаны превращать каждую часть в класс или микросервис - для начала достаточно модулей с явными функциями. Это сохраняет простоту и одновременно проводит полезные границы. Где здесь SOLID: функция расчёта отвечает только за порядок вычисления; политика скидок меняется отдельно; доставка получает небольшой договор deliveryPolicy, а не весь объект приложения; конкретного перевозчика можно заменить, не переписывая расчёт. Где SOLID был бы лишним: если создать по интерфейсу и фабрике для каждой арифметической операции, кода станет больше, а независимых изменений не прибавится - это уже архитектура ради архитектуры.
Какая часть меняется по какой причине и какая граница ей подходит - удобно держать в таблице.
Часть · Причина изменения · Подходящая граница
- Скидка - Маркетинговая политика; Отдельная чистая функция или модуль
- Доставка - Перевозчик, регион и тариф; Небольшой договор deliveryPolicy
- Округление - Единое финансовое правило; Один общий источник roundMoney
- Сценарий заказа - Порядок бизнес-операций; Функция orchestration без деталей интеграции
Границы появляются из опыта, а не из шаблона. Те же самые разделения, навязанные с первого дня, были бы преждевременной архитектурой. Здесь они оправданы, потому что у частей уже появились разные владельцы и разные темпы изменений.
Все шесть принципов проявились на одной функции. Осталось собрать их в один порядок принятия решения.