Какой выбрать - 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, сегодня мы будем использовать следующие ящики:

  • PyO3 - привязки Rust для Python
  • JNI - Привязки Rust для Java

Мы будем использовать функцию рабочее пространство 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, чтобы увидеть полный пример того, как заставить его работать.