Как стать автором
Обновить
77
0
Вадим Мартынов @Vadimyan

Программист

Отправить сообщение

Я как-то очень по-разному могу понять этот вопрос.

  1. Использовать готовые методы для авторизации или документации не должно быть проблемой.

  2. Если вопрос к общим регистрациям, то исходная статья говорит об этом вот так: "Настройка общей инфраструктуры (например, логирование, аутентификация, мидлвары, swagger, …) приложения также остается в Program.cs, потому что она используется во всех модулях."
    Но я при этом понимаю, что общая инфра как бы усложнит большие рефакторинги типа вынесения модулей в отдельные сервисы, это как-то необходимое зло, кажется.

  3. Если речь о кастомных атрибутах (методах, если мы говорим о Minimal APIs и смотреть на статью, то как раз об этом автор оригинала в конце говорит "Следуя этой архитектуре, вы по-прежнему можете выделить слой Core/Domain и слой Infrastructure в разные проекты. Будете вы это делать или нет? Зависит от размера проекта, и, как мне кажется, было бы неплохо поговорить об этом, чтобы быть одной волне. Лично у меня нет однозначного мнения на этот счет."
    В моём представлении это всё — не доменный слой, а значит не обязательно думать об изоляции этого кода внутри модуля.

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

  1. Про feature folders в asp.net mvc писали ещё в 2013ом.

  2. Статья "Vertical slices in ASP.NET MVC" из 2016. И тоже без изоляции фич/модулей.

  3. Сложная настройка поиска вьюх по папкам в статье Feature Slices for ASP.NET Core MVC из MSDN Magazine. Тоже 2016 год.

  4. В "Организации по модулям, а не слоям" на мой взгляд не хватает изоляции и самодостаточности модулей, которая бы включала отделении регистрации доменнозависимых зависимостей внутри модуля.

Мне кажется, что идея как раз в том, чтобы применить идею модулей к Mininal APIs и минималистичному подходу к конфигурации приложения, который пришёл на смену Startup.cs. Если раньше модули приходилось реализовывать, прикладывая дополнительные усилия, то в современном .net это делать легче, чем соблюдать "традиционный" подход.

Мне кажется, что это вопрос организации кода и реализации. А код из статьи это просто пример.

  1. Можно держать модули в проектах сервисов или внешних проектах, а сервисы в одном репозитории/солюшене.
    В зависимости от способа публикации (будут ли попадать "лишние" сборки в папку приложения) тут может быть разный подход, но при реализации нужно будет помнить, что есть шанс выстрелить себе в ногу.
    А при локальном дебаге поведение может отличаться, если в этом случае все сборки складываются в одну папку.

  2. Если делить модули на проекты и сервисы на репозитории, то искать по всем сборкам логичнее и этот код авторегистрации нужно переписать.

Эта же проблема всё равно была в случае контроллеров — доменный слой в разных bounded context не должен перемешиваться. Просто контроллеры не подсвечивали явно разные контексты и могли наоборот запутать. Роут /users/{id}/orders/ же просится подумать, что User тут тот же самый, что и в /users/{id}, хотя в первом случае у нас статус покупателя, размер скидки и прочие атрибуты клиента, а во втором учетные данные и всякие статусы рассылки.

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

Мне не кажется, что тут есть однозначный ответ.

Можно смотреть на то, что говорит DDD по этому поводу. Между несколькими bounded context могут быть "одинаковые" сущности, которые отражают разные данные и разное представление об одном объекте реального мира, но могут иметь одинаковые свойства. В доменах они будут представлены разными типами и даже храниться в разных таблицах на уровне БД. В этом случае ответ понятный — разным доменам могут соответствовать разные контексты.

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

Наверное, самый подходящий ответ — зависит. Потому что вопрос тут в сценарии развития. В самой статье есть фраза "Настройка общей инфраструктуры (например, логирование, аутентификация, мидлвары, swagger, …) приложения также остается в Program.cs, потому что она используется во всех модулях". В этом смысле DbContext может быть общим в рамках всего приложения (и это даже не будет противоречить DDD потому что контекст БД это не слой домена).

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

На самом деле безконтроллерность же у нас была и раньше в разных проявлениях, а не только в "Minimal APIs"-like виде.

  1. Уже, кажется, не поддерживаемый Nancy, который мне кажется довольно похожим на FastEndpoints.

  2. Тот же Carter, на который есть ссылка в статье.

  3. Довольно необычный ServiceStack, в котором [Route] вообще вешается на модели.

FastEndpoints после Minimal APIs выглядит самым привычным из всех. При этом он всё ещё смешивает маршрутизацию и обратку в одном месте, а в каком-то идеализированном варианте я вижу их разделенными. Условно, это app.MapGet<TRequest, TResponse>("myRoute"), который умеет находить хендлер по переданным generic-параметрам. Но в таком случае мы опять возвращаемся к проблемам навигации, как с MediatR.

И .NET 7 на мой взгляд расширил эту концепцию с помощью тех же Route groups (и общего развития этого типа приложений). Но в разговорах о применимости minimal APIs я всё ещё довольно часто встречаю подход "Minimal APIs для экспериментов / микропроектов, для всего крупнее используйте контроллеры".

Идея изначальной статьи, кажется, в том, что стоит посмотреть на Minimal APIs как полноценную замену контроллеров. И это ещё и облегчит реализацию domain-driven подхода. В общем случае это касается в разной степени всех MVC-фреймворков.

Да, тут вышла путаница у меня с терминами из-за того, что в общем случае термин heapify применяется к процедуре, которая для конкретного элемента ставит его на нужное место чтобы куча стала в согласованное состояние:

> This function takes in an element index index, and maintains the min heap property, by swapping with the smallest element of its immediate sub-tree.

И как раз heapify имеет сложность в зависимости от высоты. Только вот в внутренней реализации .net метод Heapify производит операцию над каждым элементом, поэтому в итоге мы всё равно получим сложность равную сложности построения кучи.

А ещё Heapify в случае EnqueueRange будет работать только для пустой очереди, если там уже были элементы, то последовательно вызовется много Enqueue. В общем, метод получается скорее сахарный, чем оптимизирующий, таковым он будет только для сценария пустой кучи.

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

> Note that the type does not guarantee first-in-first-out semantics for elements of equal priority.

Да, мапперы это история скорее про организацию кода. И иногда про валидацию, например, благодаря AssertConfigurationIsValid. То есть, ручная конвертация будет в любом случае быстрее (или сопоставима по скорости в случае с кодогенерируемым маппингом).

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

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

  • (В немикросервисах) через пару лет поддержки можно обнаружить 3-4 метода конвертации одной и той же модельки в разных местах прото от того, что разработчик не нашёл существующий метод конвертвции.

  • Сценарий "добавил свойство, не протащил в конвертер" — это как раз то, что ловится в библиотеках маппинга валидацией.

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

На самом деле в стороннем бенчмарке из статьи есть сравнение как раз с ручным маппингом в методе. Мне, правда, это сравнение кажется не совсем честным — например, там повсеместно используется LINQ, что очевидно будет замедлять код. Собственно, ручной маппинг там на 2-3 месте обычно. Без LINQ и на моих модельках результаты немного другие, Mapster вдвое медленнее на комплексных типах и чуть медленнее на простых.

У Mapster есть аналогичная ProjectTo поддержка EF какр раз через вскольз упомянутый ProjectToType, который работает с IQueryable. Можно попробовать сравнить. Тут вопрос, будет ли, например, хорошим сравнением использованием InMemory или хочется именно данных на реальной "железной" БД? И нужно, конечно, сравнивать не "очевидные" сценарии, где маппинг явный.

Про идемпотентность — да, есть решения с отдельным идентификатором типа requestId/operationId, которые требуют дополнительного механизма, и иногда могут усложнять композитную операцию, которую можно перевыполнить частично. В общем случае такой отдельный идентификатор полезен хотя бы потому, что не во всех запросах могут быть реальные идентификаторы, при этом такой подход ведь не исключает поддержку возможности повтора на уровне БД, когда Upsert должен корректно сработать при повторе вставки.

Про SQL insert — видимо, я тут как-то неточно сформулировал предложение. Вставка не усложниться, клиент, если надо, попросит, проблема тут в том, что клиенту обычно надо и он обычно просит, на столько, что ORM типа EF Core это "попросит" включает в запрос вставки, сразу делая insert + select. И в случае с микроорм и абстркатным REST клиент ожидает на вставку или сущность с идентификатором целиком, или сам идентификатор.

С последней частью статьи как-то хотелось подвести к тому, что решение нужно, библиотеки сущетсвуют, но вот нет сейчас чего-то "стандартного" из коробки или почти из коробки где-нибудь в либах EF Core, в BCL хотя бы известной community-библиотеки про RFC 4122. Чаще всего задачу решают ULIDом (для которого есть несколького надежных либ), но, учитывая некоторую хитрую "частично лексикографическую" сортировку ключей в SQL Server, это возможно только с сменой типа данных для хранения, что может оказаться очень значительным переходом.

Изначально я смотрел именно на NewId и надеялся проверить результаты на MySQL как более распространенную (субъективно по моим ощущениям) БД, но так и не нашёл нужного инструмента под неё, зато решил поискать другие реализации для генерации UUID version 1 и был сильно озадачен небольшим выбором.

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

Это же касается идемпотентности — если операция "атомарная" в том плане, что клиент сервиса классов посылает в метод создания список учеников, то он вполне и сгенерирует идентификаторы, а сервис классов вполне обработает в таком случае повторный запрос. А вот при генерации на стороне БД будет больно.

В случае с вычиткой я упоминал в статье бенчмарк, который показывает, что вычитка из разрозненного набора страниц не такая большая проблема (подозреваю, что "в некоторых сценариях" типа hdd или отчаяной нехватки оперативной памяти и необходимости чтения с диска это будет не так), но в общем случае я согласен с проблемой, что надо очень хорошо понимать не только индекс динамичности, но и профили запросов — потому что выстрелить себе в ногу в хранилищах проще простого.

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

Да, всё так, что-то пошло не так и статус стандарта сейчас вот такой:

C# VERSION    ECMA STANDARD   ISO/IEC STANDARD
V1.0          ECMA-334:2003   ISO/IEC 23270:2003
V2.0          ECMA-334:2006   ISO/IEC 23270:2006
V3.0          none            none
V4.0          none            none
V5.0          ECMA-334:2017   ISO/IEC 23270:2018
V6.0          TBD             TBD
V7.0          TBD             TBD

Даже шестая версия считается черновиком.

С атрибутом для main — это скорее конфуз про "забыли частный случай, когда это нужно". Примерно такая же история, кстати, с переносом строк в выражениях интерполяции, они просто просматривались при парсинге выражения как часть строкового литерала, это скорее исправление недоработки, чем новая фича.

Под пользовательским кодом я имел в виду код конечного приложения, условная бизнес-логика как противопоставление инфраструктурному коду, где оптимизации уровня "делегат не кэшируется, поэтому перепишем менее кратко или менее понятно, зато быстрее" или "избавимся от замыкания" важны. Те же Span<T> могут быть не так актуальны в каждом месте клиентского кода, а для поиска в словаре ключа, который является подстрокой полного имени машины, если это часть кода ServiceDiscovery и используется на каждом запросе — вполне.

Если те же params ReadOnlySpan<T> помогут забустить логирование для случаев с string.Format — отлично же будет. Надо смотреть бенчмарки будущих версий того же asp.net и других либ чтобы оценить значимость изменений.

Это "иногда" касается по большей части пользовательского кода — во внутренней реализации утилитарных библиотек это может использоваться довольно часто. Например, при работе с вебом utf8-литералы заменят большое количество конвертаций в рантайме, а изменения для generic math улучшат разработку новых типов, связанных с математикой.

Некоторые "иногда" значат, что прямо сейчас мало сценариев использования, но за счёт новых фич их может появиться больше. А другие "иногда" и "редко" — это про "очень часто в своей узкой нише", как полуавтоматические свойства для WPF.

При этом исходная простота языка не ломается — в пользовательском коде можно совсем не использовать эти фичи и не знать о них, зато при необходимости есть дополнительная гибкость.

Если речь о новых мажорных версиях библиотеки (а делать такое изменение в рамках текущей мажорной версии немного странно) — то тут breaking changes могут служить усилением безопасности. Для первичных конструкторов с большой вероятностью типы и так были спроектированы для использования конструктора базового типа (как в DbContext с options).

А вот обязательные члены меняют принцип инициализации объекта, ещё и требуют нужной области видимости, сейчас даже непонятно, как их хорошо встроить в контейнеры, чтобы те искали и инициализировали required -члены так же из контейнера. Мне сейчас кажется, что required — это больше история про модели и их безопасное использование, так что любой breaking change будет подсвечивать существующую в коде проблему инициализации. И учитывая движение к неизменяемым объектам (с помощью того же init) код уже готов к этому, а init-only свойства достаточно безопасно помечать required.

Это правда, хотя часть из описаных фич уже доставлена в C# 11 preview и много находятся в разработке или ревью уже сейчас. С большой вероятностью большая часть из описаного успеется к выходу C# 11 или по крайней мере не будут выброшены совсем.

Но при этом для некоторых фич всё ещё идут обсуждения дизайна и конечный вариант реализации может поменяться. Например, мне кажется, что params IEnumerable<T> всё-таки не завезут из-за избыточности, а модификатор уровня доступа для области видимости в пределах файла вообще болтает — то ли он будет private, то ли всё-таки file, а ещё там в комментах сразу думают над модификатором уровня доступа в пределах неймспейса.

В той же папке с пропозалами есть не только те, что сейчас в работе, но и отложенные/ожидающие — у них вероятность доехать до будущих версий гораздо ниже, я даже примером readonly locals в статью вынес, но ещё там есть в чём покопаться и о чём рассказать как о потенциальных направлениях развития языка.

Информация

В рейтинге
Не участвует
Откуда
Ростов-на-Дону, Ростовская обл., Россия
Работает в
Дата рождения
Зарегистрирован
Активность

Специализация

Backend Developer, Community manager
Lead
C#
.NET Core
Database
High-loaded systems
Designing application architecture
Database design