Pull to refresh

Создание веб-приложения на Go в 2017 году

Reading time 8 min
Views 78K
Original author: Grisha Trubetskoy
Содержание

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


Я решил воспользоваться этой возможностью и сделать такое руководство, в котором начать с нуля веб-приложение так, как это должно быть сделано в 2017 году (в моем понимании). Я провел много часов, докапываясь до тех вещей, которых раньше обычно избегал, просто для того, чтобы хотя бы раз за много лет я мог с уверенностью заявить, что у меня есть свой взгляд и опыт в этом вопросе, и получить для себя работающий рецепт, который, возможно, пригодится не только мне.


Статья начинает короткую серию, освещающаю то, что я узнал в процессе. Этот первый пост представляет собой общее введение, описывающее текущее положение дел, проблемы и то, почему я считаю Go хорошим выбором. Последующие статьи будут более детальными и содержать больше кода. Мне любопытно, насколько мой опыт коррелирует с вашим; может быть я в чем-то ошибаюсь, так что не стесняйтесь комментировать.


Если вас интересует только код, он тут.


Введение


Раньше моих базовых знаний HTML, CSS и JavaScript было достаточно для моих скромных нужд в сайтостроении. Большинство приложений, которые я когда-либо создавал, были сделаны с помощью mod_python, напрямую используя механизм публикации обработчиков (прим.пер.: пример можно посмотреть тут). Забавно, что будучи ранним последователем Python, я также немало поработал с Rails. В течение последних нескольких лет я сосредоточился на инфраструкте (больших) данных, которая вовсе не является веб-разработкой, хотя необходимость в веб-интерфейсах тут — не редкость. Фактически, приложение, которым я сейчас занимаюсь, является приложением для работы с данными, но оно не опенсорсное и то, что оно делает, не имеет значения для данной статьи. В общем, это должно прояснить, с какой стороны я на все это смотрю.


Python и Ruby


Еще год назад я бы рекомендовал Python или Ruby в качестве среды веб-приложения. Может быть есть и другие подобные языки, но с моей точки зрения, в мире доминируют Python и Ruby.


Большую часть времени главной задачей веб-приложения было конструирование веб-страниц с помощью компоновки конечного HTML на стороне сервера. Как Python, так и Ruby очень хорошо подходят для извлечения данных из БД и превращению их в кучу HTML-кода с помощью шаблонов. Существует множество фреймворков/инструментов на выбор, например, Rails, Django, Sinatra, Flask и т.д. и т.п.


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


GIL


GIL (Global Interpreter Lock) залуживает отдельного упоминания. Безусловно, это самое большое ограничение любого решения на Python или Ruby, но это очень скользкая тема, люди чаще предпочитают делать вид, что проблемы нет. А если уж речь об этом зашла, эмоции обычно бьют через край, в сообществах Ruby и Python идут бесконечные обсуждения на тему GIL.


Для тех, кто незнаком с этой проблемой — GIL позволяет выполнятся только одной вещи за раз. Когда вы создаете потоки и они ”выглядят” как параллельно выполняющиеся, на самом деле интерпретатор все еще выполняет инструкции последовательно. Это означает, что один процесс может использовать только один CPU.


Существуют альтернативные реализации, например, основанные на JVM, но они нечасто применяются. Я точно не знаю почему, возможно они не полностью совместимы или, вероятно, не поддерживают корректно C-расширения, и у них при этом все еще может быть GIL. Не уверен, но насколько я могу судить, обычно все-таки используется реализация на C. Чтобы сделать интерпретатор без GIL, придется его полностью переписать, а это уже может изменить поведение языка (в моем наивном понимании), и поэтому мне кажется, что GIL останется.


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


Обычно это делается с помощью дополнительного ПО, такого как Unicorn/Gunicorn, при этом каждый процесс слушает свой собственный порт и запускается позади какого-то балансировщика соединения, типа Nginx и/или Haproxy. Альтернативно это может быть сделано через Apache и его модули (такие как mod_python или mod_wsgi), в любом случае это сложно. Такие приложения обычно полагаюся на сервер базы данных в качестве арбитра для любых, чувствительных к конкурентности, задач. При реализации кэширования, чтобы не хранить множество копий одного и того же на одном и том же сервере, требуется хранилище с разделяемой памятью, типа Memcached или Redis, а обычно оба. Также такие приложения не могут делать фоновую обработку, для этого существует отдельный набор инструментов, такой как Resque. И потом все эти компоненты требуют мониторинга, чтобы быть уверенным, что все это работает. Логи должны быть консолидированными, и для них есть свои дополнительные инструменты. Учитывая неизбежную сложность этой настройки, также требуется наличие менеджера конфигурации, такого как Chef или Puppet. И тем не менее, эти наборы, как правило, не способны поддерживать большое количество долговременных соединений — проблема известная как C10K.


В итоге простое веб-приложение с базой данных требует целую кучу составных частей, прежде чем оно сможет обслуживать страницу «Hello World!». И почти все это из-за GIL.


Появление одностраничных приложений


Все дальше и дальше в прошлое уходит генерация HTML на сервере. Последняя (и правильная) тенденция заключается в построении пользовательского интерфейса и рендеринге полностью на стороне клиента, с помощью JavaScript. Приложения, чей пользовательский интерфейс полностью управляется JS, иногда называют одностраничным приложением и, на мой взгляд, за ними будущее, нравится нам это или нет. В таких приложениях сервер только обслуживает данные, обычно в виде JSON, не создавая HTML-кода. В этом случае та огромная сложность, введенная в первую очередь для возможности использования популярного скриптового языка [для создания веб-прилолжения], оказывается ненужной. Особенно учитывая, что Python или Ruby приносят мало выгоды, когда весь вывод — это JSON.


Взгляд на Golang


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


Программы на Go представляют собой бинарники, которые нативно запускаются, так что не требуется ничего языкоспецифического устанавливать на сервер. Исчезает проблема обеспечения правильной версии среды исполнения, требуемой приложением; отдельной среды исполнения нет — она встроена в бинарник. Программы на Go могут легко и элегантно запускать задачи в фоне, поэтому нет нужды в инструментах типа Resque. Эти программы запускаются как единственный процесс, так что кэширование становится тривиальным, а значит, Memcached или Redis не нужны. Go может управлять неограниченным количеством параллельных соединений, нивелируя надобность в фронтэндной защите, такой как Nginx.


С Go высокая многослойная башня из Python, Ruby, Bundler, Virtualenv, Unicorn, WSGI, Resque, Memcached, Redis, и т.д., и т.п. уменьшается до всего лишь одного бинарника. Единственный сторонний компонент, который обычно все еще нужен, — это база данных (я бы посоветовал PostgreSQL). Тут важно отметить, что все эти инструменты по прежнему можно использовать, но с Go можно обойтись и без них.


Время запуска такой Go-программы будет, скорее всего, на порядок превосходить любое приложение на Python/Ruby, потребует меньше памяти и строк кода.


Хорошо, а есть популярный фреймворк?


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


Надо понимать, зачем вообще были созданы фреймворки. В мире Python/Ruby так случилось потому, что эти языки не были изначально спроектированы для обслуживания веб-страниц, и для решения этой задачи необходимо было множество внешних компонентов. То же самое можно сказать и про Java, который, как и Python, и Ruby, стары как веб, каким мы его знаем, или даже немного старше.


Насколько я помню, ранние версии Python "из коробки" не предоставляли ничего для работы с базой данных, не было шаблонов, поддержка HTTP была запутанной, работа с сетью — нетривиальной, даже шифрование тогда было незаконным и в общем, много чего еще отсутствовало. Фреймворк обеспечивал все эти необходимые кусочки и устанавливал характерные для языка правила разработки для всех распространенных вариантов веб-приложений.


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


Если все вышеприведенное касаемо фреймворков звучит не достаточно убедительно, полезно рассмотреть кривую обучения для фреймворков и риски. Мне понадобилось около двух лет, чтобы наладить отношения с Rails. Фреймворки могут стать заброшенными и устаревшими, а переносить приложение на новый фреймворк тяжело, а иногда и невозможно. Учитывая, как быстро все меняется в информационных технологиях, фреймворк уж точно не следует выбирать легкомысленно.


Я хотел бы особо выделить инструменты и фреймворки, которые пытаются имитировать идиомы, общие для Python, Ruby или сред JavaScript. Все, что выглядит, или ощущается, или претендует на роль «Rails for Go», включая такие техники, как инъекции, динамическая публикация методов и т.п., которые сильно зависят от рефлексии, не вписывается в идеологию Go, поэтому лучше от такого держаться подальше.


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


Другая проблема, общая для фреймворков, это то, что они абстрагируют низкоуровневые механизмы от разработчика так, что со временем они становятся настолько загадочными, что буквально невозможно понять, что у них там на самом деле происходит. То, что начинается с лексического псевдонима для одной строки JavaScript становится слоем в слоях транспайлеров, минимайзеров, поверх хелперов, скрытых где-то в подзависимостях. Однажды что-то ломается, и невозможно понять, где искать проблему. Приятно, когда точно знаешь, что происходит, и Go в этом очень хорош.


Как насчет базы данных и ORM?


Аналогично фреймворкам, ORM'ы в Go не сильно распространены. Для начала, Go не поддерживает объекты — то, что обозначено O в аббревиатуре ORM.


Я знаю, если вместо того, чтобы пользоваться удобным User.find(:all).filter..., которое обеспечивается чем-то вроде ActiveRecord, писать SQL вручную — это нечто неслыханное в некоторых сообществах, но я все-таки думаю, что такое отношение должно измениться. SQL — прекрасный язык. Иметь дело с SQL напрямую — это не так уж сложно, а взамен мы получаем больше свободы и возможностей. Пожалуй, самой утомительной частью такой прямой работы является копирование данных из курсора базы данных в структуры, но здесь очень пригодится проект sqlx.


Заключение


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


  • Минимальная зависимость от сторонних пакетов.
  • Без веб-фреймворка.
  • PostgreSQL в качестве БД.
  • Одностраничное приложение.

Продолжение

Tags:
Hubs:
+20
Comments 44
Comments Comments 44

Articles