Какой выбрать - Java? Python? Большинство организаций используют и то, и другое, здесь нет необходимости говорить больше. Два самых популярных языка в мире.
Иногда нам нужно разделить логику между ними. Как? Написание логики дважды на каждом языке, когда логика становится более сложной и постоянно меняется, - это большой запрет. Так что мы можем сделать?
Совместное использование логики между Java и Python
Вариант 1 - микросервис
Создайте (микро) сервис, написанный на одном из двух языков, и поделитесь API (например, rest / gRPC) для общей логики.
У сервиса есть недостатки:
- Связь. Иногда две системы (Python и Java) отсоединены друг от друга, например мобильное приложение Java и конвейер машинного обучения серверной части Python.
- SLA - Иногда одна команда владеет логикой и обновляет ее. Например, группа специалистов по данным владеет алгоритмом на Python, который будет использоваться в производственной системе, написанной на Java. Вероятно, им не хватает навыков / ресурсов для поддержки микросервиса с требованиями высокой доступности для инженерной группы, работающей с производственной системой с использованием Java.
- Производительность - вызов службы имеет ограничения производительности. Даже с учетом современных технологий (таких как gRPC) добавление 50 мс на каждый запрос может быть слишком много для некоторых систем реального времени.
Вариант 2 - Общая библиотека
Написать общую библиотеку для обоих, используя Python или Java в качестве языка для общей библиотеки, сложно. Некоторые проблемы - две виртуальные машины, два сборщика мусора, многопоточность, сериализация, снижение производительности… 😕
Есть несколько проектов по импорту Java из Python (которые кажутся менее готовыми к производству). Взгляд на то, как PySpark решил это, дает нам представление о сложности этой проблемы.
Ржавчина спешит на помощь
На помощь приходит Rust - как разделяемая библиотека с привязками для обоих языков (ссылка на полный проект на github).
Многие популярные языки поддерживают Native Extensions / FFI, включая Java и Python. Поскольку Rust не имеет сборщика мусора и может быть скомпилирован в общую библиотеку, как это делает C (файл .SO /.DLL), существуют ящики для большинства популярных языков для написания собственных расширений в Rust, сегодня мы будем использовать следующие ящики:
Мы будем использовать функцию рабочее пространство Cargo для управления несколькими пакетами.
Давайте начнем!
обзор файлов проекта:
Шаг 1 - напишите логику в библиотеке Rust
Давайте создадим наш пакет, содержащий нашу общую логику - пакет rsdivider. В этом примере мы просто расскажем о функции div_numbers:
добавьте файл Cargo.toml пакета:
пока ничего особенного.
Шаг 2 - завершите логику Python
Теперь давайте используем PyO3, чтобы обернуть пакет rsdivider как модуль Python:
Код здесь простой, мы создаем функцию python и добавляем ее в наш модуль. В строке 3 мы импортируем нашу общую библиотеку, написанную на шаге 1.
Чтобы скомпилировать его, мы можем запустить cargo build --release из корневого каталога.
наш модуль python можно использовать так:
››› импорт rsdivider_py
››› rsdivider_py.div_numbers (4,2)
мы создаем библиотеку с именем rsdivider_py, которая является динамической библиотекой C (совместимой с C библиотекой).
мы будем использовать общую библиотеку как зависимость от пути к rsdivider, чтобы всегда создавать и использовать последний исходный код.
примечание: для PyO3 нам нужна особенность специализации ржавчины (то есть ночная версия ржавчины). вы можете следить за этой проблемой, чтобы решить, когда PyO3 поддерживает стабильный Rust. Существует альтернативный ящик rust-cpython, который предлагает аналогичный способ обертывания python и может быть построен со стабильной версией rust.
Шаг 3 - завершите логику для Java
Подобно оболочке Python, давайте создадим еще один пакет под названием rsdividerjava.
pub extern сообщает компилятору экспортировать функцию в нашу библиотеку в стиле C (т.е. может использоваться, как если бы она была скомпилирована из C).
# [no_mangle] - чтобы Rust не искажал имя нашей функции.
# [allow (non_snake_case)] - отключите предупреждения линтера о соглашениях об именах, нам нужно использовать Java_ {lib} _ {function} в качестве имени функции, чтобы ее можно было красиво импортирован из Java.
jdouble - мы конвертируем java-double в float64 и снова возвращаем результат как jdouble.
Наш Cargo.toml, аналогичный Python:
Пример использования из java:
Прежде чем это заработает, нам нужно создать файл .h для нашей библиотеки:
$ javah RsDivider
мы получим файл RsDivider.h в том же каталоге, который выглядит следующим образом:
Теперь у нас есть собственное расширение, используемое из нашего файла main.
Резюме
Мы можем разделять логику между Java, Python и многими другими языками, которые поддерживают собственные расширения с использованием ржавчины.
Взгляните на это репозиторий github, чтобы увидеть полный пример того, как заставить его работать.