Найти в Дзене

"Как писать кодогенераторы в Go"

Оглавление

GopherCon Russia 2020. Дмитрий Смотров о трюках и лайфхаках кодогенерации в Go

Конференция GopherCon Russia 2020 не за горами. Если вы еще не зарегистрировались на участие в ней, ловите последний шанс сделать это на сайте www.gophercon-russia.ru

Go и Js разработчик компании «Открытая мобильная платформа» Дмитрий Смотров расскажет на конференции как писать кодогенераторы в Go и как оптимизировать работу над микросервисами с помощью создания инструмента для генерации шаблонного кода микросервисов. 

«Открытая мобильная платформа» - разработчик мобильной операционной системы Аврора и продуктов на ее основе, серебряный партнер GopherCon Russia 2020. Компания динамично и успешно сотрудничает с лидерами корпоративного рынка и государственными компаниями, формирует стандарты мобильных приложений, создавая совместные решения и внедряя свои продукты.

Подробней о выступлении Дмитрия читайте в интервью.

Что это за ОС такая Аврора? Вот есть Android и iOS, а ваша чем отличается? Куда ее ставят? Почему ее, а не две обычные?

Российская мобильная операционная система Аврора позволяет создавать и использовать решения с необходимым уровнем информационно-коммуникационной защиты для государственных структур и крупных коммерческих организаций. Использование платформы позволяет исключить риски потерь и компрометации информации, которые возможны при использовании смартфонов и мобильных устройств, рассчитанных на массового потребителя.

ОС Аврора включена в Единый реестр российских программ для электронных вычислительных машин и баз данных и прошедшей сертификацию ФСБ по уровню защиты информации АК1/КС1. ОС «Аврора» получила сертификат ФСТЭК на соответствие требованиям профиля защиты операционных систем типа «А» 4 класса защиты.

На сегодняшний день ОС Аврора уже используется в ФГУП «Почта России» для оказания дополнительных услуг населению. Также ОС Аврора используется в таких компаниях как РЖД, Ростелеком и других.

Расскажи над какими продуктами и компонентами ты сейчас работаешь и как это связано с Go?

Наша компания разрабатывает мобильные решения:

  • Мобильная ОС Аврора (ранее Sailfish Mobile OS RUS)
  • SDK и экосистему приложений для ОС Аврора
  • Комплексное сертифицированное решение для управления мобильной инфраструктурой Аврора Центр, включающее несколько "коробочных" продуктов и компонентов

Работаю как раз над продуктами и компонентами Аврора Центр, в основном с продуктом  Аврора Маркет, который обеспечивает управление дистрибуцией приложений

Бекенд Аврора Центра написан полностью на Go.

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

Расскажи о себе?

Люблю разрабатывать. Для меня программирование – это не только работа, но и хобби. Разработчиком работаю уже 10 лет. Разрабатывал как бекенд на Go, Java, PHP, фронтенд на React так и мобильные приложения на iOS, Android. Нравятся и задачи, связанные с DevOps, наладил не одну CI/CD для клиентов.

Люблю принимать вызовы, связанные с программированием. Когда другие говорят, что это сделать невозможно или дорого, принимаю вызов и стараюсь сделать это, и так, чтобы это было актуально для бизнеса. Один раз за неделю написал файловую систему с многоуровневым кешем на Go и сэкономил терабайты свободного места у клиента.

Работал только в двух компаниях. 

Проработал 8 лет в компании в Ростове-на-Дону, занимающейся веб-разработкой. Меня наняли первым сотрудником в компанию. Развивался и следил за технологиями. Начал программировать на Go. Позже решил переехать в Москву и ушел из этой компании. К этому времени в компании уже работало 30 специалистов.

Два года назад, с переездом в Москву перешел в компанию Открытая мобильная платформа. И сразу же, на новом месте принял новый вызов – создать кодогенератор для микросервисов, об этом и расскажу в докладе.

Что такое кодогенерация?

Go относительно молодой язык и многие разработчики Go – это бывшие разработчики из других языков программирования. В Go принято отдавать предпочтение явному программированию (explicit) в противовес неявному (implicit). Это помогает новым разработчикам легче начинать работать над существующими проектами. Но по пути от неявного программирования к явному можно легко заблудиться и забрести в дебри дубляжа кода, который в дальнейшем превратит поддержку проекта в ад.

Чтобы этого избежать – код выносят в отдельные модули. Но, что если есть код, который приходится писать для каждого микросервиса нельзя вынести в модуль? Например, код репозитория для работы с базой данных. Этот код есть в каждом микросервисе, выглядит примерно одинаково, но он разный и не дублируется. Не хочется писать шаблонный код, который потом придется еще и поддерживать во всех микросервисах.

Кодогенерация это официальный инструмент от авторов Go.

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

И хотя в Go принято отдавать предпочтение явному программированию, разработики предоставили инструменты для метапрограммирования, такие как кодогенерация и reflection api. Reflection api используется на этапе выполнения программы, кодогенерация перед этапом компиляции. Reflection api увеличивает время работы программы. 

Пример: инструмент для кодирования и декодирования JSON из стандартной библиотеки Go использует reflection api. Взамен ему сообществом были рождены такие альтернативы как easyjson, который с помощью кодогенерации кодирует и декодирует JSON в 5 раз быстрее.

Так как кодогенерация – неявное программирование, она недооценивается сообществом Go, хотя и является официальным инструментом от создателей Go. Поэтому в интернете мало информации о написании кодогенераторав на Go.

Для решения каких задач ваша команда использует кодогенераторы в Go? Какие «боли» они решают и какую пользу дают разработчикам?

При разработке микросервисов есть много похожего шаблонного кода, который нужно писать в каждом микросервисе.  Например, код репозитория по работе с базой данных. Мы создали кодогенераторы для того, чтобы разработчики не тратили время на написание этого шаблонного кода и могли сфокусироваться на решении задач относящимся к дизайну кода и предметной области бизнеса. Команда использует кодогенераторы и для сокращения времени на создание новых микросервисов. Это позволяет не ограничивать разработчика в принятии архитектурных решений, так как создание нового микросервиса не влияет на трудоемкость выполнения задачи.

Из примеров написанных и удачно используемых в компании кодогенераторов могу подробнее рассказать о генераторе репозитория по работе с базой данных. Мне нравится переносить опыт из одного языка программирования в другой. Так наша команда попытались перенести идею по генерации репозиториев по работе с базой данных из Java Spring. В Java Spring разработчик описывает интерфейс репозитория и исходя из сигнатуры метода автоматически генерируется реализация в зависимости от того, какой бекенд для базы данных используется: MySQL, PostgreSQL или MongoDB.  Например, для метода интерфейса с сигнатурой FindTop10WhereNameStartsWith(prefix string) автоматически сгенерируется реализация метода репозитория, которая вернет до 10 записей из базы данных, имя которых начинается с переданного в аргументе префикса.

С какими трудностями внедрения кодогенератора столкнулась ваша команда и как с ними справлялась?

Существует парадигма Монолит-first, когда пишут первую версию как монолит, а потом распиливают на микросервисы. На заре новой версии проекта, когда все команды должны были переписать монолит на микросервисы, руководством было принято решение написать генератор микросервисов, который:

  • Позволит вводить в систему новые микросервисы с меньшими усилиями, чем создавая микросервисы вручную (копируя предыдущий и удаляя лишнее)
  • Сократит время на код-ревью за счет общего шаблона для генерируемых микросервисов
  • Сократит время на будущие обновления одинакового кода микросервисов (main, инфрастуктура, etc…)

Для разработки микросервисов командами было принято решение использовать go-kit. За основу я взял один из популярных существующих кодогенераторов для go-kit, и начал его дорабатывать под наши требования для микросервисов.  Он был написан с использованием не очень удобной библиотеки, которая использовала промежуточные абстракции для генерации кода Go. Код получался громоздким и в следствии трудным к восприятию и поддержке.  В будущих версиях мы отказались от такого подхода и начали генерировать код Go с помощью шаблонов Go. Генерация кода с помощью шаблонов Go позволяет писать тот же самый код на Go без каких-либо промежуточных абстракций. За пару недель нашей командой был написан прототип, а еще через месяц был написан кодогенератор для go-kit, который буквально умел делать все. 

Разработчик описывает интерфейс go-kit сервиса, а кодогенератор генерирует сразу все, что для сервиса нужно: 

  • CRUD-эндпоинты и REST, GRPC и Nats транспорты
  • Репозиторий для работы с базой данных, с возможностью расширять интерфейс репозитория, а бы дегенерировал автоматически реализацию методов этого репозитория.
  • Main для всех go-kit сервисов.

После того, как кодогенератор был закончен, началось его внедрение. Мы с сразу же столкнулись с проблемами. Разработчики компании неохотно принимали кодогенератор.  Он генерировал слишком много кода. Весь этот код нужно было ревьюевить и перерабатывать.  Сначала команды помогали исправлять генерируемый код, но подошло время уже разрабатывать сами микросервисы, а не кодогенератор для генерации сервисов. В итоге, одни команды продолжали использовать кодогенератор, обходя его баги, а другие начали писать микросервисы без кодогенератора.  Мы получили сегментацию сервисов, и когда инфраструктура менялась – изменения давались нам очень тяжело.

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

Позднее, когда наша команда вернулась к разработке нашего основного продукта, мы провели ретроспективу кодогенератора и поняли в чем была основная проблема. Кодогенераторы, которые “генерируют ВСЕ“ сложно внедрять и поддерживать. Мы исправили эту проблему. Сейчас кодогенератор разбили на несколько мелких, у каждого из которых своя собственная ответственность. Разработчики сами выбирают какие из них использовать в своих продуктах.

На GopherCon Russia ты покажешь, как написать собственный кодогенератор, который будет генерировать код из шаблонов Golang. Что будет делать этот кодогенератор? Расскажешь об основных трюках кодогенерации в Go?

На GopherCon Russia я покажу как за 15 минут можно написать простенький кодогенератор для репозиториев по работе с базой данных. Покажу основные приемы кодогенерации, освоенные в нашей компании и постараюсь таким образом исправить ситуацию, что в интернете мало информации как начать писать собственные кодогенератор на Go.

Ну и напоследок – за что ты любишь Go, что в нем не нравится?

Люблю Go за:

  • Строгую типизацию, которая позволяет писать стабильный безопасный код. Поработав годы на JavaScript и PHP и перейдя на Java начинаешь ценить и любить строгую типизацию.
  • Сборка приложения в 1 единственный бинарный исполняемый файл. Копируешь этот файл в Docker-контейнер после сборки и приложение готово для запуска. Без установки SDK или сторонних библиотек. Раза 2-3 сталкивался с ситуаций, что кончались возможные идентификаторы (иноды) для файлов в файловой системе из за черной дыры под названием node_modules в JavaScript.
  • Самодостаточная стандартная библиотека языка. Интерфейсы io.Reader, io.Writer, которые используются везде и пакет http, который поддерживает большую часть стандартов. Думаю, что здесь Go выигрывает все другие языки программирования.

За что же не люблю Go?

Это комьюнити Go. Иногда, кажется, что комьюнити Go слишком радикальное по сравнению с другими языками программирования. Возможно, потому что комьюнити еще молодое, по сравнению комьюнити других языков программирования. Такое ощущение, что в других языках программирования стараются перенимать удачный опыт, а в комьюнити Go к решению могут отнестись отрицательно даже, если решение не противоречит Go way, но было взято из другого языка программирования. Да, паттерны проектирования от других языков программирования нельзя перенести как есть, например, потому что в Go нет наследования. Наследование это антипаттерн для Go. Но это не значит, что теперь нужно отказываться от всех проверенных временем паттернов проектирования, потому что Go не такой как другие. Паттерны проектирования нужно перерабатывать под Go.

Взять только наболевшую тему с дженериками. Дженерики хотят видеть в Go те, кто работал с ними в других языках. Комьюнити же отрицало и, кажется, до сих пор отрицает надобность дженериков в Go. Хотя с этим уже согласились сами разработчики языка Go. Комьюнити говорит, что все, что вы хотите сделать с дженериками можно сделать и без них. Кстати, один из предлагаемых вариантов по замене дженериков – это кодогенерация. 

Еще не понравилась ситуация с драфтом новой обработки ошибки. Я нейтрально отношусь к идее новой обработки ошибок. Но у меня сложилось ощущение, что большинство отказавшихся от нее людей в комьюнити фанатично ничего не хотят менять. Я переживаю за Go, боюсь, что Go может постичь судьба PHP, когда разработчики PHP надолго заморозили разработку нового функционала и отстали от других языков программирования. Надеюсь, что разработчики Go будут продолжать развивать этот язык программирования и доносить видение и идеи до комьюнити, даже, если большинство будет против.

Еще одна вещь, которая расстраивает в Go. В Go самодостаточная стандартная библиотека – это да. Но нет качественных корпоративных решений. Таких как Java Spring. Думаю, что это тоже из-за того, что и язык и комьюнити относительно молодое. В интернете много статей, в которых пишут – чем плох Java Spring по сравнению с Go. Но считаю, что здесь такая же ситуация как и с React и Angular. Angular – разработчики Google написали сильный фреймворк, в котором продумали архитектуру за разработчиков. React позволяет тебе сделать любую архитектуру, которая тебе нужна, предоставляя большую гибкость. И я знаю многих разработчиков, которым не нужна такая гибкость и они с радостью пишут на Angular следуя всем рекомендациям его авторов. Хочется и в Go такой продукт, чтобы в каждой компании не изобретали свой собственный велосипед.Открытая мобильная платформа» участвует в GopherCon Russia 2020 – первой конференции о разработке на Go в России – в качестве серебряного партнера.

Сайт мероприятия: https://www.gophercon-russia.ru/