Случается, что некоторые задачи не удаётся решить на привычном для вас языке программирования. Предположим, перед нами встала задача реализовать обмен сообщениями в реальном времени при больших объёмах трафика. Как сделать это оптимально? По понятным причинам, Руби – не лучший для этого вариант, поэтому придётся начинать поиски альтернатив. Перед вами появится множество путей, и как для рубиста, самым оптимальным выбором будет использование Эликсира.
Эликсир – функциональный язык программирования с синтаксисом Руби, работающий на виртуальной машине Эрланга. Следовательно, для человека из мира Руби знакомство с языком пройдёт очень легко. Хотя для того, чтобы понять его полностью, придётся постараться, не раз перечитывая документацию.
Ответ на вопрос из заголовка и масса интересного об Эликсире сразу же под катом.
Никогда! Феникс не убийца Рейлс, а напарник
Структура веб-приложения на обеих платформах довольно похожа. И это не удивительно: один из наиболее значимых участников сообщества Феникса – бывший член основной команды Рейлс, а большинство людей, занимавшихся разработкой фреймворка – рубисты. Кое-какие различия всё-таки присутствуют.
- Внедрение подхода а-ля «Clean Architecture» для построения приложения. Самые значимые изменения пришли вместе с последней на данный момент версией Феникса 1.3. Подробнее об этом можно узнать из этой статьи. Отделение частей Феникса от частей приложения, ограничение бизнес-логики контекстами, использование репозиториев для работы с базой данных – всё это не типично для мира Рейлс.
- Зонтичные приложения aka Umbrella app – идея отказа от преждевременного использования микросервисов и разделение объёмной бизнес-логики на части внутри одного большого приложения. Подробнее об этой возможности Эликсира читайте в этой статье.
- Представления в Фениксе называются шаблонами и больше похожи на декораторы, но теоретически это не должно вызвать особых трудностей, хотя и меняет идею взаимодействия, делая её более походящей на популярный сейчас Ханами.
- Ещё одна из особенностей Феникса – каналы. При каждом подключении к каналу создаётся новый процесс, который в последствии при возникновении ошибки никак не повлияет на другие подключения. Более подробно мы рассмотрим эту тему далее. Кроме того, Феникс предлагает фронтенд-библиотеку с отличными абстракциями для соединений с неструктурированным веб-сокетом.
- Все прелести языка Эликсир, о которых будет рассказано далее.
Также присутствует огромное количество мелких различий, начиная от встроенных инструментов для работы с фронтендом, заканчивая отличными от Рейлс идеями и философией.
Виртуальная машина Эрланга и с чем её едят
Руби – объектно-ориентированный язык программирования. Это интерпретируемый язык, созданный Юкихиро Мацумото с расчётом на продуктивность и простоту. Эликсир же – функциональный язык, созданный Жозе Валимом и работающий на виртуальной машине Эрланга, называемой BEAM
. Программы компилируются в байт-код для BEAM
, которая выглядит как один процесс операционной системы, но на самом деле таит в себе целую вселенную.
BEAM
работает на сервере непрерывно. Процесс Руби (и Рейлс) запускается только тогда, когда сервер начинает обрабатывать запрос. Поэтому, если, к примеру, необходимо выполнять какое-то действие каждый час, в среде Рейлс придётся использовать CronJob
, в то время как в Фениксе об этом позаботится маленькая Эликсир-программка внутри BEAM
.
defmodule Stack do
use GenServer
# Callbacks
def handle_call(:pop, _from, [h | t]) do
{:reply, h, t}
end
def handle_cast({:push, item}, state) do
{:noreply, [item | state]}
end
end
# Start the server
{:ok, pid} = GenServer.start_link(Stack, [:hello])
# This is the client
GenServer.call(pid, :pop)
#=> :hello
GenServer.cast(pid, {:push, :world})
#=> :ok
GenServer.call(pid, :pop)
#=> :world
В Эликсире для выполнения задачи существует возможность создать новый процесс (который в магическом мире Эрланга ведёт себя скорее как тред, хотя по сути не является ни тем, ни другим), тогда как в Руби в большинстве своём приходится помещать фоновые задачи в отдельную очередь (например, через Sidekiq
), чтобы можно было осуществлять масштабирование независимо от веб-приложения.
Поскольку процессы являются изолированными, а при обмене сообщениями создаётся новая копия данных, больше не придётся беспокоиться о повреждении или изменении данных по ту сторону системы. Операции внутри процесса синхронные, поэтому порядок его выполнения намного адекватнее, нежели в той же Ноде. Ещё одно преимущество – эффективный сборщик мусора. Как только процесс «отработал своё», он освобождает память для ОС.
Потоки Руби (JRuby
, Rubinius
) имеют свойство взаимодействовать с данными других потоков, что впоследствии достаточно проблематично отладить. В таких случаях довольно быстро развивается состояние гонки.
Работа с веб-сокетами
Приложения реального времени (например, чаты) используют канал обмена данными (например, веб-сокет). Создавать такие с помощью Феникса – проще простого. С Рейлс это тоже возможно, но компонент ActionCable
, обслуживающий соединения с веб-сокетами, едва ли справится с первой тысячей одновременных подключений. С точки зрения Феникса, алгоритм ActionCable
для отправки сообщений от сервера к клиенту до смешного прост, ненадёжен и плохо масштабируем. Чтобы вы понимали, насколько выигрышнее в данном случае использовать Эликсир и Феникс: приложение сможет спокойно обрабатывать пару сотен тысяч одновременных подключений от одного сервера.
Справедливости ради, если вы столкнулись с проблемами кабеля Рейлс на работающем приложении, советую взглянуть на AnyCable
, одна из реализаций которого сделана как раз на Эрланге. Тем самым вы получите преимущества BEAM
на Рельсовом приложении.
Запуск объёмных веб-приложений с BEAM
особо приятен: масштабирование на многочисленных серверах проходит очень легко. Создавая проект на Фениксе, можно вообще забыть о проблемах с масштабированием. Кстати об этом… Вы же знаете, что сервер WhatsApp
тоже работает на BEAM
?
~~~
Многие откровенно заявляют, чтобы сделать приложение масштабируемым, необходимо использовать Эликсир, ибо Руби и Рейлс на это не способны.
Это крайне недальновидный совет, показывающий, что его автор не совсем знаком с понятием масштабирования. Пусть Руби и не самый шустрый язык программирования, но отмасштабировать типовое Рейлс-приложение можно без особых усилий. Такое приложение обычно основано на архитектуре без разделения ресурсов, что в большинстве случаев позволяет увеличить общее число обрабатываемых запросов в секунду простым добавлением серверов.
Может, и не самый дешёвый вариант по части железа, но точно масштабируемый!
~~~
Функциональное программирование vs ООП
Иногда Эликсир слегка напоминает Руби. Это ощутимо сбивает с толку, ведь руби принадлежит миру объектно-ориентированного программирования, а Эликсир построен на принципах функционального. Это же две параллельные вселенные! Если вы с Руби уже давно перешли на «ты», то для понимания Эликсира всё равно потребуется время.
Феникс – это не просто шустрый Рейлс, написанный на Эликсире
Давайте рассмотрим, что представляет собой процесс отправки сообщений в Эликсире, с помощью следующего модуля:
defmodule Example do
def start_link do
spawn(fn() -> loop() end)
end
def loop do
receive do
{:hello, pid} -> IO.inspect("Got hello from #{inspect pid}")
end
loop()
end
end
А теперь откроем IEx
:
iex(1)> pid = Example.start_link()
#PID<0.120.0>
iex(2)> send pid, {:hello, self()}
"Got hello from "#PID<0.118.0>"
"#PID<0.118.0>"
{:hello, #PID<0.118.0>}
Смахивает на ООП, не правда ли? В сообществе Эрланга/Эликсира даже с юмором отмечают, что эти два языка больше соответствуют концепциям ООП, чем другие популярные языки программирования. Так значит, Эликсир – объектно-ориентированный язык? Ничего подобного. Но об этом чуть позже.
Преимущества обмена сообщениями состоят в том, что отправитель известен заранее, а взаимодействие процессов можно контролировать. Процессы визуализации и отладки становятся значительно проще по сравнению с тем, что происходит при обычных методах управления параллельными потоками, таких как блокировки и мьютексы.
В отличие от объектного Руби, код на функциональном Эликсире состоит из модулей, каждый из которых включает в себя набор функций, обычно нацеленных на решение определённого типа задач. Огромный плюс Эликсира в том, что результаты тех или иных действий предсказуемы.
Аргументы каждой функции прописываются заранее, что избавляет разработчика от возникающих в дальнейшем трудностей, например, с переменными экземпляра. Это также помогает избежать указанной выше проблемы, когда некая часть системы взаимодействует с не предназначенными для неё данными.
Конкурентность
Про Руби можно сказать, что конкурентность в нём хромает. Да, в некоторых версиях Руби поддерживается потоковая конкурентность, но встроенный в MRI
(оригинальный Руби, написанный на языке Си) способ синхронизации потоков GIL
препятствует конкурентному выполнению задач. Нужно отметить, что Руби всё-таки позволяет осуществлять конкурентные операции ввода-вывода.
Эликсир же просто создан для этого. В прямом смысле. Конкурентность в нём реализована на основе так называемой «модели акторов» – концепции, реализованной виртуальной машиной Эрланга ещё до того, как этот термин вообще увидел свет.
Идея заключается в том, что можно создавать изолированные процессы, взаимодействующие между собой посредством обмена сообщениями и при этом не влияющие на состояния друг друга. Пример такой модели вы видели в предыдущем подразделе.
Нужно отметить, что Эликсир нравится рубистам обычно ничуть ни меньше, чем когда-то понравился сам Руби. Проблемой здесь может стать переключение с одного языка на другой. Каждый из них по-своему хорош и привлекателен, но писать одновременно на двух языках невозможно. Придётся сделать выбор.
Работа с базами данных
Давайте сравним самые популярные инструменты для работы с базами данных в Руби и Эликсире соответственно – ActiveRecord
и Ecto
. И тот, и другой позволяют делать запросы и использовать валидации, но всё же функционируют совершенно по-разному. Для рубистов ActiveRecord
прост и понятен в использовании, кроме того, совсем не требует знания SQL
. История с Ecto
совершенно противоположная: разобраться в нём сложнее, плюс необходимо базовое знание SQL
.
defmodule Sample.App do
import Ecto.Query
alias Sample.Weather
alias Sample.Repo
def keyword_query do
query = from w in Weather,
where: w.prcp > 0 or is_nil(w.prcp),
select: w
Repo.all(query)
end
def pipe_query do
Weather
|> where(city: "Krakow")
|> order_by(:temp_lo)
|> limit(10)
|> Repo.all
end
end
Можно сказать, что ActiveRecord
– секретное оружие Рейлс (от которого правда все стали воротить нос). Про Ecto
такого не скажешь. Ecto
хорош, но Феникс и без него на высоте, однако Рейлс без ActiveRecord
не был бы самим собой.
Гемы vs пакеты
Гем – это созданный другим разработчиком код, который с легкостью можно интегрировать в своё приложение. По сути обычная библиотека. Большинство рубистов в своих проектах постоянно пользуются одним и тем же набором гемов. Можно сказать, что код без гемов – это как еда без соли. Чтобы оценить всю мощь экосистемы гемов, загляните сюда.
В мире Эликсира такие кусочки программ от сторонних разработчиков называются пакетами. Экосистема пакетов пока не доросла до масштабов экосистемы гемов (подробнее по ссылке). Судя по тому, как работают над Фениксом и Ecto
, пакеты не должны создавать проблем с обновлениями в будущем. Но сказать точно можно будет только спустя пару лет.
Скорость
Можно с уверенностью сказать: Феникс быстрее. Намного быстрее, чем Рейлс.
На просторах сети можно найти множество информации о различных тестах производительности, подробных и не очень. Забавно, но при попытке нагрузочно протестировать и «перегрузить» сервер с Эликсиром, скорее откажет тестирующая машина, даже несмотря на отличные показатели мощности.
А скорость разработки в Руби и Эликсире (а также на Рейлс и Фениксе) занимает приблизительно одинаковое время. Разумеется, переходя с одного фреймворка на другой впервые, нужно понимать, что потери времени неизбежны.
Быстрое развертывание без простоя
Для большинства веб-приложений небольшое время простоя при обновлении ПО не вызывает никаких проблем. Многие компании пытаются устранить 1-2 минутный простой даже не догадываясь о том, что они выкидывают деньги на ветер. Хотя в некоторых случаях развертывание без простоя действительно необходимо.
Придётся попотеть, чтобы решить эту задачу в среде Руби, а Эликсир справится с ней на ура: в BEAM
специально для этого есть встроенный функционал – горячая замена кода. Для многих это становится чудом. Вот только скорее всего придётся отказаться от Докера. Короче говоря, одни плюсы.
Но не спешите радоваться: если обновление включает в себя миграцию базы данных, добиться отсутствия простоя для обоих технологий будет не так просто.
Юкихиро vs Жозе Дэвид vs Крис
Юкихиро Мацумото и Жозе Валим по-настоящему значимые люди, каждый из которых сделал очень много для своего языка и сообщества в целом. Но давайте сейчас подробнее поговорим о создателях движущих сил каждого из языков. Над такими проектами с открытым кодом, как Рейлс и Феникс, как правило, работают большие команды разработчиков. В подобных командах ключевую роль обычно играют создатели продуктов. Они определяют направление развития и ведут публичную деятельность.
Создатель Рейлс Дэвид Хайнемайер Хенссон aka DHH выступает с докладами с самого первого дня своего детища. Он отличный оратор. Ранее его доклады на конференциях РейлсКонф всегда открывали глаза на многие вещи. Сейчас он немного сдал позиции, но его вклад всё равно колоссален.
Дэвид умеет придать наглядность сухим примерам. Помимо программирования он занимается фотографией и автоспортом. Но всё же его способностям «оживить» программный продукт или книгу (да, он ещё и писатель) сложно составить конкуренцию.
Дэвид может показаться высокомерным, но его можно понять. Сообщество Рейлс намного обширнее сообщества Феникса, и Дэвид просто физически не смог бы пообщаться на конференциях с каждым желающим, как это делает Крис.
Не то чтобы деньги были показателем успеха, но Дэвид стал мультимиллионером благодаря своей компании Basecamp, работающей на стеке Руби и Рейлс.
Создатель Феникса Крис МакКорд тоже с самого начала выступает публично. Крис был членом сообщества Рейлс, но как-то раз не нашёл решения нескольких вопросов и ради этого перешёл на Эликсир. Чувствуется, что он увлечён своим творением не меньше, чем когда-то Дэвид своим. На конференциях Крис более лоялен к новичкам; его доклады более адаптированы для неподготовленного слушателя.
Крис работает в компании DockYard, руководитель которой собрал впечатляющую команду эликсирщиков. Также у компании имеется классный блог, многие статьи из которого мы переводим для нашего сайта.
Основная проблема обоих фреймворков
Самая большая трудность – поиск хороших разработчиков, что актуально как для Феникса, так и для Рейлс. Хотя для Феникса эта задача стоит гораздо острее. И это огромная проблема. Зачем нужен безупречный фреймворк, если некому будет в нем работать? Возможно, с Фениксом стоит подружиться первоклассным PHP-разработчикам?
Сегодня об Эликсире говорят все и везде. Самое время начать его изучать – прямо сейчас
Движущая сила технологий – это мы с вами, так что присоединяйтесь к разработке на Эликсире как можно скорее!
А какой фреймворк выбрали бы вы?
Подводя итоги, можно сказать, что Феникс предлагает немного более мощные технологии, но это ещё не показатель успеха проекта. Что действительно важно, так это наличие разработчиков, прекрасно разбирающихся в фреймворке, с которым они работают.
Если вы уже продолжительное время ведёте проект на Рейлс, будьте крайне осторожны при переходе на Феникс. На чужом дворе трава всегда зеленее. На своей практике встречаются удачные переносы, но почти всегда ими занимались мастера своего дела.
Если у вас уже есть команда талантливых Рейлс-разработчиков, желающих попробовать что-то новенькое, то стоит организовать какой-нибудь сайд-проект на Фениксе, понаблюдать, что из этого выйдет, а потом уже решать, стоит ли игра свеч.
Эликсир и Феникс можно использовать и для создания обычных CRUD-приложений, если ваша компания этого хочет по каким-то причинам, будь то функциональное программирование, наличие только одного сервера, новизна языка и т. п.
Если же скорость и развёртывание без простоя жизненно необходимы вашему проекту, то с Фениксом вам точно по пути.
И что бы вы ни выбрали, знайте, здесь нет неправильного варианта.
Заключение
Данная статья – это адаптация компиляции трёх материалов:
- Phoenix is better but Rails is more popular / Stefan Wintermeyer
- So why Elixir + Phoenix? / Dainis Lapins
- What are some use cases where Elixir/Phoenix is more advantageous than Ruby/Rails? / Hendrik Mans
Над переводом работала Надежда / tresstensel.
Адаптацию материала выполнил Ярослав / jarosluv.
P.S. Всех заинтересовавшихся приглашаем на митап по Эликсиру в Рамблере 19 октября в 19:00. Для жителей других городов и стран будет организована онлайн-трансляция.
P.P.S. Ответы на вопросы по Эликсиру в Телеграме.
Комментарии (11)
aelaa
06.10.2017 12:06Спасибо что не «Раби»
Не переводите на русский названия, пожалуйста. Ещё и в половине мест Руби, в другой Ruby.
Что плохого в словах Ruby, Elixir, Rails, Phoenix?jarosluv Автор
06.10.2017 12:09Потому что это русский язык, а на русском мы говорим «Руби», «рубист», «хочу писать на Эликсире». А по поводу того, что в половине мест написано на английском: проверил и оказалось, что ОДИН раз в статье действительно написано «Ruby», поправил. Но зачем же так передёргивать?
aelaa
06.10.2017 12:22+2А ещё на русском мы говорим «работает в Бейзкампе» и «Круд-приложения». Не говоря про «Экто» и «Эктиврекорд».
Иностранные имена собственные не переводятся. Статья сильно теряет читабельность.
Это не критично разумеется, но неудобно, думаю не только мнеjarosluv Автор
06.10.2017 12:28+1Спасибо, я понял вашу позицию. Если станет понятно, что большинству так читать неудобно, то будем оставлять названия на оригинале. Пока же мы имеем противоположное мнение.
CRUD
– аббревиатура, не переводим.Ecto
,ActiveRecord
– технически специфические термины, названия библиотек, не переводим. Такие названия специально выделяем тегом< code >< /code >
. И хоть дизайн Хабра не выделяет их в рамочку, как это принято, но хотя бы применяет моноширинный шрифт.
erlyvideo
07.10.2017 06:33иностранные имена переводятся без каких-либо проблем. Вам может показаться, что это так, если вы познакомились с руби вчера. Лично я знаком с руби и рельсами уже больше 11 лет и мне немного смешно слышать, что я обязан писать их по-английски.
Zaruki
06.10.2017 12:39Где найти хорошие доки по Фениксу?
jarosluv Автор
06.10.2017 12:42+1На русском пока нигде. Как только закончим перевод документации по самому Эликсиру, постараемся взяться за Феникс.
Вообще в качестве вводного курса можно пройти серии статей «Делаем блог на Фениксе» и «Клон Трелло на Фениксе и Реакте», а затем уже искать ответы на вопросы в официальной документации.
j_wayne
> Процесс Руби (и Рейлс) запускается только тогда, когда сервер начинает обрабатывать запрос. Поэтому, если, к примеру, необходимо выполнять какое-то действие каждый час, в среде Рейлс придётся использовать CronJob.
Автор давно не писал rails app видимо. Или не так выразился.
> A Ruby (on Rails) process only runs when the web server starts it to answer a request.
В настоящее время, на практике, процесс рейлс стартует единожды, до следующего перезапуска (скажем, в результате редеплоя). По крайней мере в passenger. Не говоря уж о встроенных серверах типа thin, puma итп. Я так и представил себе, как пользователь ждет каждый раз от 3 сек. до нескольких минут…
Другое дело, что действительно, rails представляет возможность лишь обработать запрос. Из коробки, нельзя просто так взять и запустить фоновую задачку в рамках того же процесса (потоки? Здесь это не лучшая идея).
Помимо cron jobs, используются исполнители фоновых задач, такие как delayed_job и sidekiq. Грубо говоря, это еще один rails процесс, который не выполняет веб-запросы, а периодически поллит хранилище задач. Задачи создаются в основных процессах. Существуют планировщики поверх этих исполнителей, например — github.com/moove-it/sidekiq-scheduler
P.S. феникс/эликсир понемногу пробую, пока еще не распробовал, но кажется довольно интересным.
jarosluv Автор
Спасибо за комментарий. Попробую ответить.
В статье я не вижу противоречий. Для периодического запуска задач на Руби, в любом случае, понадобится некая «cron-like» библиотека, это не обязательно сам Юниксовский `cron`. Вы далее в своём комментарии правильно уточняете, что эту задачу можно решить и через «отложенные задачи» и «планировщики к ним».
Теперь по поводу старта процесса. Возьмём для примера сервер приложения `Unicorn`. Он создаёт постоянно висящий мастер-процесс и на каждый запрос действительно форкает дочерние. Конечно же само приложение не перезапускается для каждого клиента :) В целом в этой фразе нет противоречия.
j_wayne
Спасибо за уточнения. Просто по тексту может сложиться впечатление, что процесс запускается каждый раз, с нуля.
neck_varentsov
это неверно, дочерние процессы — воркеры создаются единожды