Логотип Workflow

Article

Updated at:

Stage 5: DRY Principle

Этап 5 - Принцип DRY

DRY означает Do not Repeat Yourself, но главное слово здесь не repeat. Главное слово - knowledge. Две похожие строки кода не всегда проблема. Два места, где записано одно и то же бизнес-правило, - проблема, потому что одно место однажды изменят, а второе забудут.

Этап 5 - Принцип DRY

Почему это важно

Architecture в backend-разработке отвечает за то, где код имеет право жить и что он имеет право знать. Без архитектуры каждая feature превращается в прямую дорожку от HTTP к database, от database к external service и обратно. Для маленького demo это может работать, но в реальном продукте такой shortcut создает хрупкий код. Изменение database протекает в API. Validation rule появляется в трех services. Transaction начинается не там. Class невозможно протестировать без запуска всего application.

Хорошая архитектура нужна не для красивых diagrams. Она нужна, чтобы уменьшить стоимость изменений. Developer должен уметь добавить field, изменить rule, заменить external provider или протестировать use case без переписывания unrelated parts системы. Design должен делать нормальный путь очевидным, а опасный путь трудным.

Как об этом думать

Начинайте с ответственности. Спросите, какое решение принадлежит конкретному коду. Controller отвечает за HTTP details. Use case отвечает за application flow. Repository отвечает за persistence access. Mapper отвечает за data conversion. Transaction boundary отвечает за atomicity. Когда эти ответственности смешаны, bugs сложнее найти, потому что один class делает несколько работ по нескольким причинам.

Потом смотрите на направление зависимостей. Dependencies обычно должны идти от внешних деталей к устойчивым внутренним правилам или от high-level policy к заменяемым details через interfaces. Если domain rule импортирует web controller, направление неправильное. Если service создает HTTP client прямо внутри method, dependency скрыта. Если mapper случайно загружает lazy relations из database, boundary протекает.

Конкретный пример

@Service
public class CreateOrderUseCase {
  private final OrderRepository orders;
  private final PaymentPort payments;

  @Transactional
  public OrderResult create(CreateOrderCommand command) {
    Order order = Order.create(command.customerId(), command.items());
    payments.reserve(order.total());
    return OrderResult.from(orders.save(order));
  }
}

Пример маленький, но в нем видны архитектурные решения. Use case получает dependencies, а не создает их сам. Payment system представлен port, а не конкретным SDK class. Transaction boundary окружает business operation. API layer может превратить request DTO в CreateOrderCommand до вызова этого кода, а response DTO можно собрать после возврата результата.

Полезная таблица

ПонятиеЗначение
ControllerHTTP input и output
Use caseApplication flow
RepositoryPersistence access
BoundaryГраница ответственности

Частые ошибки

  • Считать папки архитектурой, хотя dependencies все еще указывают куда угодно.
  • Добавлять abstractions до реальной причины менять implementation.
  • Оставлять business rules в controllers, потому что сегодня так быстрее.
  • Возвращать entities из API endpoints и называть это простотой.
  • Ставить transaction boundaries вокруг helper methods вместо use cases.

Чеклист понимания

  • Я могу объяснить, какой layer или boundary владеет решением в этой теме.
  • Я понимаю, какие dependencies должны смотреть внутрь, а какие оставаться снаружи.
  • Я могу назвать конкретный bug, который предотвращает такой design.
  • Я могу понять, когда principle используют чрезмерно.

Практика перед следующим уроком

Возьмите маленькую feature создания заказа и нарисуйте, где должны жить controller, DTO, mapper, use case, repository, external payment adapter и transaction boundary. Затем отметьте одну dependency, которая была бы опасной, если направить ее в обратную сторону.

Авторизуйтесь чтоб пройти тесты

Practice

Интерактивная практика

Выполните задания и сразу проверьте ответ.