В карьере современного инженера-программиста восстание из пепла - обычная тема.

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

Rails Battle Scars

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

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

Мне, конечно, очень нравится работать с Rails - иначе я бы давно пошел дальше. В частности, я ценю то внимание, которое уделяется продуктивности разработчиков и поддержке экосистемы. Многие дизайнерские решения вокруг Rails ориентированы на продуктивность и быстрое создание продуктов, и, на мой взгляд, это остается самой сильной стороной фреймворка. С другой стороны, как и многие другие, я был свидетелем проблем, которые могут возникнуть в проектах Rails, поскольку они масштабируются как по сложности, так и по использованию. Например, приложения Rails с самого начала довольно раздуты в отношении запуска, времени выполнения и производительности памяти, и эти проблемы только увеличиваются по мере того, как проекты становятся более сложными. Кроме того, динамический универсальный подход языка Ruby с отсутствием статической типизации и других проверок во время компиляции (что позволяет использовать большую часть магии Rails) также может привести к сложному в сопровождении кода в виде кодовых баз. расти.

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

Введите феникс

Что, если бы существовала технология, которая взяла все хорошие идеи из Ruby & Rails, но исправила все плохие вещи, такие как раздувание и производительность, и положила бы ее на более прочную основу? Оказывается, это именно то, что вы получаете с Phoenix Framework на Elixir, языке, созданном Хосе Валимом, давним участником как Ruby, так и Rails.

Я смотрел на Elixir пару лет и использовал его в некоторых побочных проектах, но мне еще только предстояло полностью погрузиться в него. Побочные проекты и учебные пособия - отличные способы познакомиться с новой технологией, но только когда вы действительно погрузитесь в создание готового продукта, вы действительно узнаете и поймете его. Лично мне нужно столкнуться со всеми теми проблемами реального мира, которые легко упустить из виду, когда беру новый фреймворк для случайного вращения: каков инструмент, какова экосистема, как, черт возьми, мне это развернуть?

Использование новой технологии в реальной производственной среде всегда открывает новые проблемы и возможности для обучения, и хотя я, конечно, не буду называть себя экспертом по Phoenix & Elixir, создание этого продукта дало мне более чем достаточно опыта для решения некоторых из этих проблем. . Я зафиксировал здесь некоторые из своих мыслей и склонностей. Это не исчерпывающий анализ Elixir или Phoenix, а скорее некоторые ключевые наблюдения, которые я сделал при создании нового продукта.

Чистое функциональное программирование - в основном

Первая крупная победа Elixir - сосредоточение внимания на функциональном программировании. Я большой поклонник функционального программирования в целом: управление состоянием программы и рассуждение о нем часто бывает трудным и может быть источником коварных ошибок. Я всегда ценил, что Ruby позволяет и часто поощряет разработчиков использовать более функциональный стиль программирования, хотя и не заставляет их делать это. На микроуровне использование таких функций, как map и reduce, безусловно, более идиоматично Ruby, чем написание for -loops, но на более макроуровне, Объектно-ориентированная природа Ruby имеет тенденцию преобладать, при этом состояние хранится на уровне класса и экземпляра.

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

Так что насчет управления состоянием через среду выполнения Erlang? Среда выполнения Erlang допускает одновременные процессы (которые не являются настоящими процессами ОС и более похожи на потоки на других языках), и эти процессы могут , фактически , имеют собственное полностью изолированное состояние. Таким образом, приложения могут использовать состояние, отправляя сообщения другим процессам. Эти сообщения обрабатываются в том порядке, в котором они получены, и могут выполнять другие вычисления, помимо изменения состояния, такие как проверка данных и другие формы управления состоянием.

Кроме того, эти сообщения обычно не определяют состояние напрямую, говоря «процесс, установите переменную X». Хотя это может происходить в некоторых случаях использования, таких как кеширование, сообщения чаще представляют собой семантику приложения более высокого уровня, такую ​​как «процесс, пользователь сделал этот шахматный ход, соответствующим образом обновил состояние игры и отправил его мне ». Это способствует преобразованию и проверке данных, встроенных в управление состоянием. В некотором смысле, манипулирование состоянием в Elixir можно рассматривать скорее как выполнение чрезвычайно легкого вызова API, чем как установку переменной.

Реальность такова, что рано или поздно наиболее полезным приложениям потребуется какое-то понятие состояния, вопрос лишь в том, где и как языки предоставляют эту функциональность. Избегая состояния в основном языке и выделяя и абстрагируя его в отдельные процессы Erlang, Elixir удается резко сократить использование состояния и смягчить многие подводные камни, которые могут возникнуть вместе с решениями с отслеживанием состояния в других контекстах: несогласованность данных, взаимоблокировки, потоки условия разногласий и гонок.

Метапрограммирование и «магия»

Метапрограммирование в Ruby и его применение в Rails - спорный вопрос. Использование метапрограммирования в Rails часто называют «магией», дополняя и уничижительно. Он используется для реализации многих преимуществ фреймворка Rails; и ею злоупотребляют, чтобы создать множество недостатков. Главными из этих недостатков являются непрозрачный и трудный для понимания код, а также код, демонстрирующий непредвиденные последствия и побочные эффекты.

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

Elixir уже предоставляет механизмы для написания вашего повседневного кода в простой и удобочитаемой форме, используя свои структуры данных и функции. Макросы следует использовать только в крайнем случае. Помните, что явное лучше, чем неявное. Четкий код лучше краткого.

В качестве примера использования макросов (ab) давайте взглянем на Ecto, аналог Elixir для ActiveRecord. В ActiveRecord я мог бы написать запрос для получения имен пользователей старше определенного возраста, как этот.

User.where (: name = ›query) .select (: name)

В Ecto предпочтительным синтаксисом запроса является DSL, реализованный с помощью макроса:

от u в User, где: u.name = ^ query, выберите: u.name

Это выглядит разумным, но есть несколько функций этой строки кода, которые делают вещи совершенно отличные от того, что они обычно делают в языке Elixir.

  • «x in y» обычно сообщает вам, находится ли элемент «x» в Enum «y». Но не здесь.
  • «^» Обычно является оператором «pin», используемым в предложении соответствия, что является своего рода концептуально тем, как он используется здесь, хотя мы не находимся в предложении соответствия, поэтому на самом деле это не было бы действительный (или ожидаемый!)
  • «where: x, select: y» обычно создает список ключевых слов с использованием значений «x» и «y», но здесь этого тоже не происходит (потому что привязка «u» в этот момент - не то, что мы ожидали)

Теперь, прочитав документацию по этому конкретному, часто используемому методу, я понял и понимаю, как он работает. И поскольку здесь под капотом этот метод, по сути, представляет собой сборку фрагмента SQL, потребуется некоторая форма гимнастики кода. Но, на мой взгляд, такие макросы обычно затрудняют чтение и рассуждение кода.

Я обнаружил, что этот тип макрос-стратегии используется в удивительном количестве библиотек Elixir. Любая библиотека, интегрированная с использованием ключевого слова Elixir «use», может вводить невидимый код в модуль, который может (или не может) использовать этот вид метапрограммирования.

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

Опыт разработчиков и поддержка экосистемы

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

Я был приятно удивлен, обнаружив, что Phoenix, будучи относительно молодым, также имеет здоровую экосистему и пакеты, доступные для большинства интеграций и других функций, которые мне нужны. Иногда, в то время как Ruby может иметь 3 или 4 разных драгоценных камня на выбор (2 или 3 из которых, вероятно, устарели и не обслуживаются), Elixir может иметь только один доступный пакет. Но по моему опыту, этот пакет, скорее всего, будет актуальным и функциональным. Трудно отрицать преимущество Rails здесь, учитывая, как давно он существует, но на практике это не было проблемой ни для одной из проблем, с которыми я столкнулся.

Несмотря на то, что Phoenix намного моложе, общий опыт разработчиков легко совпадает с Rails или превосходит его. Phoenix, как и Rails, в основном использует подход, основанный на использовании батарей, что означает, что основное внимание уделяется производительности разработчиков и обеспечению базовой функциональности прямо из коробки. Конечно, есть различия в том, какие именно батареи включены, так сказать, но в целом обе платформы выполняют это обещание.

Одна особенность Phoenix, которую стоит упомянуть отдельно, - это та, которую я сейчас не использую широко, - это LiveView, решение для рендеринга на стороне сервера, встроенное в Phoenix прямо из коробки. LiveView позволяет создавать в реальном времени одностраничные приложения, отрисовываемые сервером, без написания строчки JavaScript. Создание полного приложения в LiveView было для меня слишком далеко (я выбрал React для клиента), но есть несколько конкретных функций, над которыми я работаю, которые кажутся идеальными для LiveView - ситуации, когда есть интерактивный компонент в сочетании с множеством обменов данными между сервером и браузером. Я буду оценивать LiveView для этих вариантов использования по мере их дальнейшего развития.

OTP и среда выполнения Erlang

Самый увлекательный аспект Elixir - это тот, в котором у меня было меньше всего опыта: среда выполнения Erlang с Open Telecom Protocol (OTP). Вероятно, правильнее будет сказать, что это область, в которой у меня наименее явный опыт - если вы пишете приложение в Elixir или Phoenix, вы неявно испытываете преимущества OTP с самого начала. .

Среда выполнения Erlang сложнее, чем среда выполнения большинства языков, и почти похожа на мини-операционную систему для приложений, работающих на виртуальной машине Erlang. Он разработан для обеспечения массового параллелизма и отказоустойчивости и продвигает философию «позвольте этому сбоить», согласно которой отдельные процессы могут аварийно завершиться или выйти из строя без остановки всего приложения. Иерархические деревья супервизоров используются для мониторинга процессов и корректной обработки любых сбоев.

Что все это означает, например, в контексте Феникса? «Приложение», написанное с помощью Phoenix, обычно состоит из десятков или сотен отдельных процессов, большинство из которых явно не запускается разработчиком. Каждый веб-запрос к приложению - это отдельный процесс, как и множество других скрытых функций: веб-сокеты, соединения с базой данных, пулы соединений, регистраторы и многое другое. Даже если вы никогда явно не создаете процесс, ваше приложение Phoenix использует OTP для стабильности, масштабируемости и полной изоляции всех движущихся частей вашего приложения. OTP - явная победа Phoenix над другими фреймворками, такими как Rails.

Недавний выпуск Phoenix Live Dashboard дает небольшое представление о OTP и о том, как он используется в приложении Phoenix. Эта легковесная панель мониторинга показывает мониторинг процессов и показатели, которые демонстрируют, что OTP делает в каждом приложении Phoenix.

3 месяца спустя

После 3 месяцев погружения в Elixir & Phoenix я очень счастлив, что совершил скачок. Как я упоминал в начале, мой вариант использования в основном представляет собой обычное веб-приложение, которое опирается на поддержку отличной инфраструктуры Phoenix и не требует многих из более экзотических функций OTP, поэтому есть много тем, которые я еще предстоит полностью изучить. Я знаю, что есть множество проблем, которые мне еще не нужно было решать, и, конечно же, рано или поздно я наткнусь на одну, из-за которой я буду биться головой по клавиатуре - как это происходит с любым языком или фреймворк.

Тем не менее, я считаю выбор Эликсира и Феникса большим успехом. Фактически, я был в восторге от всех, кто спрашивал, что Elixir - это «язык будущего», с небольшими преувеличениями - в наши дни есть много отличных новых языков для разработки приложений, и я не делал глубокого погружения. со всеми, конечно. Но, потратив время на работу с языком и, в частности, с OTP, я с уверенностью могу сказать: в Elixir есть что-то очень особенное .