Відмінності Phoenix і Rails очима новонаверненого

Що найбільше впало в очі завзятому рубисту, коли він тільки-тільки почав вивчати Elixir з Phoenix-му.

Примітка

 

Я людина проста і глибоко лізти не буду. Тому, будуть відмінності робітничо-селянського рівня, а про різницю на рівні запуску програми, про принципи роботи віртуальної машини Erlang’а і протокол OTP нічого сказано не буде.

 

Головне враження

 

Elixir/Phoenix дуже схожий на Rails і одночасно зовсім не схожий на нього. Деякі англійські фрази: окремо слова знайомі, а разом — незрозуміло.

 

Erlang vs Ruby

 

Думати на рубі і намагатися писати на еліксир — це важко. Регулярно заходиш в тупики, бо те що ти хочеш робиться зовсім не так, як звик робити… або, насправді, ти цього взагалі не цього хочеш.

А в іншому, про відмінності Erlang і Ruby люди книги пишуть, тому буду лаконічним. Для мене головні засади були з заміною рейкових “паровозів” на пайпи, з переорієнтування мислення на функциональщину (благо був старий досвід Haskell’я і загальна любов до inject/foldr) і з суб’єктивно більш суворими вимогами до даних типів (хоча, офіційно, обидві мови з суворою динамічною типізацією).

Патерн-матчинг ніякого подиву не викликав і я так і не зрозумів, чому про нього стільки розмов. Просто цікавий інструмент.

 

Загальний скоуп

У Еліксир все лежить в модулях. Ніякого глобального скоупа. Навіває C#.

Іншими словами: рейки плоска донезмоги і місцями заважає зробити ієрархію (пам’ятаю були колись баги з контролерами, що лежать в модулях). Еліксир — навпаки, все по модулям. В рейці призначення об’єкта вгадуєш по батьківського класу, а в еліксир — за повного назві класу/модуля.

 

Компілюємість

З одного боку — це те, чого мені іноді не вистачало в рейці. Так як можна знайти добру половину помилок прямо при компіляції, а не в рантайме на продакшені. З іншого боку, на компіляцію потрібно час. Але з третьої сторони, його потрібно трохи, а великих проектів на еліксир я поки не бачив (та й не за заповітами ерланга писати великі моноліти). У довершенні, хлопці з еліксиру відмінно попрацювали над динамічним перезавантаженням коду сторінки. І поки що, швидкість роботи укупі з відсутністю богомерзких zeus/spring мені гріє душу.

Звичайно ж це і мінуси породжує, але вони вилазять сильно пізніше. Де-то в районі продакшн оточення і деплоя. Про це буде нижче.

Тут же цікавий момент, який фізично не може трапитися в рейці: міграції та інші штуки, які в rails через rake, в elixir вимагають компіляції проекту і може статися що-небудь начебто: забув написати роути, на них посилається path-хелпер під в’юсі, а відвалилися міграції. Спочатку — дико незвично.

 

Документація

 

Сайт з документацією еліксиру виглядає набагато бадьоріше рубідока і апідока. Але от обсяг документації та приклади — це те, в чому ruby/rails далеко попереду. У Elixir сильно не вистачає прикладів на все, що трохи складніше табуретки. Та й опис деяких методів, по суті, не пішло далі сигнатури. Мені, як приученному рубями до великої кількості прикладів і описів, було складно з деякими методами еліксиру. Інший раз доводилося довго тикатись і експериментувати, щоб зрозуміти як користуватись тим або іншим методом, бо мову знаю не так добре, щоб вільно читати ісходники пакетів.

Читайте також  Відновлення розфокусованих і змазаних зображень

 

Незалежність розташування файлу від його вмісту

Як говориться “with great power comes great responsibility”. З одного боку можна накоїти вакханалію і розкласти об’єкти так, що ворог точно не пройде. А з іншого боку можна іменувати шляхи більш логічно і наочно, додаючи логічні рівні директорій, яких немає в ієрархії класів. Зокрема, можна згадати trailblazer і йому подібних з ідеєю об’єднання всього, що пов’язано з екшеном, в одному місці. У еліксир це можна зробити без сторонніх бібліотек і купи класів просто правильно переклавши існуючі файли.

 

Прозорий шлях запиту

Якщо в Rails питання про rack — це неодмінний атрибут будь-якої співбесіди, бо рейки — це верхівка айсберга і періодично хочеться зробити свій middleware. У еліксир такого бажання не виникає зовсім (хоча може я ще молодий і все попереду). Там є явний набір pipeline, через який проходить запит. І там явно видно де фетчится сесія, де обробляється flash-messge, де csrf валідуєтся і всім цим можна управляти як заманеться в одному місці. В рейці все це господарство частково прибите цвяхами, а частково розкидано по різних місцях.

 

Роути навиворіт

У Rails, ситуація коли один екшн може відповідати в декількох форматах — це норма. Там навіть (.:format) закладений прямо в роуты. У еліксир, за вищезазначеного властивості з pipeline, думки про аналог format взагалі не з’являється. Різні формати йдуть за різними pipeline і мають різні до и. По мені так це здорово.

 

Схема моделі

 

Це взагалі казка. Як опишеш поля моделі, так і буде. Ніякого неявного каста типів. Плюс немає милиць, щоб заборонити доступ до поля, які є в БД, але його з якихось причин не можна використовувати у веб-додатку.

 

Валідації і колбеки

У еліксир немає колбеків. Там все більш прямолінійно. І, здається, мені це подобається.

Замість rails-way еліксир changeset, який поєднує в собі strong_parameters, валідації і трохи колбэков. А залишки колбэков йдуть через Multi, який дає можливість набрати купу операцій, транзакційно їх виконати і обробити результат.

Коротше, все просто по-іншому. Спочатку це незвично. Потім місцями дико бісить, бо не можна просто ще один колбек для всього увіткнути і не думати про різні бізнес-кейси. А потім починаєш помічати “незрозумілу принадність”, бо доводиться робити правильно, а не як звик.

 

Робота з БД

Замість ActiveRecord з’явився якісь Ecto.Repo, Ecto.Query і ще кілька їхніх побратимів. Розповідати всі відмінності — це окрема стаття вийде. Тому скажу основні суб’єктивні відчуття.

У дебагі зручніше AR. Так як там загальний скоуп, константи з load path завантажуються при зверненні до них і можна просто відкрити rails c, написати User.where(email: 'Kane@nod.tb').order(:id).first і отримати результат.

У Elixir’е консолі недостатньо. Потрібно зробити ряд дій:

  • заимпортить метод для побудови sql-запиту: 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. І тепер трохи страждаю, так як це досить довга назва і писати його потрібно постійно. Як у файлах класів, так і в консолі при виклику чого завгодно.

 

N+1

У Rails ми її маємо “з коробки”, а в Elixir “з коробки” такої проблеми немає. Там на етапі складання запиту можна вказати які реляції знадобляться і вони будуть завантажені в ході виконання цього самого запиту. Не завантажив? Не буде тобі доступу до цієї реляції. Все просто і красиво.

 

Обробка запиту та відповідь на нього

Якщо коротко: у феніксі все більш явно, ніж у рейці.

 

Скрізь conn

Так як стан не зберігається в купі різних об’єктів, його доводиться тягати за собою в одному об’єкті. Нагадує request з ActionController, тільки більш всеосяжний. Зветься він у Феніксі connection. Містить взагалі все: і request, і flash, і session і всі всі всі. Він же фігурує у виклику всього, що пов’язано з обробкою прийшов запиту.

Тут і мінуси, так як спершу дуже ліниво ліпити скрізь conn і не до кінця розуміти навіщо. Рейки в цьому плані розбещує. Ти пишеш render або flash і немає думок про те, що це дію з з’єднанням. А в Phoenix conn постон нагадує про роботу з конкретним з’єднанням або сокетом, а не просто методи викликаються і там всередині магія відбувається.

 

Partial&template

У Феніксі немає поділу на partial і template. В кінцевому підсумку всі функція. Тут же криється ще одна принада: рейки навіть в прод оточенні постійно лізе за вьюшками на диск і породжує IO плюс оверхед на їх перетворення з erb/haml/etc в html. А в Elixir всі функція, і в’юшки в тому числі. Скомпилили в’юшку разок і все: отримує аргументи, випльовує html, на диск не ходить.

 

Views

У Rails під view розуміють партіал і темплейти, а в Phoenix вони лежать в templates, а під views, грубо кажучи, живуть різні способи представлення даних. Зокрема, там лежать “перевизначення” render’а.

Тобто, за замовчуванням, контролер нічого не рендерить. Всі викликається явно. А якщо у вас немає партиала і він вам особливо не потрібен (наприклад у випадку з json, коли він легко билдится сервісним класом), ви переопределяете рендер як-небудь так:

def render("show.json", %{groups: groups}) do
%{
 groups: groups
}
end

І партіал більше не потрібен.

 

Heplers

В Phoenix їх немає. І це круто! Бо в рейкових хелперах, зазвичай, збирається всякий непотріб, який або ліниво було розпихати по кутах, або просто треба було швиденько чого-небудь накодить.

Однак, методи контролер, подання і тд. додавати можна. Робиться це в спеціальному місці web/web.ex і виглядає досить пристойно.

 

Статика

В девелопменті все як завжди, хіба що в феніксі ще прикрутили live reload, попервой викликає “Уау!” ефект. Це коли поміняв css, повернувся в браузер, а там зміни вже самі подгрузились.

Читайте також  Що не забути, приймаючи макет сайту від дизайнера, або дрібниць не буває

В продакшені в Phoenix поведінка статики трохи інша, ніж у рейки. За замовчуванням, явно прописані місця, звідки можна тягнути статику і не можна просто так додати файликів в ассеты, щоб роздавати їх. Ще є маппінг дефолтних ассетов, щоб зайвий раз по ФС не блужлать, а одразу брати потрібний файл і віддавати його.

 

Ассети

З коробки в Феніксі — brunch. Можна замінити на webpack. Але є досить правдива жарт про те, що багато проектів загинаються на етапі налаштування webpack’а.

Коротше, js і css більш-менш збираються, а от з рештою статикою в бранчі не дуже. Її або копипастити руками прямо в проект з node_modules (мені цей варіант зовсім не подобається), або писати хуки на баш.

 

Робота з SSL

“З коробки” у Феніксі йде маленький http-сервер, званий cowboy. З вигляду нагадує рубийную пуму. У них навіть кількість зірочок на GitHub приблизно однакове. Але якось мені не зайшла налаштування SSL ні в одному з вищезазначених. Особливо разом з let’s Encrypt, доп. файлом конфига веб-сервера і регулярним оновленням сертифіката. Так що як http-сервер — ок, а для ssl беру проксі на localhost через apache/nginx.

 

Деплой

Він взагалі інший, порівняно з рейкою. У Rails, в мінімальному варіанті, схилив ріпку на сервер, поплясал з бубном для бандлу, конфігів, ассетов і запустив додаток. А еліксир ж компілиться і закопати трамвай схилити ріпку не прокотить. Потрібно збирати пакет. І тут починається:

  • дізнаєшся навіщо потрібен applications у mix.exs, бо без правильно їх зазначення в виявляли у своєму житті таку чудові помилки;
  • дізнаєшся, що змінні оточення вкомпиливаются на момент складання пакету, а не на момент його запуску і це в перші рази викликає дике здивування; потім дізнаєшся про relx разом з RELX_REPLACE_OS_VARS=true і трохи відпускає;
  • дивуєшся, що в зібраному пакеті для продакшену немає нічого схожого на rake, зокрема немає міграцій і їх потрібно запускати окремо, наприклад, з дів.оточення через кидок порту до БД (або через eDeliver, який зробить приблизно те ж саме).

 

А потім, як з вищеописаним розберешся, починаються плюси:

  • можна зробити пакет самодостатнім і на бойовій машині взагалі нічого не ставити, залежностей; просто tarball розпакувати і запустити вміст; хіба що erlang розкачати може знадобитися, так як його cross compile варіант трохи нетривіальний в збірці;
  • можна робити upgrade release, щоб деплоіти без downtime.

 

Дебаг

У Elixir є Pry і працює аналогічно рубям. Навіть є аналог 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, у порівнянні з іншими мовами.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *