- PVSM.RU - https://www.pvsm.ru -

Что больше всего бросилось в глаза заядлому рубисту, когда он только только начал изучать Elixir с Phoenix-ом.
Я человек простой и глубоко лезть не буду. Посему, будут различия рабоче-крестьянского уровня, а про разницу на уровне запуска приложения, про принципы работы виртуальной машины Erlang'а и протокол OTP ничего сказано не будет.
Elixir/Phoenix очень похож на Rails и одновременно совсем не похож на него. Как некоторые английские фразы: по отдельности слова знакомые, а вместе — непонятно.
Думать на руби и пытаться писать на эликсире — это тяжело. Регулярно заходишь в тупики, ибо то что ты хочешь делается совсем не так, как привык делать… либо, на самом деле, ты этого вообще не этого хочешь.
А в остальном, про различия Erlang и Ruby люди книги пишут, поэтому буду краток. Для меня главные засады были с заменой рельсовых "паровозов" на пайпы, с переориентированием на функциональщину (благо был старый опыт Haskell'я и общая любовь к inject/foldr) и с, субъективно, более строгими требованиями к типам данных (хотя, официально, оба языка со строгой динамической типизацией).
Паттерн-матчинг никакого удивления не вызвал и я так и не понял, почему про него столько разговоров. Просто интересный инструмент.
В Эликсире всё лежит в модулях. Никакого глобального скоупа. Навевает C#.
Иными словами: рельса плоская донéльзя и местами мешает сделать иерархию (помню были когда-то баги с контроллерами, лежащими в модулях). Эликсир — наоборот, всё по модулям. В рельсе назначение объекта угадываешь по родительскому классу, а в эликсире — по полному названию класса/модуля.
С одной стороны — это то, чего мне иногда не хватало в рельсе. Так как можно найти добрую половину ошибок прямо при компиляции, а не в рантайме на продакшене. С другой стороны, на компиляцию нужно время. Но с третей стороны, его нужно немного, а больших проектов на эликсире я пока не видел (да и не по заветам эрланга писать большие монолиты). В довершении, ребята из эликсира отлично поработали над динамической перезагрузкой кода и страницы. И пока что, скорость работы вкупе с отсутствием богомерзких zeus/spring мне греет душу.
Конечно же это и минусы порождает, но они вылезают сильно позже. Где-то в районе продакшн окружения и деплоя. Об этом будет ниже.
Тут же интересный момент, который физически не может случиться в рельсе: миграции и прочие штуки, которые в rails через rake, в elixir требуют компиляции проекта и может случиться что-нибудь вроде: забыл написать роуты, на них ссылается path-хелпер во вьюхе, а отвалились миграции. Поначалу — дико непривычно.
Сайт с документацией эликсира [2] выглядит гораздо бодрее рубидока и апидока. Но вот объём документации и примеры — это то, в чём ruby/rails далеко впереди. В Elixir сильно не хватает примеров на всё, что чуть сложнее табуретки. Да и описание некоторых методов, по сути, не ушло дальше сигнатуры. Мне, как приученному рубями к обилию примеров и описаний, было сложно с некоторыми методами эликсира. Иной раз приходилось долго тыкаться и экспериментировать, чтобы понять как пользоваться тем или иным методом, ибо язык знаю не так хорошо, чтобы свободно читать исходники пакетов.
Как говорится "with great power comes great responsibility". С одной стороны можно натворить вакханалию и разложить объекты так, что враг точно не пройдёт. А с другой стороны можно именовать пути более логично и наглядно, добавляя логические уровни директорий, которых нет в иерархии классов. В частности, можно вспомнить trailblazer и ему подобных с идеей объединения всего, что связано с экшеном, в одном месте. В эликсире это можно сделать без сторонних библиотек и кучи классов просто правильно переложив существующие файлы.
Если в Rails вопрос про rack — это непременный атрибут любого собеседования, ибо рельса — это верхушка айсберга и периодически хочется сделать свой middleware. То в эликсире такого желания не возникает совсем (хотя может я ещё молод и всё впереди). Там есть явный набор pipeline, через который проходит запрос. И там явно видно где фетчится сессия, где обрабатывается flash-messge, где csrf валидируется и всем этим можно управлять как вздумается в одном месте. В рельсе всё это хозяйство частично прибито гвоздями, а частично разбросано по разным местам.
В Rails, ситуация когда один экшн может отвечать в нескольких форматах — это норма. Там даже (.:format) заложен прямо в роуты. В эликсире, из-за вышеозначенного свойства с pipeline, мысли об аналоге format вообще не появляется. Разные форматы идут по разным pipeline и имеют разные url'ы. По мне так это здо́рово.
Это вообще сказка. Как опишешь поля модели, так и будет. Никакого неявного каста типов. Плюс нет костылей, чтобы запретить доступ к полю, которе есть в БД, но его по каким-либо причинам нельзя использовать в веб-приложении.
В эликсире нет колбэков. Там всё более прямолинейно. И, кажется, мне это нравится.
Вместо rails-way в эликсире changeset [3], который совмещает в себе strong_parameters, валидации и немного колбэков. А остатки колбэков идут через Multi [4], который даёт возможность набрать кучу операций, транзакционно их выполнить и обработать результат.
Короче, всё просто по-другому. Сначала это непривычно. Потом местами дико бесит, ибо нельзя просто ещё один колбэк для всего воткнуть и не думать о разных бизнес-кейсах. А потом начинаешь замечать "необъянимую прелесть" [5], ибо приходится делать правильно, а не как привык.
Вместо ActiveRecord появился некие Ecto.Repo [6], Ecto.Query [7] и ещё несколько их собратьев. Рассказывать все отличия — это отдельная статья получится. Поэтому скажу основные субъективные ощущения.
В дебаге удобнее AR. Так как там общий скоуп, константы из load path подгружаются при обращении к ним и можно просто открыть rails c, написать User.where(email: 'Kane@nod.tb').order(:id).first и получить результат.
В Elixir'е консоли недостаточно. Нужно сделать ряд действий:
import Ecto.Query, only: [from: 2];alias MyLongApplicationName.User — чтобы вместо MyLongApplicationName.User писать просто User;alias MyLongApplicationName.Repo — аналогично для обращения к классу, умеющему выполнять sql и отдавать результаты;from(u in User, where: u.email == "Kane@nod.tb") |> Repo.oneС другой стороны, в коде приложения эти "формальности" дают более читаемый код, плюс есть ощущение, что ты контролируешь происходящее, а не оно живёт своей жизнью. То есть ты сам выбираешь какие методы, модели и другие объекты, нужны для работы, явно их подгружаешь и пользуешься.
По образу и подобию Rails я полагал, что имя приложения используется в паре конфигов и всё. Поэтому на длину названия внимания не обратил. А зря. В Elixir модуль с названием приложения — это верхний уровень в иерархии модулей веб-приложение и он будет фигурировать везде.
Я вот назвал свою песочницу Comindivion. И теперь немного страдаю, так как это довольно длинное название и писать его нужно постоянно. Как в файлах классов, так и в консоли при вызове чего угодно. Кстати да, кому интересно, вот песочница на GitHub [8].
В Rails мы её имеем "из коробки", а в Elixir "из коробки" такой проблемы нет. Там на этапе сборки запроса можно указать какие реляции понадобятся и они будут загружены в ходе выполнения этого самого запроса. Не загрузил? Не будет у тебя доступа к этой реляции. Всё просто и красиво.
Если коротко: в фениксе всё более явно, нежели в рельсе.
Так как состояние не хранится в куче разных объектов, его приходится таскать за собой в одном объекте. Напоминает request из ActionController, только более всеобъемлющий. Зовётся он в Фениксе connection. Содержит вообще всё: и request, и flash, и session и всё всё всё. Он же фигурирует в вызове всего, что связано с обработкой пришедшего запроса.
Тут и минусы, так как попервой очень лениво лепить везде conn и не до конца понимать зачем. Рельса в этом плане развращает. Ты пишешь render или flash и нет мыслей о том, что это действие с соединением. А в Phoenix conn постонно напоминает о работе с конкретным соединением или сокетом, а не просто методы вызываются и там внутри магия происходит.
В Фениксе нет разделения на partial и template. В конечном итоге всё функция. Тут же кроется ещё одна прелесть: рельса даже в прод окружении постоянно лезет за вьюшками на диск и порождает IO плюс оверхед на их преобразование из erb/haml/etc в html. А в Elixir всё функция, и вьюшки в том числе. Скомпилили вьюшку разок и всё: получает аргументы, выплёвывает html, на диск не ходит.
В Rails под view понимают партиал и темплейты, а в Phoenix они лежат в templates, а во views, грубо говоря, обитают разные способы представления данных. В частности, там лежат "переопределения" render'а.
То есть, по умолчанию, контроллер ничего не рендерит. Всё вызывается явно. А если у вас нет партиала и он вам особо не нужен (например в случае с json, когда он легко билдится сервисным классом), вы переопределяете рендер как-нибудь так:
def render("show.json", %{groups: groups}) do
%{
groups: groups
}
end
И партиал больше не нужен.
В Phoenix их нет. И это круто! Ибо в рельсовых хелперах, обычно, собирается всякий хлам, который либо лениво было распихивать по углам, либо просто нужно было быстренько чего-нибудь накодить.
Однако, методы в контроллер, представления и тд. добавлять можно. Делается это в специальном месте web/web.ex и выглядит довольно прилично.
В девелопменте всё как обычно, разве что в фениксе ещё прикрутили live reload, попервой вызывающий "Уау!" эффект. Это когда поменял css, вернулся в браузер, а там изменения уже сами подгрузились.
В продакшене в Phoenix поведение статики немного иное, нежели у рельсы. По-умолчанию, явно прописаны места, откуда можно тащить статику и нельзя просто так добавить файликов в ассеты, чтобы раздавать их. Ещё есть маппинг дефолтных ассетов, чтобы лишний раз по ФС не блужлать, а сразу брать нужный файл и отдавать его.
Из коробки в Фениксе — brunch [9]. Можно заменить на webpack [10]. Но есть довольно правдивая шутка про то, что многие проекты загибаются на этапе настройки webpack'а.
Короче, js и css более-менее собираются, а вот с остальной статикой в бранче не очень. Её либо копипастирь руками прямо в проект из node_modules (мне этот вариант совсем не нравится), либо писать хуки на баше. Например, так [11].
"Из коробки" в Фениксе идёт маленький http-сервер, называемый cowboy [12]. С виду напоминает рубийную пуму [13]. У них даже количество звёздочек на GitHub примерно одинаковое. Но как-то мне не зашла настройка SSL ни в одном из вышеозначенных. Особенно вместе с Let's Encrypt [14], доп.файлом конфига веб-сервера и регулярным обновлением сертификата. Так что как http-сервер — ок, а для ssl беру прокси на localhost через apache/nginx.
Он вообще другой, по сравнению с рельсой. В Rails, в минимальном варианте, склонил репку на сервер, поплясал с бубном для бандла, конфигов, ассетов и запустил приложение. А эликсир же компилится и закопать трамвай склонить репку не прокатит. Нужно собирать пакет. И тут начинается:
mix.exs, ибо без правильно их указания в проде чудесные ошибки;RELX_REPLACE_OS_VARS=true и немного отпускает;А потом, как с вышеописанным разберёшься, начинаются плюсы:
В Elixir есть Pry [17] и работает аналогично рубям. Даже есть аналог rails c, выглядящий как iex -S mix.
Но в продакшене консолью пользоваться приходится иначе, так как пакет собран и mix в нём нет. Приходится подключаться к работающему процессу. Это радикально отличается от рельсы и в начале тратишь много времени на гуглинг способа запуска эликсир-консоли в продакшене, ибо ищешь что-то аналогичное рельсе. В итоге понимаешь что делать всё нужно иначе и вызываешь что-то вроде: iex --name trace@127.0.0.1 --cookie 'from_env' --remsh 'my_app_name@127.0.0.1'.
Фух, по-любому что-нибудь забыл. Ну да ладно. Лучше вы рассказывайте, что вас удивило в Elixir, в сравнении с другими языками.
Автор: Loriowar
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ruby/292854
Ссылки в тексте:
[1] мышления: http://www.braintools.ru
[2] документацией эликсира: https://hexdocs.pm/
[3] changeset: https://hexdocs.pm/ecto/Ecto.Changeset.html
[4] Multi: https://hexdocs.pm/ecto/Ecto.Multi.html
[5] "необъянимую прелесть": https://i.ytimg.com/vi/IsE7tudS7v0/hqdefault.jpg
[6] Ecto.Repo: https://hexdocs.pm/ecto/Ecto.Repo.html
[7] Ecto.Query: https://hexdocs.pm/ecto/Ecto.Query.html
[8] вот песочница на GitHub: https://github.com/Loriowar/comindivion
[9] brunch: https://brunch.io/
[10] webpack: https://webpack.js.org/
[11] так: https://github.com/Loriowar/comindivion/blob/master/brunch-config.js#L76
[12] cowboy: https://github.com/ninenines/cowboy
[13] пуму: https://github.com/puma/puma
[14] Let's Encrypt: https://letsencrypt.org/
[15] relx: https://github.com/erlware/relx
[16] eDeliver: https://github.com/edeliver/edeliver
[17] Pry: https://elixir-lang.org/getting-started/debugging.html#iexpry0-and-iexbreak2
[18] Источник: https://habr.com/post/423459/?utm_source=habrahabr&utm_medium=rss&utm_campaign=423459
Нажмите здесь для печати.