SDK
11. fejezet

Plugin deploy

A 2. fejezet lefedi a Maven projekt beállítást, a build konfigurációt és a ServiceLoader regisztrációt. Ez a fejezet onnan indul, ahol az elkészült target/plugins/{artifactId}/ könyvtár már ott van – és megmutatja, hogyan kerül a szerverre, hogyan frissíthető újraindítás nélkül, és mik a korlátok.

11.1   A plugin a szerveren

A platform egy dedikált plugins könyvtárat figyel, amelyet a PLUGINS_DIR környezeti változó jelöl ki. A könyvtáron belül minden plugin a saját alkönyvtárában él – ennek neve lesz a plugin azonosítója.

plugins/                    ← PLUGINS_DIR
  plugin-example/           ← plugin neve (alkönyvtár neve)
    plugin.jar
    plugin.properties       ← opcionális konfiguráció (lásd 11.2)
    lib/
      custom-driver.jar     ← saját függőségek (ha vannak)
  plugin-other/
    plugin.jar

A platform induláskor beolvassa a plugins könyvtárat, és futás közben is folyamatosan figyeli (→ 11.3). Minden alkönyvtárból betölti a plugin.jar-t és a lib/ tartalmát, majd a ServiceLoader fájl alapján regisztrálja a tool-okat. Ha egy alkönyvtárban nincs plugin.jar, a platform kihagyja.

ℹ️
A plugin alkönyvtár neve egyezik a Maven artifactId-dal – ez nem véletlen, a parent POM a build kimenetet erre a névre állítja be. Így a target/plugins/ tartalma közvetlenül átmásolható a szerver plugins/ mappájába.

11.2   Éles telepítés

A szerver Docker Compose-ban fut, a plugins könyvtár volume-on keresztül van becsatolva. Új plugin telepítése két lépés, újraindítás nélkül:

  1. Másold a build kimenetét a szerver plugins könyvtárába – a platform kb. 2 másodperc múlva automatikusan betölti.
  2. Ellenőrizd a logokban, hogy a tool-ok regisztrálódtak.
# Példa – plugin telepítése
cp -r target/plugins/plugin-example /srv/plugins/plugin-example

# Betöltési logok ellenőrzése
docker compose logs -f backend | grep -i plugin

Plugin frissítése éles környezetben is újraindítás nélkül történik: a régi alkönyvtár törlése, majd az új bemásolása (hot-replace, 11.3 fejezet). A frissítés pillanatában futó kérések a régi verzióval befejeződnek, az új kérések már az új verziót kapják.

ℹ️
Frissítésnél az egész alkönyvtárat cseréld le – ne csak a plugin.jar-t, mert a lib/ is változhatott.

11.3   Hot reload – újraindítás nélkül

A platform WatchService-szel figyeli a plugins könyvtárat. Alkönyvtár megjelenésekor automatikusan betölti az új plugin-t, eltűnésekor eltávolítja. Ez lehetővé teszi a fejlesztési körök gyorsítását anélkül, hogy a teljes platformot újra kellene indítani.

Hot-add – új plugin betöltése

Másold be a plugin alkönyvtárát a PLUGINS_DIR-be. A platform ~2 másodperc várakozás után (JAR másolás befejezéséhez) betölti.

# Új plugin bedobása futó platformba
cp -r target/plugins/plugin-example /srv/plugins/plugin-example

# Logban megjelenik:
# [PluginLoader] Plugin betöltése: plugin-example
# [PluginLoader] plugin-example – tool regisztrálva: example_tool (domain=null)
# [PluginLoader] plugin-example – betöltve: 1 tool, 0 configurer.

Hot-remove – plugin eltávolítása

Töröld a plugin alkönyvtárát. A platform azonnal eltávolítja a tool-okat a regisztrációból – új kérések már nem kapják meg a tool-t.

# Plugin eltávolítása futó platformból
rm -rf /srv/plugins/plugin-example

# Logban megjelenik:
# [PluginLoader] Plugin könyvtár eltűnt, unload: plugin-example
# [PluginLoader] Plugin eltávolítva: plugin-example

Hot-replace – frissítés újraindítás nélkül

Hot-replace a törlés + bemásolás kombinációja. A platform az eltűnéskor unloadolja a régi verziót, a megjelenéskor betölti az újat.

# 1. Régi verzió eltávolítása
rm -rf /srv/plugins/plugin-example

# 2. Új verzió bemásolása (a platform ENTRY_DELETE után ENTRY_CREATE-t vár)
cp -r target/plugins/plugin-example /srv/plugins/plugin-example

# Logban:
# [PluginLoader] Plugin könyvtár eltűnt, unload: plugin-example
# [PluginLoader] Plugin eltávolítva: plugin-example
# [PluginLoader] Plugin betöltése: plugin-example
# [PluginLoader] plugin-example – betöltve: 1 tool, 0 configurer.
⚠️
In-place JAR csere nem triggereli a hot reloadot. Ha csak a plugin.jar-t írja felül valaki a könyvtár törlése nélkül, nem keletkezik ENTRY_DELETE esemény a szülő könyvtáron – a platform nem észleli a változást. Mindig a teljes alkönyvtárat kell törölni és visszamásolni.

Korlátok és viselkedés

SzituációViselkedés
In-flight kérés unload közben A futó kérés befejezi magát – a tool Java példánya él, amíg van rá aktív referencia. Új kérések már nem kapják meg az eltávolított tool-t.
@Cacheable cache unload után Spring cache bejegyzések megmaradnak unload után. Ha a plugin új verziója más eredményt ad ugyanarra a cache kulcsra, rövid ideig stale adatot szolgálhat ki. TTL vagy manuális cache evict oldja meg.
Orchestrátor in-flight kérés Az unload pillanatában már példányosított orchestrátor példányok befejezik a kérést. A supplier eltávolítása csak az új kéréseket érinti.
IPluginConfigurer bean-ek A configurer által regisztrált extra Spring bean-ek (pl. EntityManagerFactory) az unload során automatikusan eltávolítódnak – hot-replace után az új konfigurátor frissen regisztrálja őket.
System prompt supplement ArangoDB-ben marad (szándékos) – újrabetöltéskor az OverwriteMode.ignore megakadályozza a duplikációt, a konfiguráció megőrződik.

Hot reload fejlesztési munkafolyamat

A platform lassan indul (Spark, modellek betöltése). Hot reload-dal a fejlesztési ciklus lerövidül: csak a plugint kell újraépíteni, a platformot nem kell újraindítani.

# Maven build (csak a plugin modul)
mvn package -pl my-plugin -am

# Hot-replace egy lépésben (bash alias-ként érdemes felvenni)
rm -rf /srv/plugins/my-plugin && \
  sleep 1 && \
  cp -r target/plugins/my-plugin /srv/plugins/my-plugin
ℹ️
A sleep 1 biztosítja, hogy az unload log megjelenjen mielőtt a betöltés elindul – diagnosztikailag jobb olvashatóság, nem funkcionális követelmény.

11.4   Tool-ok közötti függőségek – services fájl sorrend

Ha egy plugin tool egy másik, ugyanabban a pluginban lévő tool-t injektál @Autowired-del, a függőséget a META-INF/services fájlban előbb kell felsorolni, mint az injektáló tool-t.

A PluginLoader a services fájl sorrendjében dolgozza fel a tool-okat: minden tool autowireBean() + registerSingleton() hívást kap – ebben a sorrendben, egymás után. Amikor az injektáló tool autowireBean()-je fut, a függőségének már Spring beanként regisztrálva kell lennie.

# META-INF/services/hu.medical.rag.common.tool.intf.IChatbotTool

# ✅ Helyes: MyWebNERIngestionTool (függőség) előbb van mint MyContextSearchTool
hu.medical.rag.plugin.example.tool.MyWebNERIngestionTool
hu.medical.rag.plugin.example.tool.MyContextSearchTool    # @Autowired MyWebNERIngestionTool

# ❌ Hibás: fordított sorrend → UnsatisfiedDependencyException betöltéskor
hu.medical.rag.plugin.example.tool.MyContextSearchTool    # @Autowired MyWebNERIngestionTool
hu.medical.rag.plugin.example.tool.MyWebNERIngestionTool
⚠️
A @Lazy annotáció itt nem használható. A Spring CGLIB proxy generátor a platform classloaderéből próbál subclasst generálni a plugin osztályából – ez cross-classloader esetén ClassNotFoundException: ...$$SpringCGLIB$$0 hibával meghiúsul. A megoldás mindig a helyes services fájl sorrend.

Az IPluginConfigurer.configure() által regisztrált Spring bean-ek (pl. saját asszisztens osztályok) a tool-ok betöltése előtt kerülnek regisztrálásra – ezekre az ordering szabály nem vonatkozik, bármely tool injektálhatja őket.

11.5   Fejlesztői tesztelés lokálisan

A Maven build kimenetét közvetlenül lehet lokális tesztelésre használni – nincs szükség kézzel másolni. A platform PLUGINS_DIR konfigurációját a build output-ra kell irányítani:

# alkalmazás konfigurációban vagy környezeti változóként
PLUGINS_DIR=target/plugins

Mivel a target/plugins/{artifactId}/ struktúra azonos az éles deploy struktúrával, a fejlesztői szerver pontosan ugyanúgy tölti be a plugin-t. Fejlesztés közben elegendő mvn package után hot-replace-szel frissíteni – a platformot nem kell újraindítani.

11.6   Deploy ellenőrzőlista

LépésEllenőrzés
IChatbotTool ServiceLoader fájl megvan META-INF/services/hu.medical.rag.common.tool.intf.IChatbotTool létezik a JAR-ban és helyes osztályneveket tartalmaz (lásd 2. fejezet)
IPluginConfigurer fájl megvan (ha szükséges) Ha a plugin JPA-t vagy extra Spring bean-t regisztrál: META-INF/services/hu.medical.rag.common.plugin.IPluginConfigurer létezik és tartalmazza a konfigurátor osztály nevét. Lásd Adatbázis-hozzáférés fejezet.
Saját függőségek a lib/-ben Ha volt compile scope-os függőség, megjelent a target/plugins/{artifactId}/lib/-ben
Platform JAR-ok nincsenek a lib/-ben A platform-bom által kezelt függőségek nem kerültek be – ha mégis benne vannak, scope-problémára utal
Orchestrátor plugin: no-arg konstruktor megvan Plugin JAR-ból betöltött orchestrátor tool-hoz kötelező
Tool-ok közötti @Autowired függőség: sorrend helyes Ha Tool A injektál Tool B-t, Tool B a services fájlban előbb szerepel. @Lazy nem használható plugin tool-ok között – cross-classloader esetén CGLIB proxy generálás meghiúsul (lásd 11.4 fejezet)
Teljes alkönyvtár lett másolva Frissítésnél ne csak a plugin.jar-t cseréld – a lib/ is változhatott
Hot-replace: alkönyvtár törölve, majd visszamásolva In-place JAR csere nem triggereli a hot reloadot – a teljes alkönyvtárat kell törölni és visszamásolni
Log ellenőrzés A platform naplózza a betöltött tool-ok számát és nevét. Hiba esetén a cause-lánc is megjelenik a logban.