Как лучше всего обрабатывать выборку данных, необходимую для FluentValidation - PullRequest
0 голосов
/ 01 апреля 2020

В приложении, над которым я работаю, я использую Mediatr и его конвейеры для обработки взаимодействия с базой данных, некоторые мелкие бизнес-логи c, валидация и т. Д. c.

Есть несколько проверок для таких вещей, как контроль доступа, я могу обрабатывать в конвейере, так как я использую объект контекста, как описано здесь https://jimmybogard.com/sharing-context-in-mediatr-pipelines/ до go от ASP. Net идентичность с пользовательским объектом контекста с информацией о пользователях и претензиями.

Одна проблема, с которой я сталкиваюсь, заключается в том, что, поскольку это приложение является мультитенантным, мне нужно убедиться, что даже если объект существует, он принадлежит этому арендатору и является единственным способом быть уверенным в том, что нужно взять объект из базы данных и проверить его. Мне кажется, что проверка не должна иметь побочных эффектов, поэтому я не хочу полагаться на это при заполнении объекта контекста. Но затем это подталкивает кучу проверок к обработчикам Mediatr, когда они проверяют существование объекта, и так далее, что приводит к большому количеству повторяющегося кода. Я действительно не хочу запрашивать базу данных несколько раз, поскольку некоторые запросы могут быть дорогими.

Другая проблема с более сложной проверкой в ​​реальных обработчиках запросов заключается в том, чтобы получить то, что по сути является ошибками проверки. В настоящее время, если одна из этих проверок не проходит, я выбрасываю ValidationException, который затем перехватывается промежуточным ПО и превращается в ProblemDetails, который возвращается вызывающей стороне API. Это в основном исключения, как управление потоком, и ошибка проверки действительно не является «исключительной» в любом случае.

У меня есть мысли о том, как решить эту проблему:

  1. Где-то в конвейере, когда я создаю контекст, включаю попытку извлечения объектов нужно из базы данных. Проверка затем завершается неудачей, если любой из них имеет значение null. Кажется, что это усложнит тестирование, а также потребует как-то декорировать запросы (или использовать отражение), чтобы конвейер мог знать, пытаться загрузить эти объекты.

  2. Есть запросы в валидаторе, но используйте какое-то хранилище с поддержкой кэша, поэтому, когда тот же объект запрашивается позже, он подается из кэша, а не из базы данных. Обработчики также будут использовать этот репозиторий с поддержкой кэша (в настоящее время обработчики взаимодействуют напрямую с EF Core DbContext для запроса). Затем это добавляет проблему аннулирования кэша, которую я собираюсь решить в какой-то момент, во всяком случае (довольно мало элементов редко модифицируются). Для тестирования может быть введен фиктивный объект кеша, который на самом деле ничего не кеширует.

  3. Сделайте так, чтобы все ответы на запросы реализовывали интерфейс (или расширяли абстрактный класс), который имеет информацию проверки , общие флаги успеха, et c. Это может быть либо возвращено через API напрямую, либо иметь какой-то конвейер, который преобразует сбои в ProblemDetails. Это добавит некоторый шаблон к каждому ответу и обработчику, но позволит избежать исключений, таких как управление потоком, и проблем с кэшированием / отражением в других параметрах.

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

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

1 Ответ

0 голосов
/ 06 апреля 2020

Я не вижу ничего плохого в обработке проверки бизнес-логики c на уровне обработчика. Более того, я не думаю, что было бы правильно выбрасывать исключения для них, поскольку вы сказали, что это исключения в качестве управления потоком.

Введение кеша кажется излишним и для варианта использования. Наиболее разумным вариантом является третье ИМХО.

Вместо реализации интерфейса вы можете использовать классную библиотеку OneOf и иметь что-то вроде

    using HandlerResponse = OneOf<Success, NotFound, ValidationResponse>;

    public class MediatorHandler : IRequestHandler<Command, HandlerResponse>
    {
       public async Task<HandlerResponse> Handle(
        Command command,
        CancellationToken cancellationToken)
    {
        Resource resource = await _userRepository
            .GetResource(command.Id);

        if (resource is null)
            return new NotFound();

        if (!resource.IsValid)
            return new ValidationResponse(new ProblemDetails());

        return new Success();
    }

И затем отобразить ее в своем API-слое как

    public async Task<IActionResult> PostAsync([FromBody] DummyRequest request)
    {
        HandlerResponse response = await _mediator.Send(
            new Command(request.Id));

        return response.Match<IActionResult>(
            success => Created(),
            notFound => NotFound(),
            failed => new UnprocessableEntityResult(failed.ProblemDetails))
        );
    }
...