МАЙ 2016

ТОМ 31 НОМЕР 5

Работающий программист - MEAN: получаем преимущество (Edge.js)

Тэд Ньюард | МАЙ 2016

Ted NewardС возвращением, дорогие «министы» («MEANies»). В предыдущей статье я ввел какую-то долю структуризации в изначально неструктурированную среду JavaScript, Node и MongoDB, добавив библиотеку MongooseJS в программный стек, медленно создаваемый мной. Это позволило сформировать некую «схему» вокруг различных наборов, которые принимало и сохраняло промежуточное программное обеспечение Node/Express. Это здорово, потому что помогает избежать некоторых распространенных ошибок (скажем, поиска «fristName» вместо реального поля «firstName»). Но самое главное в том, что MongooseJS полностью на стороне кода, а значит теперь вы получаете лучшее из двух миров, по крайней мере, если речь идет о базе данных: отсутствие схемы в базе данных (сильно облегчая ее рефакторинг) и наличие схемы в коде (значительно уменьшая вероятность опечатки, которая может привести к очень печальным последствиям).

Но, если можно высказать здесь свое личное отношение, я должен согласиться, что мне не хватает Microsoft .NET Framework. Или точнее, мне не достает некоторых из очень классных вещей, доступных в экосистеме .NET. В частности, когда я выполняю код в облаке Microsoft Azure, где ряд организаций намерен сделать сравнительно малые (или очень крупные) капиталовложения в «стек» .NET, кажется слегка неуместным слишком много говорить о JavaScript, если все эти «прелести» .NET остаются недостижимыми. Ну разве что, кроме выполнения какой-либо разновидности запросов в стиле «дальнобойного» HTTP, что кажется слегка глупым, когда работаешь в пределах одного информационного центра.

К счастью, у нас есть Edge.js.

Edge.js

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

Например, код Node.js для вызова какой-либо .NET-функции выглядит так:

var edge = require('edge');

var helloWorld = edge.func(function () {/*
  async (input) => {
    return ".NET Welcomes " + input.ToString();
  }
*/});

helloWorld('JavaScript', function (error, result) {
  if (error) throw error;
  console.log(result);
});

Как видите, с программной точки зрения, это нетрудно: передаем литерал функции методу edge.func и внутрь этого литерала функции включаем .NET-код для вызова, заключая этот код в тело комментария.

Да, дорогой читатель, комментария. Это не столь странно, когда вы осознаете, что:

  • C#-синтаксис недопустим, а иначе интерпретатор Node не распознает его как правильный синтаксис программы (в конце концов, интерпретатор Node является интерпретатором JavaScript, а не C#);
  • в отличие от скомпилированной программы интерпретатор имеет доступ ко всему исходному коду, а не только к тому, что отобрал компилятор при генерации исполняемого кода.

Кстати, заметьте, что этот функционал не ограничен одним лишь C#; в проекте Edge.js перечислены несколько других языков, которые могут быть задействованы как целевые при Edge-вызове, в том числе F#, Windows PowerShell, Python и или даже Lisp, с использованием .NET-реализации каждого из этих языков. Разумеется, моим любимцем является F#:

var edge = require('edge');

var helloFs = edge.func('fs', function () {/*
  fun input -> async {
    return "F# welcomes " + input.ToString()
  }
*/});

helloFs('Node.js', function (error, result) {
  if (error) throw error;
  console.log(result);
});

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

Здесь нужно понять главное: тело функции — написано оно на C# или F# — имеет специфическую сигнатуру .NET-типа: Func<object, Task<object>>. Асинхронность необходима потому, что Node предпочитает обратные вызовы, а не последовательное выполнение, чтобы избежать блокировки основного цикла обработки событий Node.js.

Edge.js также делает сравнительно простым вызов этих функций из скомпилированных .NET DLL. Поэтому, если у вас есть, например, скомпилированная сборка .NET-кода, которая разрешает внешние вызовы, Edge.js может вызывать ее при условии, что имя сборки, имя типа и имя метода предоставляются как часть вызова func:

var helloDll = edge.func({
  assemblyFile: "Echo.dll",
  typeName: "Example.Greetings",
  methodName: "Greet"
});

Если сигнатура типа Greet — Func<object, Task<object>>, как, например, показано на рис. 1, то Node.js может вызывать его, используя тот же шаблон вызова (с передачей входных параметров и обратного вызова функции), который вы видели в других примерах.

Рис. 1. Конечная точка .NET, совместимая с Edge.js

using System;
using System.Threading.Tasks;

namespace Example
{
  public class Greetings
  {
    public async Task<object> Greet(object input)
    {
      string message = (string)input;
      return String.Format("On {0}, you said {1}",
        System.DateTime.Now,
        Message);
    }
  }
}

Можно пойти и другим путем: помещать .NET-код для вызова в пакеты Node.js. Но, поскольку здесь целью является работа Node.js на серверной стороне, я оставлю этот вариант в качестве упражнения заинтересованным читателям. (Кстати, со всей начинкой Edge.js гораздо легче работать из Windows, чем из Mac; попытка заставить ее работать на моем Mac при написании этой статьи определенно отняла уйму времени. С учетом всего сказанного это тот самый случай, когда Windows явно превосходит Mac в разработке, относящейся к Node.js.)

Прежде чем заняться более-менее сложными вещами, я хочу предложить короткий пример в стиле «Hello World».

Hello, Edge

Сначала о главном: все, что нужно для работы в Microsoft Azure (поскольку это выбранная мной целевая среда для развертывания), вновь ввести команду npm install --save edge, чтобы она отслеживалась в манифесте package.json, когда он попадет в Azure. Затем я добавляю функцию helloWorld в код app.js и быстро настраиваю конечную точку, чтобы иметь возможность выдать к ней GET-запрос и получить приветствие обратно по HTTP, как показано на рис. 2. И, конечно же, отправка GET-запроса к msdn-mean.azurewebsites.net/edgehello вернет:

{"message":".NET Welcomes Node, JavaScript, and Express"}

Рис. 2. Добавление функции helloWorld

var helloWorld = edge.func(function () {/*
    async (input) => {
        return ".NET Welcomes " + input.ToString();
    }
*/});

var edgehello = function(req, res) {
  helloWorld('Node, JavaScript, and Express',
  function (err, result) {
    if (err) res.status(500).jsonp(err);
    else res.status(200).jsonp( { message: result } );
  });
};

// ...

app.get('/edgehello', edgehello);

Hello, SQL Server

Один из вопросов, периодически возникающий в разговорах о стеке MEAN с .NET-разработчиками, касается MongoDB. Приличному числу людей не нравится идея передачи своих SQL Server, особенно при выполнении в Azure. Конечно, сообщество Node.js создало несколько API для доступа к реляционным базам данных, и SQL Server — просто TDS-соединение, далекое от любого из них, но Edge.js действительно предлагает весьма изящное решение данной конкретной проблемы (как только вы выполните команду npm install --save edge-sql для получения пакета Edge-SQL):

var edge = require('edge');

var getTop10Products = edge.func('sql', function () {/*
  select top 10 * from Products
*/});

getTop10Products(null, function (error, result) {
  if (error) throw error;
  console.log(result);
  console.log(result[0].ProductName);
  console.log(result[1].ReorderLevel);
});

В этом коде предполагается, что в Azure-среде переменной окружения EDGE_SQL_CONNECTION_STRING присвоена соответствующая строка подключения к SQL Server (что в данном случае, по всей видимости, указывало бы на экземпляр SQL Server, выполняемый в Azure).

Если честно, это, пожалуй, проще, чем работать со всем, что я видел до сих пор. Вероятно, это не заменит в ближайшем будущем Entity Framework, но для быстрого доступа к экземпляру SQL Server, возможно, с использованием подхода с несколькими хранилищами, где SQL Server хранит реляционные данные со строгими схемами, а MongoDB оперирует данные в стиле JSON безо всяких схем, это действительно весьма элегантно.

И опять: зачем?

Учитывая, что большинство разработчиков обычно с неодобрением смотрят на любое решение, требующее от них «говорить» на нескольких языках одновременно, вероятно, стоит копнуть чуть поглубже, чтобы понять, когда и как это могло бы использоваться.

Очевидный ответ заключается в том, что для большинства проектов, создаваемых с нуля, где не требуется поддержки какого-либо устаревшего кода, целесообразнее оставаться исключительно в рамках какого-либо одного языка/платформы: либо придерживаться .NET и применять Web API и прочее, либо использовать Node.js на полную катушку и полагаться на разнообразные библиотеки и пакеты для решения своей задачи. В конце концов, почти для всего, что можно было сделать в мире .NET, скорее всего есть эквивалентный пакет в репозитарии npm-пакетов. Однако в этом подходе есть несколько подвохов.

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

Во-вторых, определенные задачи легче решать при использовании определенных сред или подходов к программированию; хороший пример — язык F# зачастую имеет больше смысла для тех, кто обладает определенным математическим багажом, и для них иногда проще писать какие-то виды кода именно на этом языке. Так было и со мной, когда несколько лет назад я писал библиотеку Feliza (в серии статей, демонстрирующих, как использовать SMS в облаке Tropo); хотя ее можно было бы написать на C#, такие возможности F#, как проверка на соответствие шаблону и «активные шаблоны», значительно упростили ее создание по сравнению с тем, если бы я делал то же самое на C#. Это же относится и к JavaScript (возможно, даже в большей степени).

И последнее, но, по-видимому, самое важное в том, что иногда среда, в которой выполняется код, естественным образом отдает предпочтение одной из платформ по сравнению с другими. Например, множество организаций, размещающих приложения в Azure, используют Active Directory в качестве своей базы аутентификации и авторизации; в итоге они захотят по-прежнему использовать Active Directory для любого нового приложения — будь оно написано под .NET или Node.js. Обращаться к Active Directory, в целом, намного легче и проще из .NET-среды, чем из любой другой, поэтому библиотека Edge.js предлагает удобную «лазейку», так сказать, чтобы упростить доступ к Active Directory.

Заключение

На этот раз статья была немного полегче — в основном потому, что библиотека Edge.js берет на себя большую часть работы по организации взаимодействия с .NET-средой. И открывает уйму новых вариантов для «министов» в Azure, так как теперь вы получаете доступ не к одной, а к двум богатым экосистемам инструментов, библиотек и пакетов.

Заметьте, что у меня ждет своего часа целая статья для этой рубрики, посвященная, кстати, прямо противоположному: во многих случаях некоторые виды приложений гораздо легче писать с использованием разнообразных пакетов, доступных в репозитарии npm, и Edge.js теперь открывает такую возможность для разработчиков, применяющих традиционную .NET. А с недавним выпуском проекта Chakra с открытым исходным кодом — ядра Microsoft JavaScript, которое можно подключать к Node-среде как обычный плагин, — появляется еще больше возможностей использовать JavaScript как часть «стандартного» .NET-приложения, в том числе вы сможете сделать свое приложение хостом для интерпретатора JavaScript.

Осталось еще несколько вещей, которые я хотел бы обсудить до того, как полностью покончить с серверной стороной, но, увы, нам пора прощаться, так что на сегодня все и… удачи в кодировании!


Тэд Ньюард (Ted Neward) — глава фирмы Neward & Associates, предоставляющей консалтинговые услуги по самым разнообразным технологиям. Автор и соавтор многочисленных книг, в том числе «Professional F# 2.0» (Wrox, 2010), более сотни статей, часто выступает на многих конференциях по всему миру; кроме того, имеет звание Microsoft MVP в области F#. С ним можно связаться по адресу ted@tedneward.com или почитать его блог blogs.tedneward.com.

Выражаю благодарность за рецензирование статьи эксперту Шону Уайлдермуту (Shawn Wildermuth).


Discuss this article in the MSDN Magazine forum