Привет, Habr! С вами Антон, руководитель Архитектурного комитета компании SimbirSoft. Мы продолжаем цикл статей, посвященных практическому внедрению подхода Design API First в разработку наших проектов. Прежде чем погрузиться в новую тему, напомню о предыдущих материалах: общий взгляд на Design API First, особенности его применения; разбор практического использования подхода на примере сервера аутентификации; наш опыт подготовки и планирования интеграции Design API First в наш конвейер разработки ПО; ‘how-to’ для генерации моделей frontend по спецификации OpenAPI. Настало время поделиться практическим опытом использования спецификаций OpenAPI для кодогенерации контрактов backend. Дисклеймер:  Материал публикации в первую очередь передает практический опыт работы системных аналитиков и практикующих архитекторов при интеграции Design API First с непосредственным процессом разработки. Некоторые технические детали реализации будут описаны не полностью. В качестве приложения мы использовали сервис файлового хранилища. Backend написан на .Net Core 6. Реализация REST API на стороне backend была проведена разработчиками (Code First). Мы сделали так намеренно для проверки сценария перехода c Code First на Design First. Подробнее об этом описали в статье «Интеграция паттерна Design API First в конвейер разработки ПО: наш опыт». Итак, что было сделано для поддержки подхода Design API First на стороне backend: В качестве инструмента кодогенерации мы выбрали кодогенерацию с использованием компилятора Roslyn. В качестве прикладной инструментальной библиотеки кодогенерации был выбран NSwag. В качестве реализации была разработана служебная библиотека, которая использовала Source Generators и NSwag. В качестве реализации была разработана служебная библиотека, которая использовала Incremental Generators и NSwag. Библиотека кодогенерации на Incremental Generators была протестирована для REST API Backend. По результатам проделанных работ мы провели оценку удобства использования кодогенераторов Roslyn для создания «заглушек» кода по спецификации OpenAPI. Но об этом расскажем в конце статьи. А сейчас приведем основные этапы проверки технологического решения. Мы решили проверить актуальные возможности платформы разработки .Net 6 и обнаружили, что компилятор Roslyn и его анализаторы предоставляют готовые механизмы для генерации исходных программных кодов. Для проверки этих возможностей, очевидно, было необходимо разработать непосредственно генератор для спецификации OpenAPI. В рамках задачи генерации исходных кодов по спецификации OpenAPI мы также не стали ничего изобретать. В качестве инструмента выбрали NSwag — самое популярное решение среди наших backend-команд разработки. Кодогенератор на основе Source Generators был разработан довольно быстро и оказался вполне работоспособным. Привожу листинг его реализации: Код генератора довольно прост. Под «капотом» мы использовали стандартный вызов из библиотеки NSwag.Core: OpenApiDocument.FromFileAsync(filePath). Настройки NSwag для генерации были следующими: Фактически мы использовали контекст GeneratorExecutionContext context нашего генератора при вызове ISourceGenerator.Execute для поиска спецификаций OpenAPI в заданном проекте. Затем мы передавали найденные спецификации для обработки в NSwag. На выходе получали исходники контроллеров и моделей предметной области согласно исходным спецификациям OpenAPI. В целом, идея реализации на практике оказалась довольно жизнеспособной. Но были и проблемы... В частности, связанные с нюансами работы кодогенерации Roslyn. Наверняка те, кто с ними уже знаком, понимают, о чем речь. Всем остальным вкратце расскажу (информация общедоступна и не является темой нашей статьи): у Source Generators на Roslyn есть существенные проблемы с производительностью, что хорошо проявляется на больших решениях и при частых изменениях источников кодогенерации (в нашем случае это спецификации OpenAPI). Работать, по факту, крайне неудобно. Например, при внесении изменений в спецификации часто приходится перезагружать проект или IDE целиком для запуска перегенерации исходных кодов. Для решения указанных и довольно очевидных проблем мы пошли на поводу у обещаний «мелкомягких» и уверовали в предлагаемые Incremental Generators. В этой версии реализация стала сложнее, проблемы с производительностью окончательно не устранились. Вдобавок выяснились некоторые нюансы использования кодогенерации Roslyn при подключении к другим проектам в решении. Но обо всем по порядку. Немного кода. Так сейчас выглядит наш IncrementalGenerator:  В приведенной выше реализации помимо обработки спецификаций в вызове .SelectAndReportExceptions(GetSourceCode, context, Id) мы используем также вызов  .Combine(context.CompilationProvider.Select(static (x, _) => x.AssemblyName)) для передачи в генератор соответствующего пространства имен, которое задает namespaces исходных кодов, созданных по спецификации OpenAPI. Пример результатов работы кодогенерации в структуре проекта приведен на рисунке ниже:   Что касается проблем с производительностью кодогенерации на основе IncrementalGenerators, в нашем случае проблемы полностью не исчезли, но работать стало возможно. Зависания IDE не наблюдалось, однако изменения в спецификации OpenAPI все так же не всегда отображались в генерируемых кодах без перезагрузки проекта IDE. На этом этапе также выяснились базовые ограничения кодогенераторов Roslyn. Они могут быть созданы только в проектах, у которых в качестве целевого фреймворка указан netstandard2.0. Изначально мы не придали значения этому ограничению, так как все работало и исходные коды на языке C# генерировались по заданным спецификациям OpenAPI. Главные проблемы и неудобства (впрочем, не критичные) возникли при использовании результатов кодогенерации непосредственно в проектах (projects) конечных API. Чтобы вам было легче представить ситуацию и состояние решения к этому моменту, приведу простую схему backend в части использования моделей кодогенерации: Рассмотрим ряд сложностей и неудобств, с которыми мы столкнулись при использовании кодогенераторов Roslyn: Сгенерированные модели из проекта OpenAPI spec Project отказывались «подхватываться» при их использовании в FileUploadAPI, FileDownloadAPI, FileManageAPI. Причем ошибка (error CS0246: The type or namespace name ‘...’ could not be found) возникала временно, в момент сборки. В остальное время ссылки на модели из OpenAPI specs Project доступны и ошибки в IDE не отображаются. Проблема связана с форматом их подключения. При подключении служебной библиотеки кодогенерации в виде nuget-пакета ошибка при сборке API не возникает. Мы были ограничены во времени для дальнейшего изучения и решения проблемы зависимостей. Если вы сталкивались с таким — расскажите в комментариях, какое решение вам удалось найти. Если возникает необходимость использовать результаты кодогенерации одной спецификации OpenAPI в нескольких проектах, появляется технический долг на поддержку наследования и создание заглушек для неиспользуемых методов. Ухудшается читаемость кода и увеличивается время на работу с кодовой базой. Это проявляется также в случае, когда изменения вносятся в спецификацию OpenAPI, т.к. генерируемые абстрактные классы контроллеров требуют поддержки во всех проектах, использующих изменяемую спецификацию. Ряд атрибутов из OpenAPI-спецификации не добавляется в классы кодогенерации. Ни одна из возникших сложностей не была критичной. Ниже приведем возможные решения, к которым мы пришли в рамках текущего состояния: НО! Только как временное решение для возможности проверки всего процесса. Поэтому необходимо время на понимание проблемы. НО! Это неудобно с точки зрения целостности доменной области. Есть необходимость нарезать спецификации на уровне реализации сервисов. НО! Возможны дополнительные временные издержки, связанные с доработкой кодогенерации — смотрите опыт команды МТС. По результатам проверки возможности реализации технического решения (фух!) мы пришли к следующим выводам: Для более общего понимания и принятия решения стоит рассмотреть использование Swagger вместо NSwag в качестве прикладного инструмента кодогенерации. Также следует рассмотреть альтернативу кодогенераторам Roslyn (например, в виде шаблонизатора T4). К моменту написания этого материала мы еще не попробовали использовать связку Roslyn+Swagger, а вот с реализацией кодогенерации на основе шаблонизатора T4 поработали. Что из этого получилось — расскажем подробнее в следующей статье. Спасибо за внимание! Авторские материалы для разработчиков и архитекторов мы также публикуем в наших соцсетях – ВКонтакте и Telegram.