Comments 17
В статье об этом есть - использовал генераторы не заметил тормозов. Инкрементальные пока не смотрел.
В статье об этом есть - хочу по доменным сущностям все сделать. При этом хочу чтобы сгенерированный код лежал где надо (в Infrastructure.Web, например. А атрибуты для аннотации в Domain.Common). Где вы тут антипаттерн нашли(и как он называется)? xD
Если что - я вот пару четыре лет назад исследовал кодогенерацию через source generator - https://habr.com/ru/articles/542300/
По итогу эта штука ужасно тормозит при малейшей навигации по файлам и глючная, зайти в отладку тяжело.
Поэтому сейчас бы я пошел другим путем, если нужна именно кодо-генерация - отдельная консольная утилита, которой через конфиг указываешь что и где брать и куда складывать и запускать тупо на папке с проектом.
Можно не регистрировать обработчики изменений синтаксиса.
context.RegisterForSyntaxNotifications(()=>new AttributeSyntaxReceiver<GenerateServiceAttribute>());
А просто вызывать генерацию (написав все нужное в Generate)
Такая генерация запускается при сборке\пересборке.
Даже 10к строк генерируется за секунды.
Зайти в отладку просто:
public void Initialize(GeneratorInitializationContext context)
{
#if DEBUG
//if (!Debugger.IsAttached)
//{
// Debugger.Launch();
//}
#endif
}
Уберите комментарий и отлаживайте кодогенератор как и любой другой код на C#.
Было бы славно еще освоить гит и публиковать код нормально, а не архивом, загруженным в репозиторий.
Жду продолжения, был как то план сделать подобное, но руки не дошли, интересно увидеть чужой результат.
П.с. а все же сделать "правильное подключение" - генератор в клиентские проекты (в домен, аппликейшен, веб) вместо их перечисления в генераторе не выйдет?
Я себе это представляю так (не факт что это сработает):
Генератор сканирует веб сборку, в ней явного ничего нет (атрибутов и т.д.), но есть референсы на другие сборки, по ним прыгает до домена
В домене находит свои атрибуты (на доменных объектах)
По доменным объектам генерирует что нужно для веб (контроллеры) и пишет их в код базу веб проекта.
Есть минусы - точка запуска генератора для всех проектов единая, т.е. понять для какого проекта сейчас запущена генерация и куда бежать искать классы с атрибутами, да еще и какой код для текущей сборки надо генерировать - запарно, в целом решается через какие-нибудь общие маркеры (тип проекта/фреймворка/подключенные зависимости/шаблоны наименования проекта)
Тогда все генераторы надо подключать к доменной сборке.
Хотелось хотя-бы немного по проектам разнести (чтобы в доменной сборке не падать от тысяча и одного класса).
Да и сделать не только запуск в зависимости от сборки, но и добавить очередность.
Ненене, наоборот, генераторы подключать к конечным сборкам (веб, аппликейшен), ведь они имеют доступ к домену, а не наоборот. И классы соответственно будут сгенерированы в этих сборках
Уже смотрел, да, имеют доступ к домену, все что можно получить это IAssemblySymbol.
И тут нет синтаксических деревьев.
Тогда зачем эти атрибуты на доменах. Берете Web API, подключаете к нему генератор. Даёте ссылку на проект домена. Все, дальше конвенционально парсите aggregate, entities, value objects, domain exceptions и вуаля web API готов. Т.е. комбинация проект + генератор + ссылки на проекты домена = то, как должен домен быть представлен во внешнем мире. Тот вариант: консольное приложение + генератор воркера + проект домена = проект для асинхронной обработки сообщений из домена (например для интеграции с внешними сервисами).
Если надо что-то подкорректировать (задать base url, настроить конвенции) подкидываете специальный конфиг в формате JSON (как nswag делает) ну или на C# объявляете его прямо в коде (типа как MappingGenerator), не важно.
Таким образом, когда меняется путь эндпойнта, правила парсинга (вчера передавали параметры в query string, а сегодня добавили поддержку body) и ТД, вы не трогаете домен. В конце концов какая разница бизнесу, какой у вас там путь к эндпойнту (только если конечно ваш бизнес не Apigee, но даже так можно сгенерировать management tools для управления доменом: эндпойнт для добавления эндпойнта :) )
В конце концов какая разница бизнесу, какой у вас там путь к эндпойнту (только если конечно ваш бизнес не Apigee
Apigee :)
Берете Web API, подключаете к нему генератор
Какое веб-апи?
Я объектом на едином языке в рамках домена задаю требование заказчика - конечная точка WebApi. Тут многие привыкли видеть или документ (объект), или документы и какие-то правила их валидации. Но задание просто конечной точки - тоже вполне валидный вариант (потому что это - основа проекта, и без WebApi никакого сбора данных из 100 источников не будет. И замены WebApi на шины тоже не будет).
Все остальное (DTO, код в Infrastructure и application) генерируется.
Да, задание объектом было бы логичнее, но менее удобно для статьи (мне не нужно через Web конструктор добавлять endpoint-ы, они добавляются раз в неделю\месяц. Задание Api динамически объектами - скорее всего очень много лишней писанины). Да и вообще статья о кодогенерации ).
Как я буду разделять конфигурации (какие воркеры запускать, какое API поднимать) - еще не думал. Думаю через атрибуты :).
Конфигурировать скорее всего буду стандартно через https://learn.microsoft.com/ru-ru/dotnet/core/extensions/configuration.
Классом.
Задание объектами как в Apigee было бы логичнее, но слишком муторно в плане бойлерплейта и не нужно.
Уже понятнее. В какой-то момент мы с Вами более менее синхронизировались. Далее уже надо определиться, что будет лучше - динамическая конфигурация или статика. Ответ: зависит. Но если предположить, что в Вашем кейсе статика лучше, то да, все выглядит логично. Правда это не раскрыто в статье, поэтому жёсткий диссонанс вызывает у практикующих DDD. Всё-таки обычно DDD реализуется через динамическую конфигурацию, а у вас получается нестандартный кейс. Вроде как бы да, DDD, но все инварианты это валидация. И особенность, что единственный тип контроллера это HTTP.
А так, всё-таки посмотрите на Orleans. Во-первых, обработчик данных с датчика / девайса будет "теплым", держать открытым соединения с базой, какой-нибудь кеш метаданных девайса и ТД. Во-вторых, можно стриминг данных настроить и скейлится автоматически в рамках кластера (очень удобно, когда подключаете нового нагруженного заказчика - железок пару добавили в кластер и оно само смасштабировалось ровно на столько, на сколько нужно, не нужно выделять отдельные железяки под заказчика и потом настраивать роутинг). В-третьих данные реалтайм отображать на какие-то дашбордики. Это как раз туда для IoT. Ничто не мешает сгенерировать API, которое будет использовать Grain как инфраструктурный компонент. Получится, что у вас все типа "stateless", но где надо для перфоманса "stateful".
Во второй статье же обсуждали -
Да в большинстве CRM (ABP - BoilerTemplate) объекты тоже в коде задаются, и норм. Это дело вкуса и ТЗ.
Выражений проекта "в реальном мире" пока два - Web и консольное приложение (где воркеры будут из шины читать).
В реальном проекте - или выделю Domain.* и Generators.* в отдельный репозиторий\пакет и буду всюду подключать (или придумаю еще что-нибудь, вертикальные слайсы например). Или даже дерево доменных проектов, и контейнеры (с воркерами\webApi) в infrastructure.
В рамках цикла статей я не буду на этом акцентировать внимание.
Я, конечно, прошу пардону, как говорится, но воркеры в аппликейшен слое? Дядя Боб вами был бы явно недоволен
C#, Кодогенерация и DDD Часть 1 — Настраиваем проект и запускаем простой кодогенератор