Давайте посмотрим, например, на Futures и Promises в Node.js.

Прежде всего, обещания ECMAScript намного лучше, чем простые обратные вызовы. Промисы и дополнительный асинхронный/ожидающий сахар — отличный примитив, но действительно ли они подходят для замены линейных процессов и потоков в корпоративных системах старой школы? Нет…

Давайте посмотрим на другое решение: FutoIn AsyncSteps — оно имитирует потоки в беспоточном асинхронном мире. Примеры относятся к Node.js, но концепция применима к любой технологии, включая C++, JVM, .Net, PHP, Ruby и другие.

Что нужно архитектору программного обеспечения корпоративного уровня?

1. Линейное выполнение кода

Как рецензент кода, я хочу, чтобы бизнес-логика четко имитировала традиционный «синхронный» код без сложных несвязанных конструкций.

Код должен быть чистым и явно линейным в исполнении, если только не используются явные циклы.

Традиционный:

Обещание:

Добро пожаловать в дни «перехода» или в плохой if (result) { /* next step */ } else { /* error handler */ } стиль.

FutoIn AsyncSteps:

2. Контроль потока выполнения

Как архитектор программного обеспечения я хочу четко спланировать максимальное время выполнения клиентских запросов, чтобы иметь предсказуемое использование памяти и ЦП.

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

Традиционный:

Обещание:

В Promise нет эквивалента. Это фундаментальная проблема для любого фреймворка Node.js, основанного на Promise.

FutoIn AsyncSteps:

3. Петли

Как разработчик и рецензент кода я хочу видеть чистый код асинхронного цикла без зависимости от дополнительных библиотек.

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

Традиционный:

Обещание:

Перебор с Promise, помогает только async/await. Еще хуже обстоят дела с break/continue и внутренними циклами.

Интегрировано в AsyncSteps:

4. Локальное хранилище потоков

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

Обоснование ее необходимости здесь опущено ради объема текста.

Традиционный:

Обещание:

Ни Promise, ни async/await не поддерживают это. Даже эмуляция через WeakMap не является решением, так как нет значения, которое можно было бы использовать в качестве ссылки на ключ. Каждое обещание — это в основном автономный объект.

FutoIn AsyncSteps:

5. Синхронизация

Как архитектор ПО я хочу использовать примитивы синхронизации на семантическом уровне.

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

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

Традиционный:

Обещание:

Это выполнимо с небольшим беспорядком, но стандартного решения нет.

FutoIn AsyncSteps:

Сравнение читаемости кода

Давайте просто сравним простой код, который обеспечивает постоянный случайный UUID для каждого значения в абстрактной базе данных SQL.

Обещание:

В обоих случаях Promise обработчики ошибок находятся на уровне ниже, чем охват, который они охватывают. Это строго противоречит традиции «попробуй-поймай/начни-спасать». Кроме того, отсутствует четкая визуальная идентификация альтернативного потока ошибок.

Футоин путь:

Если первый вариант вам более читабелен, то перед вами профессиональная деформация JS.

А как насчет асинхронности/ожидания?

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

Тем не менее, есть фундаментальные проблемы с концепцией «ожидания» в целом:

  1. Это НЕ видно — легко пропустить асинхронную операцию, глядя на код с высокого уровня.
  2. Это неестественно для процесса обработки процессора на «голом железе»:
    — обычно реализуется с многопоточностью, переключением контекста и возможной синхронизацией, что убивает производительность,
    — сопрограммы на «голом железе» имеют долгую историю известных проблем.
  3. Такая абстракция уводит молодых разработчиков все дальше и дальше от железа, что еще больше снижает уровень компетентности в индустрии.
    - через несколько лет новички даже не будут разбираться во внутренностях await и ничего не будут знать о фьючерсах и промисах.

Вывод

Концепция FutoIn AsyncSteps была формализована в виде спецификации в середине 2014 года. Он уже много лет работает в производственном коде. Он стабилен и обратно совместим даже после 10 ревизий.

Разработка ПО в Enterprise известна своим постоянством: даже обновление версии компилятора JDK или C/C++ — невероятный шаг. Многие архитекторы старой школы не считают Node.js и основное асинхронное программирование приемлемым решением для критической бизнес-логики.

К сожалению, это все еще остается верным, но Node.js имеет явно более низкий начальный уровень, он гораздо более прощает ошибки и в целом проще в развертывании. Самое главное, он идеально подходит для прототипирования и требует меньше усилий, чем традиционный стек Enterprise.

Концепция FutoIn AsyncSteps может помочь SW Architects по-новому взглянуть на асинхронное программирование для бизнес-логики.

Для получения дополнительной информации посетите официальное Руководство по FutoIn и проверьте эталонную реализацию на GitHub.

Copyright © 2018 Андрей Галкин

Первоначально опубликовано на futoin.tumblr.com.