← Назад ко всем статьям

Архитектура, которую я использую в проектах: продолжение

Опубликовано

Предыдущий пост об архитектуре получил несколько хороших вопросов, поэтому я решил разобрать их подробнее в отдельной публикации.

Краткое повторение

Вспомним основные требования:

  • Приложение должно быть легко покрывать тестами.
  • Приложение должно быть легко обновлять.
  • Код должен быть простым.
  • Поставщика данных должно быть легко заменить.
  • Не стоит использовать библиотеки, которые влияют на общую архитектуру.

Эти требования ориентированы на практическое применение. Цель не в том, чтобы строго следовать DDD, CQRS, Event Sourcing и так далее.

Почему я избегаю внешних библиотек, которые глубоко проникают в архитектуру

Использование внешних библиотек всегда несет риски. Платные библиотеки могут заметно увеличить стоимость поддержки. Автор open-source библиотеки может потерять мотивацию поддерживать ее, добавить вредоносный код или изменить лицензию. Библиотеку можно форкнуть, но тогда нагрузка по ее поддержке перейдет к вам.

Если библиотека используется только в ограниченной части приложения, это обычно нормально. Проблема начинается, когда библиотека плотно интегрирована в общую архитектуру.

Такие библиотеки часто берут, чтобы сократить boilerplate, например через reflection. Но меньше строк кода не означает лучший design. Современные IDE уже дают отличное автодополнение и навигацию. Отказываясь от глубоких framework libraries, мы получаем сильные преимущества: независимость проекта, более простую отладку и более понятную навигацию по коду.

Почему я предпочитаю use-case классы вместо больших сервисов

Частый вопрос: зачем создавать отдельный use-case class для каждого действия вместо одного сервиса с несколькими методами. Хотя большой сервис сначала может казаться удобным, со временем он часто превращается в огромный класс на 1000+ строк с десятками методов и зависимостей. Это сильно усложняет тестирование и понимание поведения.

Один use case на одно действие помогает сохранять single responsibility, меньший граф зависимостей и более простые тесты.

Можно ли использовать базовые классы для use cases или repositories?

Я предпочитаю этого не делать. Наследование от базовых классов может усложнить вынос функциональности в отдельный сервис или microservice. Оно часто навязывает конкретное поведение и увеличивает coupling.

Когда возможно, лучше использовать небольшие явные interfaces и composition вместо inheritance.

EF и repository pattern

Некоторые команды считают, что использование EF (Entity Framework) убирает необходимость в дополнительной абстракции, потому что он уже скрывает базу данных. На практике это создает сильную зависимость от ORM.

В моей архитектуре все взаимодействие с EF находится внутри Infrastructure layer, в реализации каждого repository. Я избегаю общего base repository: каждый repository явный и независимый.