From 02b040a57b744876b141a625fdba6ab8daaa008e Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 13 Aug 2026 17:30:31 +0200 Subject: [PATCH] feat(B-EVT+B-SCHEMA): Event-System Rollen + Schema Authority dokumentiert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B-EVT: Plugin-Dev-Guide Kapitel 8 — 4 Event-Systeme mit Rollen: - HookRegistry (Lifecycle), EventBus (ephemeral), Outbox (durable), WebhookDispatcher (external) - Entscheidungsregel: Wann welches System - Verboten: dieselbe Funktion über Hook UND EventBus B-SCHEMA: Plugin-Dev-Guide Kapitel 9 — Schema Authority: - Core → Alembic, Plugin → Plugin-Migrationen, Runtime Auto-Sync → nicht authoritative - Migration-Staffelung, Plugin-Migrationen, keine Schema-Drift --- docs/plugin-development-guide.md | 140 +++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/docs/plugin-development-guide.md b/docs/plugin-development-guide.md index 91c1c07..702a2e7 100644 --- a/docs/plugin-development-guide.md +++ b/docs/plugin-development-guide.md @@ -964,4 +964,144 @@ total_cost += result["cost_usd"] --- +## 8. Event-System Rollen + +LeoCRM hat **4 Event-Systeme** mit klar getrennten Rollen. **Nicht dieselbe Funktion über Hook UND EventBus triggern.** + +### Übersicht + +| System | Rolle | Persistenz | Use Case | +|--------|-------|-----------|----------| +| **HookRegistry** | Lifecycle-Erweiterungspunkte | In-Memory | `contact.before_create`, `mail.after_send`, `dms.after_delete` — Plugins können Daten anpassen oder reagieren | +| **EventBus** | Flüchtige interne Events | In-Memory | `notification.created`, `ui.contact_selected` — asynchrone Notifikationen, UI-Events, Proactive Suggestions | +| **Outbox** | Dauerhafte Domain Events | DB (transactional) | `contact.created`, `mail.received`, `task.completed` — reliable Delivery, Retry, DLQ, Worker-Polling | +| **WebhookDispatcher** | Externe HTTP-Zustellung | DB + HTTP | Externe Webhooks an registrierte URLs — Retry, Auth, Payload-Signatur | + +### 8.1 HookRegistry (`app/core/hooks.py`) + +**Wann verwenden:** Wenn ein Plugin bei einem Lifecycle-Punkt Daten anpassen oder reagieren will. + +```python +from app.core.hooks import get_hook_registry + +reg = get_hook_registry() + +# Action — kein Return, nur Seiteneffekte +reg.register_action("contact.before_create", self._on_contact_create, priority=10) + +# Filter — Return modifizierten Wert +def _format_name(self, name: str) -> str: + return name.title() +reg.register_filter("contact.format_display_name", self._format_name, priority=10) +``` + +**Aufruf im Core/Plugin-Service:** +```python +from app.core.hooks import do_action, apply_filters + +await do_action("contact.before_create", contact_data, db=db) +display_name = await apply_filters("contact.format_display_name", contact.name) +``` + +### 8.2 EventBus (`app/core/event_bus.py`) + +**Wann verwenden:** Für flüchtige interne Notifikationen, UI-Events, Proactive Suggestions. **Nicht** für Events die reliable Delivery brauchen. + +```python +from app.core.event_bus import get_event_bus + +event_bus = get_event_bus() + +# Subscribe +event_bus.subscribe("notification.created", self._on_notification) + +# Publish (ephemeral — geht verloren bei Crash) +await event_bus.publish("notification.created", {"user_id": "...", "message": "..."}) +``` + +### 8.3 Outbox (`app/core/outbox.py`) + +**Wann verwenden:** Für dauerhafte Domain Events die reliable Delivery, Retry und Worker-Verarbeitung brauchen. + +```python +from app.core.outbox import enqueue_outbox_event + +# In derselben Transaktion wie die Business-Operation +await enqueue_outbox_event(db, tenant_id, "contact.created", { + "contact_id": str(contact.id), + "tenant_id": str(tenant_id), +}) +# Transaction commit → Event ist durable → Worker pollt und published an EventBus +``` + +**Features:** DLQ (`error_message`, `failed_at`), Replay (`replay_failed_event`), Consumer Registry, Stats. + +### 8.4 WebhookDispatcher (`app/core/webhook_dispatcher.py`) + +**Wann verwenden:** Für externe HTTP-Zustellung an registrierte Webhook-URLs. + +```python +from app.core.webhook_dispatcher import register_webhook_event_handlers + +# Wird automatisch im Worker registriert — Plugins müssen nur Webhook-Configs erstellen +# und Events über die Outbox publishen +``` + +### 8.5 Entscheidungsregel + +```text +Braucht das Event reliable Delivery + Retry? + → JA → Outbox + → NEIN → Braucht es Daten-Anpassung (Filter)? + → JA → HookRegistry (register_filter) + → NEIN → Braucht es nur Reaktion (Action)? + → JA → HookRegistry (register_action) + → NEIN → Ist es eine flüchtige Notifikation / UI-Event? + → JA → EventBus + → NEIN → Geht es an externe Systeme? + → JA → WebhookDispatcher (über Outbox) + → NEIN → Braucht kein Event +``` + +**Verboten:** Dieselbe Funktion über Hook UND EventBus triggern — das führt zu Doppel-Ausführung und Race-Conditions. + +--- + +## 9. Schema Authority + +LeoCRM hat eine klare Schema-Authority-Hierarchie. **Kein neuer Schema-Mechanismus.** + +### Authority-Regeln + +| Schema-Typ | Authority | Wie | +|-----------|----------|-----| +| **Core-Tabellen** | Alembic-Migrationen | `alembic revision --autogenerate -m "..."` → `alembic upgrade head` | +| **Plugin-Tabellen** | Plugin-Migrationsweg | `plugin/migrations/` → `sync_plugin_schema.py` bei Aktivierung | +| **Runtime Auto-Sync** | **Nicht authoritative** | `Base.metadata.create_all` in Tests/dev — nie in Produktion | + +### Verbindliche Regeln + +1. **Core-Schema-Änderungen** immer über Alembic-Migrationen — nie manuelle SQL-Statements in Produktion +2. **Plugin-Schema-Änderungen** über Plugin-Migrationen — nie Core-Migrationen für Plugin-Tabellen +3. **Runtime Auto-Sync** (`create_all`, `sync_plugin_schema.py`) ist Convenience für Dev/Tests — **nicht** für Produktion authoritative +4. **Migration-Staffelung** beachten: neu → migrieren → umstellen → testen → release → alt entfernen +5. **Keine Schema-Drift** — wenn Core und Plugin dasselbe Modell nutzen, ist Core authoritative + +### Plugin-Migrationen + +```python +# plugin/migrations/001_initial.py +from alembic import op + +def upgrade(): + op.create_table("my_plugin_table", ...) + +def downgrade(): + op.drop_table("my_plugin_table") +``` + +Plugin-Migrationen werden bei Plugin-Aktivierung automatisch ausgeführt (`sync_plugin_schema.py`). Bei Deaktivierung bleiben die Tabellen erhalten (Soft-Deactivate). Bei Uninstall werden sie gedroppt. + +--- + *This document is authoritative for all plugin development at LeoCRM.*