Best Framework?
Best Framework?

Вольный перевод статьи на ресурсе 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-разработчики предполагают, что Вам вообще не следует использовать фреймворк. Вы можете про себя сказать - они что, сумасшедшие? ????

Создание сервиса на Go из библиотек может создать ощущение создания монстра Франкенштейна
Создание сервиса на 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 и о финансовом обосновании реализации того или иного сервиса. Увы, "серебрянной пули" как обычно нету и надо все взвешивать опираясь на многочисленные факторы... И легко может получиться так, что вчера было выгодно делать все на фреймворка,а сегодня уже нет и наоборот.

Комментарии (10)


  1. baklajan
    13.12.2022 05:14
    +6

    Вообще выдавать ограничение за свободу это не правильно ("Запрет - это как раз свобода"©). Выбор фреймворков в экосистеме платформы или языка - это как раз свобода, кто-то их использует, кто-то нет. А не делать фреймворки, оправдывая это тем, что они "зло" - это пахнет выдачей желаемого за действительное.
    Но ни в коем случае не могу ни опровергнуть, ни подтвердить тот факт, что фреймворки - это зло.


  1. Virviil
    13.12.2022 11:23
    +7

    Обычно не использование фреймворка заканчивается изобретением своего собственного фреймворка, если проект разрастается. С которым в конце-концов так же приходится бороться.

    Если учесть то, что стандарты веба никто толком не знает - получаются очень часто очень топорные решения. К примеру, видел тысячи проектов на Го, которые отдают в браузер и не удосужились сделать HEAD и OPTIONS роуты. Необходимость вручную думать про CSRF, XSS, правильные роуты в соответствии с REST (которые тоже никто не знает)... А во фреймворке это из коробки, он за тебя позаботился.

    Из-за кажущейся свободы начинается полный зоопарк с http codes и error handling в API, структуры папок в которых надо вникать 2 недели после смены проекта, зоопарк решений для конфигурации.

    То же самое относится к SQL - тысячи проектов с конкатенацией строк для запросов SQL, каша из функций, raw sql без какого-либо порядка в миграциях и дата-миграциях.

    Дикий запад PHP в его худшие годы.

    И ради чего? Чтобы "гипотетически" не сражаться с фреймворком, причем на том этапе где все и так начинают переписывать, рефакторить, разбивать на микросервисы?


    1. mirecl Автор
      13.12.2022 17:40
      +1

      Автор исходной статьи имел ввиду немного другое.

      Приведу пример:

      Допустим стоит задача разработать небольшой REST c n-ручками. В Golang есть выбор:

      • Можем взять сервер из стандартного пакета net/http;

      • Если понимаем, что нагрузка будет очень большая - можем посмотреть в сторону fasthttp/fiber и тд.

      Идем дальше, нужен конечно routing:

      • Если скучно - можно написать самому???? и как раз решать все нюансы с HEAD/OPTIONS/CSRF/XSS и тд;

      • Можно воспользоваться отдельными модулями для данной задачи - httprouter/gorilla и тд.

      Куда ж без логирования - отдельный модули logrus/zap и тд.

      Продолжаем, необходимо добавить метрики Prometeus + Tracing = берем отдельные модули

      Нужно нам JWT - опять добавляем модуль + различные middelware.

      Куда же без БД????:

      • Любим ORM - идем в GORM (использовать или нет ORM - это отдельный вопрос);

      • Хотим все контролировать и не боитесь SQL - sqlx.

      Миграция - отдельный модуль или вообще можно использовать специализированное решение для этого https://flywaydb.org.

      Таким образом, мы собираем нужный функционал из отдельных модулей - которые специализированно решают свою конкретную задачу. Лишних зависимостей в сервисе не будет при таком подходе. Я привел лишь примерный набор модулей - каждый сам выбирает нужное себе.

      Обычно такие решения потом перерастают в корпоративный framework cо своей структурой и допиливаются под нужды организации (если мне память не изменяет - так как раз происходит в Авито).

      И говорить:

      Дикий запад PHP в его худшие годы.

      С таким утверждением - не согласен, так как это уже работает в больших организациях)

      Так же стоит отметить - что в Golang есть проекты, которые могут в той или иной мере подходить под определение framework:

      Подход описанный автором - я не защищаю, а пытаюсь просто донести его основную мысль.

      Если Вам комфортно работается c framework'ом и Вы не чувствуете проблем - то ок)

      Главное: чтобы Ваша привычка к framework`у и его приятной экосистеме не привело к такому сценарию, что при реализации фичи - такого функционала не окажется в framework'е и Вы просто разведете руками и скажете нельзя сделать.


      1. anayks
        13.12.2022 22:51
        +1

        Вы забыли упомянуть об использовании Query-Builder’ов а-ля Squirrel, которые однозначно имеют право на жизнь!

        А вот судя по ошибками, с которыми я столкнулся в GORM’е, на которых нет ответа, а Issue как висели без ответа, так и висят — я бы очень много раз настоятельно бы подумал еще раз, прежде чем его использовать в проекте, особенно если будет множество сложных, кастомных типов данных, которые необходимо будет брать из базы нестандартными запросами


  1. fishHook
    13.12.2022 15:45
    +2

    К сожалению, велосипедостроение интересно и увлекательно, когда у вас "молодой развивающийся коллектив" который пилит никому не нужный стартап на чистом энтузиазме разработчиков, которым ввиду молодости и восторженности просто интересно кодить и строить архитектуру приложения с нуля. Бизнес так не работает - не может себе позволить риски связанные с безопасностью, со сроками, с незаменимостью сотрудников, имеет требования по документированию и так далее. Вы вполне можете ожидать, что человек имеющий в портфолио Spring более-менее безболезненно и быстро вольется в ваш Spring проект, поэтому такого специалиста вы и будете искать. На это будет заточен ваш HR и онбординг. Вам не надо чтобы Вася, наваявший ответственный модуль, пришел к вам за неделю до мерджа и сообщил, что он прочитал новый учебник и всё надо срочно пределать. Вам не надо, чтобы ваши сотрудники, вместо того, чтобы писать код срались какой подход лучше

     Когда Ваш проект станет более сложным и Вы уже знаете - как Ваши
    библиотеки работают вместе, Вы легко можете начать его рефакторинг.

    Вот-вот, будет у вас бесконечный рефакторинг, а не развитие проекта.


  1. GoodGod
    13.12.2022 16:14
    +4

    Я не знаю что у вас за нагрузка, но обычно проблема фреймворков - не в том, что они не выдерживают нагрузку. Может для проекта мирового масштаба - это да - проблема. Но в России таких проектов очень мало, я думаю, что даже Wildberries и Ozon с их 100 миллионами посетителей в месяц могут позволить себе писать на фреймворке, а не на нативном go. Может не hot point точку куда делаются прямо все запросы, но очень много своих сервисов - могут (сервис оплаты, сервис личного кабинета, сервис доставки и т.д.).

    Основная проблема фреймворков, что либо он забрасывается (или уходит из мейнстрима и все начинают писать на другом фреймворке) или выходит v2, v3, которые "ну надо переписать весь проект, потому что v2 это совсем другой фреймворк, полностью не совместимый с первой версией".

    Пример фреймворка из Go: Gorilla Mux, раньше, когда я изучал Go мы прям изучали конкретно этот - популярный на тот момент фреймворк. Он прям был популярен, нам прям говорили - вот будете знать что нужно на рынке труда. А теперь - The Gorilla project has been archived, and is no longer under active maintainenance.


    1. mirecl Автор
      13.12.2022 17:47
      +1

      Да) Gorilla Mux буквально 4 дня назад ушла в архив


  1. guryanov
    13.12.2022 20:10

    Для обработки HTTP goswagger очень понравился, жаль что он только OAS 2.0 поддерживает.


    1. anayks
      13.12.2022 22:55

      Есть конвертеры-утилиты, позволяющие конвертировать v2 в v3, но с ними придется помучаться и вызывать их каждый раз при изменении документации. Они работают и некоторые требования, например на моем месте работы, удовлетворяют


      1. guryanov
        13.12.2022 23:25

        Это да, но в третьей версии есть возможности, которыми хочется пользоваться :)