Передача лямбда выражений на сервер
Проект trambda (transportable lambda) - эта передача части байткода JVM работающего приложения на сервер для последующего исполнения на сервере без перезапуска самого сервера.
Наглядный пример
Давайте рассмотрим пример поиска процесса java
на компьютере
Допустим у нас есть такой интерфейс IEnv:
public interface IEnv { List<OsProc> processes(); }
Который возвращает список процессов
public class OsProc implements Serializable { public Optional<Integer> getPpid(){ return ... } public int getPid(){ return ... } public void setPid(int pid){ ... } public String getName(){ return ... } public Optional<String> getCmdline(){ return ... } }
1| var query = TcpQuery 2| .create(IEnv.class).host("myserver.com").port(9988) 3| .build(); 4| 5| query.apply( 6| env -> env.processes().stream().filter( 7| p -> p.getName().contains("java") 8| ) 9| .collect(Collectors.toList()) 10| ).forEach(System.out::println);
- Строки 1-3 и 10 - выполняются на (локальном) компьютере клиента,
- Строки 6-9 выполняются на сервере (myserver.com)
Как это работает ?
При разработке клиентского приложения на Java
- Мы создаем набор исходных файлов, допустим
Client.java
- Компилятор генерирует байткод - файл
Client.class
- При вызове
query.apply()
- мы передаем ссылку на лямбдуenv -> env.proc...toList())
- Реализация
query.apply()
:- Принимает ссылку на лямбду
- Для лямбды обнаруживает имя класса (например
Client
) и метода (напримерlambda1
) реализующего лямбду - Отыскивает среди ресурсов программы соответствующий байткод класса (
Client.class
) и его метода - Загружает байткод реализации лямбды и отправляет его на сервер
- Сервер принимает байткод лямбды
- Генерирует в памяти класс в который вставляет принятый байткод
- Загружает этот класс в память и через рефлексию получает доступ к лямбде
- Возвращает идентификатор этого метода обратно
- Принимает идентификатор метода и делает вызов его на сервере
- Сервер выполняет ранее скомпилированный класс/метод/лямбду
- возвращает результат выполнения
- Принимает результат серверного вызова и возвращает его как результат локального вызова
- Возврат результата вызова
query.apply()
Сравнение с существующими решениями
Протоколов передачи данных и вызова процедур много, будут рассмотрены некоторые из известных автору. Эти протоколы можно разделить на несколько категорий/свойств:
- Передача данных
- Строго ограниченные форматы данных / типы данных
- Гибкие форматы данных / комбинированные/структурированные типы данных
- Передача программного кода
- Ограничения на алгоритмы
- Простые выражения
- Циклы/ветвления
- Процедуры/функции/объекты…
- Ограничения на интерпретацию алгоритмов
- Слабая типизация
- Строгая типизация
- Безопасность
- авторизация и т.д.
- время исполнения
- … и т.д.
- Ограничения на алгоритмы
- Поддержка существующий решений
- Потребность в перезапуске серверов, что бы опробовать новые решения
- Возможность на ходу опробовать новые решения
- Профилирование выполнения
Фича | Java-RMI | SOAP | REST-JSON | SQL | GraphQL | Hadoop |
---|---|---|---|---|---|---|
Передача данных | + | + | + | + | + | + |
Строго ограниченные форматы данных | +/- | +/- | - | - | + | - |
Гибкие форматы данных | +/- | +/- | + | + | - | + |
Передача программного кода | - | - | - | + | - | + |
Простые выражения | - | - | - | + | - | + |
Циклы/ветвления | - | - | - | + | - | + |
Процедуры/функции | - | - | - | + | - | + |
программные объекты… | - | - | - | ? | - | + |
Слабая типизация | - | - | + | - | - | + |
Строгая типизация | + | + | - | + | + | - |
авторизация | + | + | + | + | + | + |
время исполнения | ? | ? | ? | +/- | ? | ? |
Потребность в перезапуске серверов | + | + | ? | - | + | - |
Возможность находу опробывать новые решения | - | - | ? | + | - | + |
Профилирование выполнения | + | + | ? | + | + | ? |
- Большинство протоколов ориентированы только на передачу данных (Java-RMI, SOAP, REST-JSON, GraphQL)
- Часть из них работают со строго типизированными данными (Java-RMI, SOAP, GraphQL)
- Другие (REST-JSON, Hadoop) со слабо типизированными
- Небольшое кол-во протоколов поддерживают еще передачу программного кода (SQL, Hadoop)
Наличие строгой типизации и передачу программного кода из рассмотренных есть только в SQL
В предлагаемом проекте есть следующие возможности, с оговорками
- Передача данных (*)
- Гибкие форматы данных / комбинированные / структурированные типы данных
- Передача программного кода
- Простые выражения
- Циклы/ветвления
- Процедуры/функции/
- программные объекты… (**)
- Ограничения на интерпретацию алгоритмов
- Строгая типизация
- Безопасность (***)
- Поддержка существующий решений
- Возможность находу опробовать новые решения
- Профилирование выполнения (****)
Оговорки
(*)
- передаваемые типы должны быть Serializable
- требуется апробация Proxy для интерфейсов
(**)
- требуется апробация Proxy для объектов - очень неоднозначный вопрос
(***)
- реализована проверка байт-кода, без учета текущего пользователя
- не реализован механизм аутентификации, см план реализации
(****)
- еще не реализовано, см план реализации
Область применения
Поскольку проект только начат, говорить о реальном применении рано, можно говорить о потенциальном применении
Возможны следующий области применения
- фильтрация данных в программах написанных на Java по аналогии SQL WHERE (уже есть)
- выполнение серверных процедур по аналогии RPC/RMI/SOAP/… (уже есть)
- подписка клиента на события сервера
- масштабирование нагрузки (как частный случай реализации сетевого протокола)
При дальнейшем развитии возможно автоматическая прозрачная трансляция JAVA/Kotlin/Scala кода в целевые системы (SQL, MongoDB, REST, …)
Данное возможно при условии развития функции декомпиляции байт-кода в код AST/Java/…, по факту такая функция реализована в декомпиляторе JAD
Что собственно введет к уменьшению издержек при разработке ПО.
Ограничения
На текущей стадии реализации есть ряд ограничений, часть из них можно устранить
- Версия JVM на сервере должна быть не ниже чем на клиенте
- Не все языки/компиляторы поддерживаются, в текущий момент реализовано только Java 11
- Согласованность типов данных сервера и клиента