Предыдущий пост об архитектуре получил несколько хороших вопросов, поэтому я решил разобрать их подробнее в отдельной публикации.
Краткое повторение
Вспомним основные требования:
- Приложение должно быть легко покрывать тестами.
- Приложение должно быть легко обновлять.
- Код должен быть простым.
- Поставщика данных должно быть легко заменить.
- Не стоит использовать библиотеки, которые влияют на общую архитектуру.
Эти требования ориентированы на практическое применение. Цель не в том, чтобы строго следовать 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 явный и независимый.