Как реализовать принцип единой ответственности сервиса
Что проверяют
Понимаете ли вы SRP глубже, чем «класс делает одно дело», и умеете ли применить его на уровне сервиса, а не только класса. Следом обычно спрашивают, как понять, что сервис пора делить, и где граница, после которой дробление вредит.
Как отвечать
Точная формулировка: у модуля одна причина для изменения. Роберт Мартин позже уточнил её так: модуль отвечает перед одним актором, то есть одной группой людей, чьи требования его меняют. «Делать одно дело» он относит к функциям, а не к классам.
Как это выглядит в Spring-сервисе:
- признаки нарушения:
OrderServiceна тысячи строк, конструктор с восемью зависимостями, методы на непересекающихся наборах полей, правки от бухгалтерии и от маркетинга в одном файле, тесты с десятком моков; - разделение по причинам изменений: расчёт цены, сохранение, уведомления и документы уходят в отдельные компоненты, а сервис заказа остаётся координатором сценария. Оркестрация тоже ответственность, и это нормально;
- развязка через события: вместо прямого вызова
NotificationServiceпубликуется событие черезApplicationEventPublisher, а слушатель с@TransactionalEventListenerпо умолчанию срабатывает после коммита. Письмо не уйдёт по заказу, транзакция которого откатилась; - уровень микросервиса: сервис владеет одной бизнес-возможностью и своими данными. Делят по бизнес-границам (заказы, оплата, каталог), а не по техническим слоям.
Где спотыкаются
Трактуют SRP как «один метод на класс» и плодят UserCreator, UserUpdater, UserDeleter. Связанная логика размазывается, а сервисы становятся транзитными обёртками друг над другом.
Делят микросервисы по таблицам: сервис пользователей, сервис адресов, сервис телефонов. Любой сценарий превращается в цепочку сетевых вызовов и распределённую транзакцию, получается распределённый монолит.
Выносят уведомление в отдельный класс, но вызывают его внутри транзакции. Письмо уходит, транзакция потом откатывается, и пользователь получает приветствие от аккаунта, которого нет.