Всем привет! Меня зовут Виталий Карпенко, я Go-разработчик в tooling-команде Lamoda Tech. Наша команда занимается разработкой внутренних библиотек и Go-инструментария, а также поддержкой существующих opensource-инициатив.
При создании новых сервисов повсеместно используют шаблонизацию, в крайнем случае — в формате ручного копирования кода. Но ручное копирование не исключает ошибок, а еще это муторно и долго. Быстрее и приятнее ответить CLI-утилите на несколько вопросов о будущем проекте и получить на выходе готовый «скелет» сервиса. Остается только начать писать бизнес-логику.
В этой статье речь пойдет о том, как мы генерируем код для новых сервисов с помощью инструмента собственной разработки Scratch.
Почему нам не подошел Cookiecutter
Главное преимущество шаблонизации, кроме экономии времени, — это соблюдение стандартов. Когда стандарты появляются в проекте на этапе генерации, а затем их соблюдение проверяется в CI, придерживаться их намного проще. Под стандартами я понимаю в том числе и общий формат метрик, логов, линтеров, конфигурации и общую структуру проекта.
Долгое время мы пользовались популярным инструментом для генерации проектов Cookiecutter. Это отличная утилита, достаточно простая и удобная, но нам хотелось иметь более широкие возможности, чем подставление переменных в шаблоны и запуск постген хуков.
Мы хотели управлять сгенерированным кодом не только при создании нового проекта, но и при работе с ним: например, управлять его инфраструктурными зависимостями. Также у нас появилась необходимость добавлять в сервисы работу с Kafka, чтобы одной командой получать сгенерированный для нее коннектор и добавлять в конфигурацию сервиса переменные окружения для настройки этого коннектора.
В Lamoda Tech активно используется Go. Благодаря кросс-компиляции в самодостаточные исполняемые файлы он отлично подходит для разработки CLI-приложений. Так что в качестве языка разработки для нового решения был выбран именно этот язык.
Как Scratch генерирует сервисы
Мы используем API-first подход, так что без спецификации в формате OpenAPI воспользоваться Scratch не выйдет. Установив и запустив исполняемый файл, отвечаем на ряд вопросов:
Как будет называться сервис?
В какой команде он будет?
Короткое и длинное описание сервиса.
Будет ли использоваться PostgreSQL, Kafka или другие зависимости, и если да, то с какими параметрами?
Нужно ли генерировать клиенты к другим сервисам, и если да, то к каким?
Если все прошло успешно, Scratch подумает, при необходимости скачает спецификации сервисов для генерации клиентов к ним и сгенерирует скелет нашего нового проекта.
На выходе получаем следующую структуру файлов:
Scratch сохраняет все заданные настройки нового сервиса (scratch.json в корне проекта на скриншоте) и затем использует их при необходимости перегенерации кода, например, при добавлении новой зависимости.
Он резервирует для себя папку (app/scratch), в которой в дальнейшем будет находиться весь перегенерируемый код: коннекторы к базам, инициализация rpc-сервера, модели и параметры запросов и ответов для методов RPC-сервера. Пользовательский код просто импортирует необходимое из этой папки.
Что еще умеет Scratch
Scratch append. Позволяет добавить любую зависимость, если она вдруг появилась в проекте. Это может быть Kafka, PostgreSQL, клиент к другому сервису или что-то еще.
Scratch dashboard. Создает в Grafana дашборд со всеми базовыми метриками сервиса: rps, количество подов в k8s и их рестарты, рантаймовые метрики Go. Поскольку Scratch знает о зависимостях сервиса, он может добавить туда также метрики и для них.
Scratch update. Проверит наличие свежей версии, скачает ее и заменит самого себя. При запуске будет предлагать обновиться: это можно сделать одной командой.
Что дальше
Мы запустили Scratch несколько месяцев назад. Около десяти проектов, сделанных на нем, добрались до продакшена, что значительно упростило нам жизнь. Коллеги уже предлагают идеи для доработки, и вот что у нас в планах.
Полноценная интеграция с CI/деплоями. В планах сделать так, чтобы Scratch мог автоматически создавать репозиторий и генерировать конфигурацию для запуска тестов, линтеров и сборки финального Docker-образа в CI.
Gonkey. В Lamoda Tech широко применяется разработанный внутри нее opensource-инструмент для интеграционного тестирования — Gonkey. Подробнее о нем рассказывал мой коллега в статье «Автотесты на языке разметки или как мы в Lamoda тестируем микросервисы». Scratch умеет парсить и «понимать» спецификацию сервиса: для этого используется надстройка над go-swagger. Поэтому было бы очень удобно, если бы он умел автоматически генерировать заглушки для тестов всех методов сервиса. Оставалось бы только заполнить параметры запросов.
Opensource. Инструмент получился удобным и полезным, в перспективе хочется поделиться им с сообществом. Основная сложность тут в том, что он заточен под Lamoda Tech — конкретную инфраструктуру и соглашения. Мы хотим решить эту проблему с помощью системы плагинов для более гибкой настройки, отделенной от основной логики.
P.S. Поделитесь в комментариях, насколько вам интересно было бы получить открытый доступ к нашей разработке и пользоваться инструментом в своих целях. Хочется убедиться, что у сообщества есть отклик :)
Комментарии (7)
manyakRus
25.04.2023 09:32Фигня получится. Шаблонизация это то же самое копирование.
Должно быть: весь шаблонный код хранится в отдельном репозитории, и при необходимости используется (не копируется, а импортируется)
"работу с Kafka, чтобы..." - это делается 1 строчкой кода, если есть специльеый репозиторий.
anaxita
25.04.2023 09:32В вашем образце вообще нет ни малейшей возможности что-либо конфигурировать,.
Пример с кафкой - захардкоженные параметры для ридеров, не уверен, что такие параметры подойдут всемю
Так же придётся целиком импортировать всю либу по сути, чтобы использовать один модуль.
vitkarpenko Автор
25.04.2023 09:32Мы тут, кажется, говорим о немного разных вещах - непосредственно код работы с зависимостями хранится, конечно, в отдельных репозиториях и импортируется как библиотека. Речь идёт о возможности одной командой добавить конфигурацию/инициализацию зависимости, проброс необходимых ей переменных окружения в конфиг "внешнего" сервиса, добавление соответствующего контейнера в docker-compose для локального запуска и так далее. :)
vizorz
25.04.2023 09:32+1Я комментарии никогда не писал, но тут напишу
С удовольствием буду ждать развития проекта!
BATAZOR
25.04.2023 09:32Как развитие идеи модулей - хорошо заходит DI - google/wire например, можно сделать набор провайдеров и потом через запятую указать нужные, в целом к этому при желании можно и promt-прикрутить что бы в диалоге прописать какие модули нужны для сервиса
p.s. генерация графана-дашбордов выглядит интересно, полезная вещь, было бы интересно посмотреть как генерите и какие метрики туда вносили (и почему добавили те или иные - тоже было бы интересно послушать)
serjeant
Интересный проект, хотелось бы пощупать руками код