Управление транзакциями Entity Framework Core при обработке команд

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

Реализация конвейера для транзакций имеет некоторые ключевые преимущества даже при использовании Entity Framework Core или другой ORM, которая отслеживает и сбрасывает изменения в один момент времени.

Поскольку обработчик команд выполняется внутри неявной транзакции, разработчик может сбросить свои изменения по своему усмотрению, зная, что любое исключение откатит изменения. Это очень полезно, когда требуется отменить поток, когда некоторые бизнес-правила не соблюдаются и могут быть проверены только после изменения состояния системы. Оптимистичный параллелизм - хороший пример, когда вы можете захотеть сбросить свои изменения перед выходом из обработчика, чтобы вы точно знали, какая сущность завершилась сбоем, и могли отправить более подробное сообщение пользователю, что является обычным подходом при реализации выделенного Backend для Сервис Frontend.

Кроме того, ORM очень хорошо абстрагируют доступ к базе данных, но не могут решить больше крайних случаев, особенно когда требуется производительность. Возможность использовать более легкую библиотеку (например, Dapper) для выполнения некоторых массовых операций и использование соединения контекста ORM внутри одной транзакции устранит необходимость использования TransactionScope и снизит некоторую сложность приложения.

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

Проэкт

Эта статья будет основана на моих предыдущих статьях, в которых объяснялось, как использовать посредник и реализовать трансверсальное поведение с конвейерами. Напоминаем, что мы реализовали конечную точку для управления продуктами со следующим:

  • GET / products - поиск товаров с помощью фильтров (SearchProductsQuery);
  • GET / products / {id} - получить товар по его уникальному идентификатору (GetProductByIdQuery);
  • POST / products - создать товар (CreateProductCommand и CreatedProductEvent);
  • PUT / products / {id} - обновить товар по его уникальному идентификатору (UpdateProductCommand и UpdatedProductEvent);
  • УДАЛИТЬ / products / {id} - удалить товар по его уникальному идентификатору (DeleteProductCommand и DeletedProductEvent);

Вы можете проверить их здесь:







Исходный код доступен на GitHub, не стесняйтесь его посмотреть.

Entity Framework Core транзакции

Entity Framework Core поддерживает явное управление транзакциями, которое работает аналогично ADO.NET или классу TransactionScope.

Вы можете получить одноразовый экземпляр IDbContextTransaction, вызвав метод BeginTransactionAsync, доступный из свойства базы данных. Транзакция может быть либо зафиксирована, либо откатана либо путем явного вызова, либо путем удаления экземпляра перед любой фиксацией.

Как указывалось ранее, это может быть очень полезно, если SaveChanges необходимо вызывать несколько раз внутри операции unit of work и вы хотите, чтобы исключения откатили изменения данных.

В приведенном выше примере, если после первого SaveChangesAsync было сгенерировано исключение, все изменения будут отменены, потому что транзакция будет удалена без явного вызова фиксации.

Мы собираемся использовать это поведение для реализации конвейера.

Трубопровод

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

Поведение конвейера будет:

  1. Перехватить любую команду;
  2. Создайте новый IDbContextTransaction с BeginTransactionAsync;
  3. Вызвать следующий конвейер внутри using области;
  4. Если исключение не генерируется, сбросить все изменения и зафиксировать;
  5. Осуществить сделку;

Я также добавлю код с комментариями, если вы хотите, чтобы запросы никогда не изменяли состояние системы. Профилактика лучше лечения…

Внутри папки Pipelines создайте класс TransactionPipeline, расширяющий Pipeline (потому что нам нужны только некоторые методы):

Откройте файл Startup.cs и добавьте TransactionPipeline после ValidationPipeline, чтобы предотвратить открытие транзакций, которые можно было бы немедленно закрыть, если в команде были недопустимые данные.

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

Теперь, когда у нас есть сквозное поведение, которое управляет транзакциями и сбрасывает все изменения контекста в базу данных, мы можем удалить из всех обработчиков команд явные вызовы SaveChangesAsync:

Ускорение!

Поскольку это конвейер, который мы очень часто используем в большинстве наших API, уже доступен NuGet, который позволяет открывать явную транзакцию Entity Framework Core для команд, событий или запросов, в зависимости от ваших потребностей.



Чтобы использовать его, просто откройте диспетчер пакетов NuGet и установите пакет SimpleSoft.Mediator.Microsoft.Extensions.EFCoreTransactionPipeline:

Откройте файл Startup.cs и зарегистрируйте конвейер с помощью метода расширения AddPipelineForEFCoreTransaction<TContext>. Явные транзакции по умолчанию отключены, поэтому вам нужно включить их только для команд.

При использовании всех NuGet, упомянутых в моих предыдущих статьях, файл Startup.cs должен быть похож на этот:

Заключение

Я надеюсь, что эта статья дала вам хорошее представление о том, как использовать конвейеры-посредники для упрощения управления транзакциями Entity Framework Core, сделав реализацию обработчиков команд менее подверженной ошибкам, поскольку разработчики не забудут сбросить изменения в базу данных (такое поведение будет быть неявным, если не возникло исключение).

Даже если ваше приложение использует другой ORM или другие облегченные библиотеки (например, Dapper), вы можете легко реализовать свой собственный конвейер.

Я также написал статью об аудите действий пользователей с помощью конвейеров, вы также можете найти ее полезной: