Как пользоваться репозиторием

  • Создайте fork репозитория
  • Начните с ветки step-1, прочитайте пункт 1 этого readme
  • Прочтиайте второй пункт этого readme. Обратите внимание на подпункты "задача" и "что делаем"
  • Переключитесь на ветку step-2, посмотрите на результат рефакторинга
  • Вернитесь на ветку step-1 и проведите рефакторинг
  • Повторите процесс для веток step-2 - step-9
  • Каждый следующий шаг более сложен и дорог в реализации. В реальных проектах используйте инструменты там, где действительно необходимо.

1. Начало: логика в контроллерах или сервисах

Ссылки

Плюсы

  • Просто
  • Лучшая возможная производительность

Минусы

  • Затраты на поддержку растут с количеством кода; по закону брукса разработка в какой-то момент остановится

2. Вертикальные слайсы / CQRS

Мотивация

2.1 Логика в контроллерах

  • нарушение SRP
  • смешение бизнес логики и инфраструктурного кода
  • затруднено повторное использование

2.2. В сервисах

  • Services, Helpers, Managers, ... повышается связность по мере роста проекта, в итоге имеется тенденция к Big Ball of Mud
  • Cross-cutting concerns
  • Затруднен Feature Toggle и рефакторинг
  • Конфликты при мердже, не соблуюдается принцип OCP
  • Потеря контекста при навигации по файлам

Задача

  • Реализовать вертикальные слайсы без использования внешних библиотек, вроде MediatR или SimpleInjector
  • Или применить одну из них :)

Что делаем

  • Создать папку Features
  • Внтури создать папку на каждую "фичу"
  • Разложить DTO, Handler, Controller по фича-папкам
  • Использовать базовый класс GetEnumerableQueryHandlerBase для стека чтения
  • Автоматически вызывать SaveChanges в стеке записи

Ссылки

Доклады

Статьи

Библиотеки

Актуальные техники

Плюсы

  • Меньше конфликтов
  • Больше добавления и удаления кода, меньше редактирования
  • Базовые хендлеры, меньше бойлерплейта
  • Обобщенная обработка Cross-cutting concerns

Минусы

  • Больше инфраструктурного кода
  • Непривычно

3. Модули

Мотивация

  • Циклические зависимости
  • Когнитивная сложность
  • Затруднен рефакторинг
  • Время компиляции, запуска
  • Убрать явную зависимость от конкретной ORM

Задача

Разделить проект на модули (отдельные сборки)

Что делаем

  • Создать сборку на для каждого модуля
  • Перенести фича-папки в соответствущие сборки

Ссылки

Плюсы

  • Зависимости инвертированы, нет циклических зависимостей
  • Нет зависимостей от конкретной ORM в домене
  • Видны зависимости между модулями

Минусы

  • Больше абстраций
  • Больше инфраструктурного кода
  • Нужны Domain Events

4. Rich Domain Model

Мотивация

Задача

Что делаем

  • Ипользовать базоый класс EntityBase
  • Создать специализированные конструкторы, соблюдающие инварианты
  • Изменить модификаторы доступа свойств на public get; protected set;
  • Создать публичные методы вместо публичных property setters
  • Создать спецификации
  • Удалить foreign key <FK>Id properties

Ссылки

Плюсы

  • Защита от NRE
  • Соблюдение инвариантов
  • Лучшая читаемость кода
  • Следование принципам ООП

Минусы

  • Возможное дублирование валидации в конструкторах и в валидаторах
  • Ухудшение производительности
  • Сложность моделирования
  • Куда мне класть этот метод?

5. Контексты операций

Мотивация

  • Дублирование кода в валидаторах/контроллерах
  • Повторные загрузки сущностей
  • Создание обобщенных валидаторов

Задача

  • Использовать фабрику контекстов
  • Использовать обобщенные валидаторы

Что делаем

  • Добавить в параметры методов контроллеров Func<T, TObjectContext<T>>
  • Использовать фабрику для инициализации контекстов операций
  • Добавить DataAnnotation атрибуты на обязательные поля
  • Использовать DataAnnotationValidator по-умолчанию

Плюсы

  • Нет повторных чтений из БД
  • Обобщенная валидация

Минусы

6. State

Мотивация

Задача

  • Объединить паттерн State и идеи функционального программирования
  • Запрограммировать конечный автомат

Что делаем

  • Создать класс на каждое состояние заказа, распределеить методы между классами-состояниями
  • Использовать контекст для валидации

Ссылки

Плюсы

  • Компилируется - значит работает
  • Переходы между состояниями и правила - явные

Минусы

  • Смешение ответственности между валидаторами и State

7. Domain Services

Мотивация

Задача

  • Перенести работу с IO в отдельный слой
  • Не потерять преимущества обобщенных интерфейсов, но сохранить холистические абстракции

Что делаем

  • Использовать IDomainHandler в качестве Domain Service
  • Использовать CommandResult для распределенных операций, которые могут завершиться ошибкой

Ссылки

Плюсы

  • Более формальные правила структурирования кода
  • Упрощение тестирования

Минусы

  • Фрагментация логики

8. Domain Events

Мотивация

  • Отделение мух от котлет основного процесса от побочных эффектов
  • Коммуникация между модулями
  • Упрощение перехода к шине данных и микросервисам
  • Аудит лог

Задача

  • Добавить обработку событий в пайплайн

Что делаем

  • Запрограммировать диспетчер
  • Добавить шаг с обработкой событий в пайплайн

Ссылки

Плюсы

  • Слабая связанность

Минусы

  • Неявность
  • Управление транзакциями
  • События в обработчиках событий
  • Синхронные и асинхронные обработчики

9. Массовые операции

Мотивация

  • Производительность: импорт, экспорт, массовое обновление, денормализация

Задача

  • Реализовать массовую операцию с помощью Bulk Extensions

Что делаем

  • Добавить публикацию события в модуле
  • В другом модуле подписаться на событие
  • Выполнить массовую операцию в обработчике события

Ссылки

Плюсы

  • Не нужно писать хранимые процедуры

Минусы