Spring Boot + Python: від експерименту до open-source

💡 Усі статті, обговорення, новини про Java — в одному місці. Приєднуйтесь до Java спільноти!

Вступ

У 2024 році я почав вивчати Spring AOP і вирішив перевіряти знання на практиці — створив експериментальний проєкт.

Ідея була простою: щось унікальне та реально корисне. У Java майже немає сучасних бібліотек для інтеграції з Python. Py4J і Jython або обмежені, або застарілі, а повноцінної інтеграції зі Spring Boot не існувало.

Так я вирішив створити бібліотеку для зв’язку Java та Python. Спочатку це був експеримент з AOP і динамічним кодом, але згодом проєкт виріс у повноцінну open-source бібліотеку, яку можна підключити однією залежністю.

Початок експерименту: вивчення AOP у Spring Boot

Спочатку проєкт під назвою spring-python-executor позиціонувався як звичайний приклад моїх навичок для резюме й нічого більше. Тому до середини 2024 я швидко завершив розробку. Код був «на костилях», про безпеку чи довгострокову розширюваність не йшлося, але воно працювало.

Перша версія працювала на шести анотаціях:

  • @PythonBeforeMethod
  • @PythonAfterMethod
  • @Py4JBeforeMethod
  • @Py4JAfterMethod
  • @SpelythonBeforeMethod
  • @SpelythonAfterMethod

@PythonBeforeMethod і @PythonAfterMethod працювали зі звичайними скриптами, незалежними від Py4J або Spring Boot.

@Py4JBeforeMethod і @Py4JAfterMethod відповідали за використання Py4J разом зі скриптом, вказаним у методі.

@SpelythonBeforeMethod і @SpelythonAfterMethod були «родзинкою» версії. Вони реалізовували Spelython (SpEL + Python), дозволяючи використовувати SpEL-вирази в кожному скрипті.

Я вирішив викласти цю версію в Maven Central Repository за гайдом з YouTube, думаючи, що вона «пройде» без належного тестування.

P.S. Вона не запрацювала — я забув додати spring.factories.

Другий етап експерименту: виправлення та усвідомлення

У 2025 я вирішив повністю оновити своє резюме й перевірити працездатність проєктів. Як ви вже здогадалися, spring-python-executor не виправдав очікувань...

Я почав глибше вивчати екосистему Spring Boot і створення Java-проєктів, які не соромно публікувати в Maven Central.

Прочитавши документацію, я почав виправляти старі помилки. Логічні помилки усувалися за кілька десятків хвилин, але глобальною проблемою була складність розширення коду. Виконання скриптів через ProcessBuilder теж виявилося не найкращим рішенням.

Я дійшов висновку, що треба розділити «спагеті-код» на модулі й додати більше варіантів виконання скриптів, наприклад через REST або gRPC.

Перед цим я додав підтримку RestrictedPython і зрозумів, що 6 анотацій — це надмір, тож потрібно змінювати контракти між інтерфейсами.

Фінальний етап: перехід до production-ready коду

Перейменувавши проєкт у spring-boot-python-executor та розділивши його на модулі (spring-boot-python-executor-common, spring-boot-python-executor-core, spring-boot-python-autoconfigure, spring-boot-python-executor-starter), я значно покращив якість коду та розширюваність.

Першим кроком у рефакторингу стало виправлення контрактів між інтерфейсами й створення централізованого PythonProcessor для виконання Python-скриптів, прибравши надмірну кількість анотацій.

Тепер залишилося лише 4 анотації:

  • @PythonBefores
  • @PythonBefore
  • @PythonAfters
  • @PythonAfter

Анотації з ...s виконують кілька анотацій без s. Також додано профілювання.

За Spelython або Py4J відповідає PythonResolverHolder зі списком потрібних PythonResolver.

За спосіб виконання скриптів відповідає PythonExecutor, який тепер має дві реалізації: GrpcPythonExecutor і RestPythonExecutor.

Я створив два Python-сервери й виклав їх у Docker Hub:

  • w4t3rcs/spring-boot-python-executor-python-grpc-server
  • w4t3rcs/spring-boot-python-executor-python-rest-server

«Вишенькою на торті» стало додавання модулів для кешування та testcontainers.

Приклади використання

Детальніше про бібліотеку можна дізнатися з README.md і Javadoc у репозиторії проєкту (github.com/...​ring-boot-python-executor).

Висновок

За рік мій маленький експеримент перетворився на повноцінний open-source проєкт з гнучкою архітектурою, підтримкою REST, gRPC і безпечного виконання Python-скриптів. Зараз spring-boot-python-executor вже можна використовувати у production, підключивши всього одну залежність.

Далі планую розширювати функціонал, покращувати документацію та додавати приклади реальних сценаріїв.

А тепер питання до вас:

  • Чи поверталися ви до своїх старих незавершених проєктів?
  • Чи доводилося вам інтегрувати Python і Java в одному проєкті?
  • Який спосіб взаємодії між ними для вас найзручніший?
  • Чи цікава вам ідея глибокої інтеграції Python у Spring Boot?

Давайте обговоримо це в коментарях! Буду радий зворотному зв’язку та вашим ідеям для розвитку проєкту!

👍ПодобаєтьсяСподобалось10
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Чи доводилося вам інтегрувати Python і Java в одному проєкті?

Нащо це? Інтегрувати Python і Java в одному проєкті доцільно через мікросервіси. Кожна мова має свої сильні сторони: Java для високопродуктивних систем, Python — для аналізу даних і машинного навчання. Мікросервіси дозволяють писати окремі компоненти на кожній мові. Це гнучкіше і масштабованіше, ніж пряме інтегрування кодів двох мов в одному додатку.

Чи цікава вам ідея глибокої інтеграції Python у Spring Boot?

Ідея глибокої інтеграції Python у Spring Boot справдi цікава, але вона виглядає складною і не завжди виправданою з точки зору архітектури. Якщо мета — використати переваги обох мов у межах одного додатку, можна розглядати підхід через мікросервіси, як я вже згадував.

Дякую за Ваш коментар. Повністю згоден з Вами щодо мікросервісів, але деякі проєкти надають перевагу швидкості, тому, на мою думку, такий сценарій використання двух мов в одному коді має місце на існування. На даний момент, я оновлюю цю бібліотеку, щоб дати можливість відмовитись від REST, gRPC або Process API і перейти на більш гнучкі та швидкі варіанти

Execute Python scripts in a local process on the same machine as your Java application. This implementation has a bunch of limitations, so it’s a lot more suggestible to use REST/gRPC implementations.

Чесно?
Не дуже зрозуміло нафіга тоді город городить.

Нє, воно технічно норм зроблено, solid.
Але local направду не local, бо воно outside of JVM. Та ще й bunch of limitations.
А протоколи remote call по суті language agnostic.

Local малося на увазі, що виконується через Process. На рахунок лімітів, я мав на увазі проблеми з розгортанням у Докері і т.д., тому бажано використовувати або REST або gRPC. Local може підійти для маленьких задач або при тестуванні, коли не хочеться запускати контейнер з рест або грпс сервером. Але дуже дякую за коментар, все сказано справедливо і треба буде зайнятися рефакторингом, і якщо ще є якісь проблеми з кодом або архітектурою, то я з великим задоволенням все вислухаю

Local малося на увазі, що виконується через Process

Дякую, звісно — але я вмію читать.

Але дуже дякую за коментар, все сказано справедливо і треба буде зайнятися рефакторингом, і якщо ще є якісь проблеми з кодом або архітектурою

А от ви не то шоб були уважні, в свою чергу.

Мій пойнт: за відсутності реально локального виконання в самій JVM, і того факту що ремоут-протоколи взагалі не потребують жодних знань шо саме там крутиться на тому боці, а тільки як це викликать — нафіга цей тул ВЗАГАЛІ?

Ну, окрім як показать на інтерв’ю шо ви маєте ’em skills?

Добре, я зрозумів ваш пойнт і дякую за ваше зауваження. Спробую виправити відсутність локального виконання у наступній версії проєкту

дякую за пост
цікава бібліотека, пару років тому, памʼятаю командою переписували Python скрипти з математикою на Java
з цією лібою думаю все б злетіло куди краще і швидше 😁

Дуже дякую за відповідь. Якщо не секрет скільки часу зайняло переписувати скрипти і з якими труднощами зіштовхнулися?

математика була складна, але переписали відносно швидко, годин 16
довше зайняло приєднання логіки до існуючої системи та тести
коли вже перейшли до тестів, то зрозуміли, що Python трішки по іншому рахує зі своїми NaN, і BigDecimal у Java вимагає вигадування своїх фільтрів, які нам треба дописати самим щоб результати співпадали

так а рест до того пітона ніяк не було причепить і зробить з його сервіс?

ні, треба було з найменшою затримкою отримувати результат

Підписатись на коментарі