Из Erlang/Elixir в Java и обратно. Приключение на 20 минут

  • Tutorial

Всем хай!


Когда приходится общаться из Erlang/Elixir мира с Java и обратно — не так уж и много вариантов имеется. Всеми заброшенный jinterface и новая библиотека encon, базовый пример использования которой представлен под катом.


Добавление зависимостей


Добавляем Encon зависимость к JVM приложению:


Maven:


<dependencies>
  ...
  <dependency>
    <groupId>io.appulse.encon</groupId>
    <artifactId>encon</artifactId>
    <version>1.6.4</version>
  </dependency>
  ...
</dependencies>

Gradle:


dependencies {
  compile 'io.appulse.encon:encon:1.6.4'
}

ЗАМЕТЬТЕ: если есть такая необходимость (например проект на Ant), то зависимости в виде jar-файлов можно найти на GitHub, в разделе релизы.

Запуск Erlang-ноды


import io.appulse.encon.Node;
import io.appulse.encon.Nodes;
import io.appulse.encon.config.NodeConfig;

// Создание конфига ноды.
// Больше деталей - см. проект encon-config
NodeConfig config = NodeConfig.builder()
    // true - для локальных нод,
    // false (по умолчанию) - доступных извне
    .shortName(true)
    .cookie("secret")
    .build();

// Созадние, регистрация в EPMD и запуск сервера новой Erlang ноды
Node node = Nodes.singleNode("echo-node", config);

ЗАМЕТЬТЕ: что бы пример выше заработал, необходимо либо запустить демон EPMD или его Java-имплементацию.

Создание мейлбокса


Мейлбокс, он же процесс, в терминалогии Erlang:


import io.appulse.encon.mailbox.Mailbox;

Mailbox mailbox = node.mailbox()
    .name("popa") // опционально
    .build();

Подключение к нодам


ЗАМЕТЬТЕ: Вы можете инициировать соединение из Erlang/Elixir или Java автоматически отправив сообщение, используя формат тюпла {Имя, Нода} или по PID'у (если он есть).

Можно начать общение между нодами с отправки ping-сообщения cо стороны Erlang через net_adm:ping/1:


(erlang@localhost)1> net_adm:ping('java@localhost').
pong
(erlang@localhost)2>

Также возможно отправить сообщение на {Name, Node}, где Node это атом вида 'java@localhost', и Name это PID или зарегестрированное имя мейлбокса, который существует на стороне Java.


(erlang@localhost)1> {my_process, 'java@localhost'} ! hello.
hello
(erlang@localhost)2>

Если мейлбокс существует на стороне Java, то он получит сообщение.


Отправляем сообщение из Java


Есть целое семейство send-методов у Mailbox, которые доставляют:


  • send(ErlangPid, ErlangTerm) — отправляет сообщение на удалённый или локальный PID;
  • send(String, ErlangTerm) — пересылает терм на локальный мейлбокс по его имени этой же ноды;
  • send(String, String, ErlangTerm) — отправляет сообщение на удалённую/локальную ноду и мейлбокс по его имени.

Давайте попробуем, откроем Erlang-shell и зарегаем его с именем...'shell':


(erlang@localhost)1> erlang:register(shell, self()).
true
(erlang@localhost)2>

Теперь, мы можем отправить сообщулю из Java (связь с нодой будет установлена автоматически):


import static io.appulse.encon.terms.Erlang.atom;

mailbox.send("erlang@localhost", "shell", atom("hello"));

Возвращаемся в Erlang и читаем полученное сообщение:


(erlang@localhost) 1> flush().
Shell got hello
ok
(erlang@localhost) 2>

Получение сообщений в Java


Для получения входящего сообщения необходимо воспользоваться одним из Mailbox-методов: receive() или receive(timeout, timeUnit), которые могут бесконечно ждать нового сообщения или фиксированное количество времени.


import io.appulse.encon.Node;
import io.appulse.encon.Nodes;
import io.appulse.encon.config.NodeConfig;
import io.appulse.encon.connection.regular.Message;
import io.appulse.encon.mailbox.Mailbox;

public class Main {

  public static void main (String[] args) {
    NodeConfig config = NodeConfig.builder()
        .shortName(true)
        .build();

    Node node = Nodes.singleNode("java@localhost", config);

    Mailbox mailbox = node.mailbox()
        .name("my_process")
        .build();

    Message message = mailbox.receive();
    System.out.println("Incoming message: " + message.getBody().asText());
  }
}

Стартанём Erlang ноду и отправим сообщение:


$> erl -sname erlang@localhost
...
(erlang@localhost)1> {my_process, 'java@localhost'} ! hello.

Заключение


Эти и многие другие примеры можно изучить на сайте проекта и/или на GitHub страничке.

Поделиться публикацией

Похожие публикации

Комментарии 6

    0
    не так уж и много вариантов имеется. Всеми заброшенный jinterface и новая библиотека encon

    … или таки поднять уже брокер, хотя бы тот же RabbitMQ, и слать сообщения через него. Надежнее будет.
      0
      Так получится ещё одна дополнительная сущность в виде гномика кролика. Её нужно так же саппортить, деплоить, реплецировать, чёт туда править… это ж усложнение системы.

      Наверн, если кролик уже в системе, то это имеет смысл, а если нужно просто скрестить Erlang/Elixir с Java (например, ты мигрируешь из одного в другое) — то encon тут супер подходит
        0
        Кролик просто замечательно работает из коробки, да и в docker-образе есть, так что для предположительной миграции, когда не нужна производительность и все такое, он как раз предпочтительнее — работает, есть не просит, сообщения гоняет, кода для этого требуется… хм… меньше, чем в приведенных примерах. Если со Spring, так и вообще… А если речь идет о продакшене, где данных много, то тем более промежуточное звено в виде кроля прямо показано, ибо изолирует одно от второго.

        В общем не зря таких библиотек практически нет, у них область применения почти отсутствует.
          0
          Во-первых, у либы из статьи тоже есть spring-интеграция (вот, например, эхо сервер с клиентом), причем с готовым databinding'ом а-ля Jackson, ток для Erlang'овских термов.

          Ну а во-вторых, каким бы кролик замечательным (сам им пользуюсь в одном из проектов, вот прям щаз) ни был, это всё равно дополнительный инфраструктурный элемент, от которого хотелось бы избавиться, если есть возможность, так как он тоже имеет свои накладные расходы и, повторюсь, затраты на поддержку, ибо докерный образ кто-то тоже должен поднять, а где хранить креды к кролику? «А настройте мне вот тут эксчейндж», «У меня была очередь и теперь её нет», «А кто нибудь это уже синькал со стейджем?» — и многие многие другие радости. Иными словами — это не бесплатно.
          Тут же — нативное общение для Эрланга + доп обвязочки (databinding, spring интеграция) для того что бы и со стороны Java это выглядело более-менее нативно и твой мейлбокс походил на какой нибудь спринговый контроллер.

          Лично для себя, я вижу область применения такой либы, когда для тебя Erlang/Elixir — это шина, клей твоей системы с парой-тройкой инфраструктурных сервисов внутри. BEAM VM держит кучу соединений, на нём приятно писать какую нибудь маршрутизацию, балансировку, сервис дескавери, распределенный ин-мемори кэш.

          В общем замена: nginx, redis, RabbitMQ, etcd сервисов и Zuul, Eureka (это из Spring Cloud)… ну Apigee для бедных, кароч)
      0
      Не совсем понятно как быть с типами — maps,proplists. Либо когда сообщение необходимо отправить одним из типов:
      {ok, <<"Result">>}
      или
      {ok, "Result"}

      Опять же maps:
      #{key => value}

      #{"key" => <<"value">>}

      ну и далее в любом порядке. В README этого не нашел.
        0

        Там на сайте есть новая статья, в которой есть раздел с маппингом Java2Erlang. Но не написано что есть ещё класс со статическими хелп-функциями — Erlang


        Конкретно твои примеры выглядят так:


        import static io.appulse.encon.terms.Erlang.atom;
        import static io.appulse.encon.terms.Erlang.bstring;
        import static io.appulse.encon.terms.Erlang.map;
        import static io.appulse.encon.terms.Erlang.string;
        import static io.appulse.encon.terms.Erlang.tuple;
        
        import io.appulse.encon.terms.ErlangTerm;
        
        // {ok, <<"Result">>}
        ErlangTerm message1 = tuple(atom("ok"), bstring("Result"));
        
        // {ok, "Result"}
        ErlangTerm message2 = tuple(atom("ok"), string("Result"));
        
        // #{key => value}
        ErlangTerm message3 = map(atom("key"), atom("value"));
        
        // #{"key" => <<"value">>}
        ErlangTerm message4 = map(string("key"), bstring("value"));

        С мапами, как в Guava хелпер был — они идут vararg массивом, нечётный элемент — ключ, чётный — значение (что и видно в примере выше).


        Никто не мешает через конструктор всё создать, но на мой вкус тут статические функции лаконичнее выглядят.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое