Comments 27
Я хотел использовать minimal API, но у меня возникла проблема. GET методы с query параметрами я всегда спокойно преобразовывал в DTO запроса, примерно вот так
[HttpGet] public async Task<List<QuestionDto>> Search([FromQuery]string? search)
Как я понимаю minimal API так и не научился таким штукам? Вроде там было что-то с кастомными парсерами, но с ними проблема в том, что по ним не генерируется корректно swagger. А вы реально используете minimal API в проде? Как вы решаете эту проблему?
Думаю, можно использовать точно такой же подход. Лучше всего, мне кажется, обратить внимание на AsParametersAttribute.
app.MapGet("/", ([AsParameters] MyRequest request) => Results.Ok());
Вообще, передача массивов рекордов через строку запросов выглядит как нестандартная задача. Как это вообще организовано? Не могли бы вы привести QueryString для такой конечной точки?
Еще есть вариант с десеарилизацией
app.MapPost("/", async (HttpContext context) => {
if (context.Request.HasJsonContentType()) {
var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
У нас такой проблемы не было, т.к. изначально только жсончики бегали, но в закладках нашёл эту статью: https://andrewlock.net/behind-the-scenes-of-minimal-apis-3-exploring-the-model-binding-logic-of-minimal-apis/
И вот эту: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding
Пока детально не ковырял ни то, ни то, т.к. не было нужды.
А вы нашли какое-то решение под ваши задачи?
Появление Minimal API подвесило в воздух ощущение, что время контроллеров неумолимо истекает.
почему?
Удобнее оно как-то, нагляднее, стыкуется по синтаксису с некоторыми библиотеками под другие ЯП. Но, конечно же, это не значит, что контроллеры прямо вот всё, это просто частное мннние )). Но я новые проекты уже только на "минималках" начинаю.
А код у вас в апи остается или выносите? Статик или инжект?
Обычно реализуется clean architecture с CQRS, получается удобно.
Для не большого микросервиса норм. Но для проекта с 10 контроллерами, это превратиться бардак.
Что такое 10 контроллеров? Сколько это концов? Метод с CQRS, чаще реализуемый на базе MediatR, спокойно вытянет и 100 и 1000 обработчиков, и больше. Разницы нет, и читаемость проекта при этом ухудшается незначительно.
Тем не менее, использование MediatR ухудшает читаемость кода. Хотя бы потому, что возможно добиться аналогичной организации кода, руководствуясь банальным SOLID, при этом избежав проблемы поиска "а какой класс в проекте реализует вот тот дженерик интерфейс с таким то параметром", что особенно сильно проявляется на больших проектах.
Соглашусь, тут есть некоторые неудобства, как и с отладкой - надо знать, где точку останова ставить. Но, честно говоря, меня это как-то не сильно напрягает, всё дело в организации кода - когда по F12 на молели можно перейти в папку со всем скопом классов, поиск не вызывает трудностей. Попробуйте посмотреть пару-тройку примеров с clean architecture на гитхабе, там полно этого сейчас.
Влияет ли minimal api на производительность?
Про удобство: возьмем типичный не очень микро сервис с сотней-другой эндпоинтов, обвешанных атрибутами авторизации, кастомной сериализации, подсказками для OpenAPI и прочей часто встречающейся в энтерпрайз-разработке логикой. Перенос всего этого в один файл (или предполагается разбитие файла на секции?) значительно ухудшит и удобство, и наглядность. Даже при использовании максимально тонких контроллеров.
Пишите на контроллерах ))) Это вкусовщина, как мне кажется.
Конечно, меппинг бъётся на секции. А, вообще, я использую extension-метод, у меня проброс в MediatR происходит за скобками, а в самом меппинге только строка маршрута и DTO запроса. Правда, подгрузку файлов приходится штатно делать, но сколько там того.
OK, вы разделили маппинг на секции. Логически это чем-нибудь отличается от разделения экшенов по отдельным контроллерам? Принудительным использованием fluent builder вместо атрибутов?
На мой взгляд, принципиально выбор между контроллерами и "минималками" лежит в основном в плоскости согласования с конкретными потребностями и/или ограничениями проекта. Контроллеры чисто психологически воспринимаются как нечто более постоянное, устойчивое. Наверное, они могут быть предпочтительнее для крупных, сложных проектов, где количество endpoint-ов велико. В Minimal API очевидна выгода при быстрой разработке, когда количество конечных точек не превышает уровень памяти программииста.
Использование любого подхода или даже гибридной модели просто оставляет возможность выбора.
Про сравнение производительности minimal api vs controllers - увы, ничего не могу сказать. Даже не задумывался как-то, в уверенности, что раз технология более новая, то и более быстрая. А вопрос интересный.
А тут думать нечего, minimal api экономит ровно одну операцию GetRequiredService на запрос (благодаря тому что не требуется запрашивать контроллер) и чуть-чуть ускоряет запуск (благодаря тому что не требуется искать контроллеры в сборках).
Такая штука совершенно точно выглядит хорошо в соревновании фреймворков, но я сомневаюсь что эти факторы будут заметны в реальном сложном бекенде.
Влияет на производительность, потому что не нужно создавать экземпляр контроллера, а также потому что вы имеете возможность инжектить только то что нужно данному конкретному эндпоинту.
В случае с контроллерами вы будете резолвить все его зависимости даже если для какого-то эндпоинта все и не нужны.
А только меня напрягает такой код как в примере.
// involve custom userService for specific logic
v.ErrorIf(async m => await userService.IsUserExistAsync(m.Email),
"User already registered", m => m.Email);
Бегать в базу при валидации дорогое удовольствие (хотя бы потому, что после этого запросто может быть еще одна проверка, но уже без обращения к БД. И тогда будет просто трата ресурсов).
Тут вроде как речь про валидацию, а в примере проверка бизнес правила. Как мне кажется, стоит разделять валидацию и проверку бизнес правил. В результате, запрос, который не проходит валидацию, всегда не сможет ее пройти. А вот для бизнес правил результат проверки зависит от текущего состояния системы.
И такое разделение хорошо ложиться на CQRS: command и query валидируется (правлиа можно положить рядом с ними), а бизнес правила проверяются в соответствующих сервисах.
StealthDogg, этот код был написан в основном просто, чтобы продемонстрировать возможность использования асинхронных вызовов. async
и await
тоже были добавлены только для демонстрации асинхронности, можно писать и так -
ErrorIf(m => userService.IsUserExistAsync(m.Email),
m => $"Email {m.Email} already registered",
m => m.Email);
Соглашусь, в вашем замечании определённо присутствует рациональное зерно, у меня тоже были сомнения - вставлять нечто, что выглядит как вызов в базу, непосредственно в код самой валидации, кажется диким и режет глаз. Хотя ведь не факт же, что это именно вызов в базу, мало ли что там за сервис ))). Да, обычно такие проверки появляются уже в коде обработки запроса, после чего может следовать возврат NotFound404.
Вероятно, нужно было подыскать более рациональный пример, просто вот как-то не придумалось ничего достойного. Буду благодарен за возможные предложения.
А result тут - это что такое?
Это результат валидации, экземпляр класса FlatValidationResult
Валидация входных данных в фильтрах Minimal API .NET, просто и без затей