Привет! Начну с главного - я лентяй. Я очень-очень ленивый разработчик. Мне приходится писать много кода - как для бэка, так и для фронта. И моя лень постоянно терзает меня, говоря: Ты мог бы не писать этот код, а ты пишешь... Так и живем. Но что делать? Как можно избавиться от необходимости писать хотя бы часть кода? Есть много подходов к решению этой проблемы. Давайте посмотрим на некоторые из них. Предположим, ваш backend - это набор REST-сервисов. Первое, с чего стоило бы начать - изучить документацию вашего бэка в надежде наткнуться на спецификацию OpenAPI. Идеальной будет ситуация, когда ваш бэк предоставляет максимально полную спеку, в которой будут описаны все методы, которые используются клиентами, а также все передаваемые и получаемые данные и возможные ошибки. На самом деле, я пишу эти строки и думаю, что это само собой разумеющееся: кажется очевидным, что если ты разрабатываешь API, то должна быть и спецификация, причем не в виде простого перечисления методов, а максимально полная, и, самое главное - генерируемая из кода, а не написанная руками, но так дело обстоит далеко не везде, поэтому надо стремиться к лучшему. Ну ок, вот мы нашли нашу спеку, она полноценная, без темных пятен. Отлично - дело почти сделано. Теперь осталось  использовать ее для достижения наших коварных целей. Так уж вышло, что я пишу еще и приложения на Flutter и в качестве клиента буду рассматривать именно его, но подход, применяемый тут - подойдет и для web-клиентов (да и для любых других тоже найдется что заюзать). Думаю, не будет откровением, что магии то и нет. Чтобы фича появилась - все равно должен появиться некий код. И да, мы не будем его писать, но будет кодогенератор. И вот тут-то и начинается самое интересное. Есть библиотеки для Flutter (да и не только для него), которые сгенерируют код для работы с бэком исходя из аннотаций, которые вы можете накидать на псевдо-сервисы (которые вам все еще придется написать).  Выглядит это примерно так: После запуска генератора мы получим рабочий сервис, готовый к использованию: Данный способ (применимый на любых типах клиентов) может сэкономить вам немало времени и в случае, если ваш бэк не имеет нормальной OpenAPI схемы - то у вас то и не особо большой выбор, однако, если качественная схема есть, то по сравнению с тем способом генерации кода, о котором мы поговорим дальше у текущего варианта есть несколько недостатков: Вам все еще нужно писать код, меньше, чем раньше, но немало Вы должны самостоятельно отслеживать изменения в бэкенде и вслед за ними менять написанный вами код На последнем пункте стоит остановиться немного подробнее - если (когда) произойдут изменения на бэке в методах, которые уже используются в вашем приложении - то вам нужно самостоятельно отслеживать эти изменения, дорабатывать модели DTO, и, возможно, endpoint'а. Также, если по какой-то невероятнейшей причине произойдут обратно-несовместимые изменения метода, то узнаете вы об этом только в рантайме (в момент вызова данного метода) - чего может не произойти во время разработки (особенно, если у вас нет или недостаточно тестов) и тогда у вас будет крайне неприятный баг в проде. Вы же еще не забыли, что у нас есть качественная OpenAPI-схема? Отлично! Все поле боя вам открыто и нет смысла идти наощупь (я добавил эту фразу, чтобы хоть как-то оправдать заголовок этого блока, придумывать которые, со скрипом, приходится самому; генерация тут не поможет). Тогда стоит обратить внимание на те инструменты, которые предлагает вся экосистема OpenAPI в принципе! Из всего многообразия молотков и микроскопов сейчас нас интересует всего один. И имя ему - OpenAPI Generator. Данный напильник позволяет генерировать код для любого языка (ну почти), а также - как для клиентов, так и для сервера (чтобы сделать mock-сервер, к примеру). Давайте уже перейдем к коду: В качестве схемы мы возьмем то, что предлагает демка Swagger. Затем, нам надо установить сам генератор. Вот прекрасное пособие для этого. Если вы читаете эту статью, то с высокой долей вероятности у вас уже установлена Node.js, а значит, одним из самых простых способов установки будет использование npm-версии. Следующий шаг - сама генерация. Есть парочка способов сделать это: Использование сугубо консольной команды Использование команды в сочетании с файлом конфигурации На нашем примере 1й способ будет выглядить следующим образом: Альтернативный способ - описание параметров в файле openapitools.json, например так: И последующий запуск команды: Полный перечень доступных параметров для Dart представлен здесь. А для любого другого генератора список этих параметров можно узнать, выполнив следующую консольную команду: Даже если вы выберите полностью консольный вариант, после первого запуска генератора, у вас появится файл конфигурации с прописанной в нем версией используемого генератора, как в данном примере - 5.1.1. В случае с Dart / Flutter эта версия имеет весьма важное значение, так как каждая из них может нести определенные изменения, в том числе, с обратной несовместимостью или интересными эффектами. Так, начиная с версии 5.1.0 генератор использует null-safety, но реализует это посредством явных проверок, а не возможностей самого языка Dart (пока это так, к сожалению). К примеру - если в вашей схеме некоторые из полей модели размечены как обязательные, то если ваш бэкенд вернет модель без этого поля - то случится ошибка в рантайме. А все из-за того, что поле name модели Pet явным образом указано, как обязательное, но отсутствует в ответе запроса: Что же, генератор запущен - дело сделано и осталось несколько несложных шагов, в которых нам почти не придется писать код (ради этого же все затевалось!). Стандартный openapi-generator сгенерирует только базовый код, в котором используются библиотеки, полагающиеся уже на кодогенерацию средствами самого Dart. Поэтому, после завершения базовой генерации необходимо запустить и Dart генератор: На выходе мы получаем готовый пакет, который будет располагаться там, где вы указали в файле конфигурации или консольной команде. Осталось включить его в pubspec.yaml: И использовать данную библиотеку следующим образом: Из важного в ней - необходимость прописать, какие мы будем использовать сериализаторы (standartSerializers) для того, чтобы JSON'ы превращались в нормальные модели. А также прокидывать инстансы Dio в сгенерированные ...Api, указывая в них базовые урлы серверов. Вроде бы и все, что можно сказать по этой теме, но Dart не так давно получил крупное обновление, в нем появилась null-safety. И все пакеты активно обновляются, а проекты мигрируют на новую версию языка, более устойчивую к ошибкам нашим. Однако, на данный момент, генератор не поддерживает эту новую версию, причем - сразу по нескольким направлениям: Версия языка в пакете (в последней версии генератора - 5.1.1, используется Dart 2.7.0) Устаревшие пакеты Обратная несовместимость некоторых из используемых пакетов (в актуальной версии Dio некоторые методы имеют другие названия и много чего еще) И это может доставлять сразу несколько проблем - если вы уже перешли на Flutter 2.0+ и Dart 2.12+, то, чтобы запустить кодогенерацию второго этапа (которая на Dart) - вам придется переключать язык на старую версию, FVM позволяет это сделать довольно быстро, но это все равно неудобство. Второй минус заключается в том, что данный сгенированный api-пакет теперь является legacy-зависимостью, что не позволит запустить ваш новый проект с sound-null-safety. Вы сможете использовать преимущества null-safety при написании кода, но рантайм проверки и оптимизации вам будут недоступны, а проект будет работоспособен только при использовании дополнительного параметра Flutter: --no-sound-null-safety. Варианта исправления этой ситуации три: Сделать pull-request, с обновлением openapi-generator Дождаться, пока это сделает кто-то другой, через пол года это, скорее всего, случится Исправить сгенерированный код, чтобы он уже сейчас стал sound-null-safety Третий пункт звучит так, будто нам придется писать код... Немного придется, но не тот. До начала наших манипуляций покажу вам bash-скрипт, который получился на данный момент и который запускает всю нашу логику генерации кода: Данный скрипт полагается и на файл конфигурации, который мы обсуждали выше. Давайте дополним этот скрипт, чтобы он сразу же и обновлял все зависимости нашего сгенированного пакета: Теперь - наш генератор корректно запустится и с новой версией Dart (>2.12.0) в системе. Все бы ничего, но использовать наш api-пакет по прежнему не получится! Во первых - сгенерированный код изобилует аннотациями, привязывающими его к старой версии языка: А во вторых - есть обратная несовместимость в логике Dio и пакетов, которые используются для сериализации / десериализации моделей. Давайте исправим это! Для исправления нам потребуется написать совсем немного утилитарного кода, который будет исправлять несовместимости, которые появятся в нашем сгенерированном коде. Я упоминал выше, что советовал бы ставить генератор посредством npm, как самого простого способа, если у вас есть Node.js, соответственно, по инерции - и утилитарный код будет написан на JS. При желании его несложно переписать на Dart, если у вас нет Node.js и нет желания с ней связываться. Давайте взглянем на эти нехитрые манипуляции: Включим использование данного скрипта после openapi-генератора и до Dart-генератора: Теперь все готово! Большая часть всех этих регулярок исправляют базовую логику сгенерированного кода, однако есть и тройка кастомных, которые нужны для конкретного API. В каждом конкретном будут свои кастомные регулярки, но очень вероятно, что добавить их не составит большого труда, а все базовые будут работать на любом API. Подход к генерации клиентского кода, при наличии качественной OpenAPI схемы является крайне простой задачей, вне зависимости от языка клиента. В случае Dart - все еще есть определенные неудобства, вызванные, сугубо, переходным периодом на null-safety. Но в рамках данной заметки мы успешно преодолели все неурядицы и получили полностью работоспособную библиотеку для работы с бэкендом, зависимости которой (и сама она) обновлены до самой новой версии и могут быть использованными в проекте на Flutter с sound-null-safety без каких-либо ограничений. Дополнительный плюс подхода, когда источником истины является именно схема - в случае ее изменения с потерей обратной совместимости наш сгенерированный код тут же отреагирует на это и покажет все ошибки на этапе статического анализа что убережет ваши нервишки от отлова багов в рантайме. Также, есть и другие способы, которые позволят не писать то, что можно не писать. А тем временем, весь код из статьи с рабочими заплатками, позволяющими использовать генератор с null-safety уже сейчас можно найти тут. Lead Fullstack Developer