Вольный перевод статьи на ресурсе threedots.tech от Robert Laszczak - главного инженера в SlashID, сооснователя Three Dots Labs и создателя популярной Golang-библиотеки Watermill.
На протяжении некоторого времени я занимался руководством группы Go-разработчиков и одним из самых распространенных вопросов от начинающих специалистов был - "Какой фреймворк мне следует использовать?". Одним из самых худших советов, которые вы можете услышать в Golang - это следовать подходу и принципам других языков программирования.
В других языках программирования имеются популярный фреймворки, которые стали неким стандартном и используются “по умолчанию”. В Java есть Spring, в Python есть Django и Flask (+ различные расширения), в Ruby есть Rails, у Node есть Express, а у PHP есть Symfony и Laravel. В Golang на данный момент времени есть отличие - здесь нет фреймворка по умолчанию.
Что еще более интересно, многие опытные Go-разработчики предполагают, что Вам вообще не следует использовать фреймворк. Вы можете про себя сказать - они что, сумасшедшие? ?
Философия Golang
Фреймворки в Go существуют, но ни один из них не предоставляет такого набора функций - как фреймворки в других популярных языках. Это не скоро изменится.
Вы можете подумать, что это потому - экосистема Go крайне молодая и еще не сформирована полностью. Но есть более важный фактор. Go построен на философии Unix, которая гласит:
1. Write programs that do one thing and do it well.
2. Write programs to work together.
3. Write programs to handle text streams because that is a universal interface
Эта философия возникла у Ken Thompson - разработчика языка программирования B (предшественника C).
На практике философия Unix отдает предпочтение созданию небольших независимых частей программного обеспечения, которые хорошо выполняют что-то одно. Возможно, вы видели это в своем терминале (пример):
cat example.txt | sort | uniq
Разберем подробнее пример выше: утилита cat считывает файл example.txt, далее утилита sort сортирует данные и после утилита uniq оставляет только уникальные значения.
Все команды независимы и выполняют одну задачу. Это происходит непосредственно из философии Unix. Благодаря такому дизайну вы можете самостоятельно разрабатывать гораздо меньшие по размеру независимые команды.
В Golang философия Unix видна в стандартной библиотеке. Лучшими примерами являются наиболее распространенные интерфейсы ввода-вывода: io.Reader
и io.Writer
. Многие популярные библиотеки также придерживаются такой философии.
Фреймворки разработаны в соответствии с этой философией часто пытаются охватить все возможные варианты использования (use cases), что в конченом итоге может привести к невозможности его переиспользования. Через какое-то время такой фреймворк просто умрет.
Что важно понимать!
У каждого технического решения есть свои компромиссы. Вам нужно осознать эти ограничения, которые имеют большую важность для Вас и Вашего проекта .
Некоторые решения имеют смысл, когда Вы например работаете над доказательством некой концепции/подхода и после просто проект выбрасываете. В этом случае наиболее важным фактором является то, как быстро Вы сможете все развернуть/установить и проверить Вашу гипотезу (качество соответственно вторично тут). Но если Вы работаете над проектом в долгую и создаете его с участием нескольких человек - такой подход может оказаться провальным.
Для большинства проектов наиболее важными параметрами являются:
как быстро вы можете начать проект;
как быстро вы сможете развивать этот проект в долгосрочной перспективе;
насколько гибким для будущих изменений будет проект (это тесно связано с предыдущим пунктом).
Предлагаю начать оценивать наши выбранные решения именно с этих позиций.
Экономия времени
Одно из самых больших обещаний любого фреймворка - это экономия времени. Вы запускаете одну команду и получаете полностью функциональный проект готовый ко всему. Фреймворки обычно обеспечивают четкую структуру проекта и это действительно помогает при отсутствии необходимых знаний. Но, как и в случае с любыми техническими решениями - это не бесплатно.
Со временем, когда проект будет развиваться и код будет стремительно становиться все больше - вы быстро наткнетесь на стену условностей и ограничения конкретного фреймворка. Идеи и требования к фреймворку автора, может легко отличаться от Ваших взглядов и подходов. Решение принятое создателем фреймворка, может хорошо работать для простых CRUD-приложений, но может не подходить для более сложных сценариев использования. Таким образом легко потерять все свое сэкономленное время на начальном этапе проекта и в скором времени Ваша работа перерастет в борьбу с ограничениями фреймворка.
Пару лет назад я работал в компании, которая изначально начинала с фреймворка на Go (я пропущу название фреймворка). Компания росла и создавала новые сервисы. Со временем мы начали испытывать больше боли, когда хотели реализовать более сложные варианты использования. Это стало для нас серьезной проблемой. К сожалению, избавиться от фреймворка было уже непросто.
В какой-то момент некоторые компоненты/модули фреймворка могут перестать поддерживаться/развиваться и будут несовместимыми с остальной экосистемой Вашего проекта. Мы были вынуждены избавиться от такого решения. Замена фреймворка в десятках сервисов была нетривиальной и трудоемкой задачей. Это потребовало межкомандных взаимодействий, на устранение данной проблемы ушло несколько человеко-месяцев и было несколько инцидентов на ПРОМе. Даже если в конце концов проект был признан успешным, я не рассматривал его таковым с технической точки. Все потраченное время можно было бы использовать гораздо эффективнее, если бы кто-то принял другое решение на самом старте. Неудивительно, что многие компании страдают от недостатка доверия к команде разработки.
Это отличный пример того, как такое незначительное решение может через пару лет превратиться в проблему - на устранение которой потребуется много усилий и денег.
Сложность поддержки проекта
Измерение сложности поддержки проектов спорная тема. Трудно сравнивать два проекта. Некоторые люди говорят, что фреймворки великолепны и они не испытывают боли от их использования. Для других фреймворки могут стать самым большим кошмаром в долгосрочной перспективе. Некоторые проекты гораздо более сложные, чем другие. Многие люди думают, что борьба с фреймворком - это всего лишь часть работы. Вот почему трудно объективно измерить влияние фреймворков на сложность поддержки.
К счастью, мы можем помочь себе понять это с помощью небольшой науки. Точнее, с отличной книгой Accelerate: The Science of Lean Software and DevOps, основанной на научных исследованиях. Книга сосредоточена на поиске параметров лучших и наихудших команд. Что важно для нас - одним из наиболее значимых факторов хорошей производительности является слабосвязанная архитектура.
Команды, которыми я руководил, часто спрашивали меня - как узнать, слабо ли связана наша архитектура. Один из самых простых способов - обеспечить легкость замены или удаления частей вашего приложения. Если трудно удалить части вашего приложения -значит Ваше приложение тесно связано. Прикосновение к одной части - вызывает эффект домино изменений в разных местах.
Почему слабосвязанная архитектура так важна? Давайте просто это признаем - это так. Мы все люди и даже после самых тщательных исследований мы совершаем ошибки. Когда вы выбираете неправильный фреймворк или библиотеку - их должно быть легко заменить, не переписывая весь проект. Если мы хотим сэкономить время, мы должны подумать о том, что помогает в долгосрочной перспективе, а не только в начале проекта.
Рассмотрим сценарий, когда вы хотите полностью удалить фреймворк. Потребуется ли для этого много переписывания кода? Можно ли это сделать на нескольких сервисах независимо? Если нет, то Вы могли бы приложить некоторые усилия, чтобы отделить фреймворк от Вашей основной логики. Но это же потребует пожертвовать “экономией времени”, ради чего и используют фреймворки в первую очередь!
Есть ли альтернатива? Создаем сервисы без фреймворков
Вам может показаться, что создание Ваших сервисов без фреймворка займет целую вечность. Особенно, если вы переходите с других языков программирования. Я это прекрасно понимаю. У меня было такое же чувство пару лет назад, когда я начал писать на Go. Сейчас могу сказать уже с уверенностью - это был неоправданный страх. Отсутствие фреймворка не означает, что Вам нужно будет создавать все самостоятельно. Существует множество проверенных библиотек, которые обеспечивают необходимую Вам функциональность.
Вам нужно приложить немного больше усилий к исследованиям. Даже пара часов исследований - ничто по сравнению с продолжительностью всего проекта. Вы также очень скоро вернете это время назад благодаря гибкости, которую это Вам даст данный подход.
Что Вам следует делать, если Вы решите не использовать фреймворки? Самым большим препятствием в начале может быть то, как надо создавать сервис. Самый простой способ - начать с того, что поместить все в один файл. Вы можете начать с простого и постепенно усложнять с развитием проекта.
Полезно иметь примеры проектов, которые вы можете использовать в качестве основы. Вы можете взглянуть на проект, который я использовал для своего приложенияLet’s build event-driven application in 15 minutes with Watermill presentation at GoRemoteFest –github.com/roblaszczak/goremotefest-livecoding. Для этого примера потребовались всего две внешние библиотеки.
Не стесняйтесь клонировать этот репозиторий и адаптировать его под Ваши потребности. Я уверен, что в этом примере нет всех библиотек, необходимых для Вашего проекта. Чтобы помочь вам с более конкретными вариантами использования, на следующей неделе мы опубликуем статью со списком библиотек Go, которые Вы можете использовать для создания своих сервисов на Go. Мы пользуемся ими уже пару лет. Мы также объясним, почему мы используем эти библиотеки и как узнать, хороша ли подобная библиотека или плоха для Вас.
Когда Ваш проект станет более сложным и Вы уже знаете - как Ваши библиотеки работают вместе, Вы легко можете начать его рефакторинг. В конце концов, Вам может не понадобиться большинство функций фреймворка, которые казались критически важными на самом старте. Благодаря этому Вы можете в конечном итоге получить более простой проект.
Если Вы ищете информацию о том, как могут выглядеть более продвинутые проекты, Вам следует ознакомиться с Wild Workouts – нашим полнофункциональным примером проекта на Go. Мы выпустили бесплатную электронную книгу объемом более 200 страниц, описывающую - как мы создали это приложение.
Итого
Решение о том, как Вы будете создавать свои сервисы - это не тот вопрос, где Вам следует не думать и сразу выбирать популярный фреймворк на все случаи жизни. Принятие неправильного решения может очень негативно сказаться на Вашем времени в долгосрочной перспективе. Это негативно влияет и на скорость Вашей команды и что более важно - на моральный дух.
Приняв неправильное решение, Вы можете быстро попасть в ловушку заблуждения о заниженных трудозатратах на разработку. Вместо того чтобы становиться героями, которые решают проблемы (борясь с фреймворком), мы должны по максимум избегать возможность их появления.
ИМХО
Статья и мнение Robert Laszczak имеет конечно право на жизнь - это его опыт и виденье. Вставлю и свои 5 копеек. Каждый проект проходит стандартные станции жизни и от идеи до текущего момент могут меняться и технологии реализации и непосредственно команды (люди), которые отвечали за разработку/поддержку.
В самом начале большинству проектов нужна именно скорость разработки для подтверждения гипотез и для быстрого выхода на рынок - как итог привлечение конечных потребителей и завоевание хоть какой-то доли рынка. Когда появляются реальные потребители (aka клиенты) - растет нагрузка на сервисы и тут уже все зависит от прямых рук разработчика/архитектора. Когда уже начинаешь упираться в фреймворк - необходимо задать себе вопрос, а все ли я правильно делаю вообще, возможно надо сделать rtfm? Думаю в 99% случаев должна помочь тонкая настройка или точечная оптимизация кода.
Кроме проблем с ограничениями фреймворков, есть же и нюансы с самими языками программирования. Например если у Вас действительно высоконагруженные сервис, где Вам вплотную приходится профилировать CPU/RAM и использовать грязные хаки в Go для оптимальной работы GC. Тут мы уже возможно начинаем бороться с самим языком и может надо смотреть уже в сторону Rust?
В комментариях под постом Robert Laszczak нашлись люди, которые явно согласились с автором:
В моей компании мы использовали сервисы Go без какого-либо фреймворка. Это был приятный опыт работы после связки Java + Spring. ... через пару месяцев цикл разработки стал гладким.
Кончено были и те, которые в корне не согласны с ним:
Категорически не согласен с тем, что долгосрочные проекты страдают от использования фреймворков. Вы либо используете неправильную структуру, либо MVC не соответствует Вашим потребностям. Ограничения зрелых фреймворков и их обходные пути - как правило, хорошо известны. Я подозреваю, что Go медленно внедряет свои собственные Rails или Django из-за ограничений системы типов. К такому выводу я пришел после экспериментов с созданием достойной оболочки базы данных задолго до появления дженериков, что в первую очередь сводит на нет преимущества использования Go в производительности.
Из своего опыта скажу так, обычно всегда для своих задач использую стандартный HTTP-сервер + удобный кастомные роутинг в Go. Если проблемы с производительностью - вперед в pprof. Для проектов с маленькой нагрузкой хватает с головой и Flask + Gunicorn.
Для менеджеров проектов тут тоже все неоднозначно и сразу бросаться в полный кастом без фреймворков опасно - ведь при таком подходе на рынке не будет "готовых" специалистов, которые сразу смогут условно помочь проекту на 2ой день работы.
Необходимо так же помнить и о DevOps и о финансовом обосновании реализации того или иного сервиса. Увы, "серебрянной пули" как обычно нету и надо все взвешивать опираясь на многочисленные факторы... И легко может получиться так, что вчера было выгодно делать все на фреймворка,а сегодня уже нет и наоборот.