Відмінності 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 сильно не вистачає прикладів на все, що трохи складніше табуретки. Та й опис деяких методів, по суті, не пішло далі сигнатури. Мені, як приученному рубями до великої кількості прикладів і описів, було складно з деякими методами еліксиру. Інший раз доводилося довго тикатись і експериментувати, щоб зрозуміти як користуватись тим або іншим методом, бо мову знаю не так добре, щоб вільно читати ісходники пакетів.

Читайте також  Blue pill (синя таблетка) STM32F103 як ПЛК

 

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

Як говориться “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

З іншого боку, в коді програми ці “формальності” дають більш читабельний код, плюс є відчуття, що ти контролюєш ситуацію, а не воно живе своїм життям. Тобто ти сам вибираєш які методи, моделі та інші об’єкти, потрібні для роботи, явно їх підвантажуєш і користуєшся.

Читайте також  Не попадіться в пастку використовуючи Oracle JDK 11

 

Назва додатка

 

За образом і подобою 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 адреса не оприлюднюватиметься. Обов’язкові поля позначені *