Zum Inhalt springen

Module (NXM)

Moduldienste

Code eines NXM-Moduls, der außerhalb von nexxus läuft. Wie nexxus einen Moduldienst aufruft, wie der Dienst mit einem kurzlebigen Token zurückruft und wie Sie einen Dienst bauen, betreiben und anbinden.

Für
Entwickler
Dauer
20 Min.

Deklarationen und Ansichten decken das meiste ab, was ein Modul tut. Manche Arbeit braucht aber echten Code: eine gesetzliche Frist mit den Feiertagen eines Bundeslands berechnen, reagieren, wenn sich ein Datensatz ändert, den Text eines eingegangenen PDFs lesen. Dieser Code läuft nie innerhalb von nexxus. Er läuft in einem Moduldienst, einem eigenen HTTP-Dienst, den das Paket anmeldet und den nexxus aufruft.

Die Regel dahinter ist einfach. Ein Paket kommt von außerhalb der Plattform, also bleibt sein Code auch außerhalb des Plattformprozesses. nexxus schickt dem Dienst, was er braucht, zusammen mit einem signierten Token, das eine Minute gilt. Der Dienst ruft über dieselbe Datensatz-API zurück wie alle anderen, mit genau den Rechten, denen die Administration bei der Installation zugestimmt hat. Er hält keine Passwörter und keine API-Schlüssel.

Ein echtes Beispiel ist der Fristendienst aus der Fallstudie CuriaOS: Er berechnet Fristen, legt Vorfristen an und schlägt Fristen vor, die er in Gerichtsschreiben findet.

Ein Dienst lohnt sich, wenn

  • eine Aktion Logik braucht, die update, create und Vorlagen nicht abbilden, etwa eine Berechnung mit eigenen Regeln,
  • das Modul auf Änderungen reagieren muss, die Menschen, Aktionen oder der KI-Assistent vornehmen, zum Beispiel um ein zusammenfassendes Feld aktuell zu halten,
  • die Arbeit eine Bibliothek braucht, die eine Ansicht nicht laden kann, etwa zum Auslesen von PDF-Text,
  • die Arbeit im Hintergrund läuft, ohne dass jemand auf der Seite ist.

Reicht eine Ansicht oder eine deklarative Aktion, brauchen Sie keinen Dienst.

  1. Etwas passiert

    Ein Datensatz ändert sich, die Installation wird installiert, aktualisiert oder aktiviert, oder jemand führt eine Dienst-Aktion aus.

  2. nexxus ruft den Dienst

    POST an den Dienst mit einem signierten Aufruf-Token, gültig für 60 Sekunden.

  3. Der Dienst prüft

    Er prüft das Token gegen die öffentlichen Schlüssel von nexxus.

  4. Token-Tausch

    Beim ersten Rückruf tauscht er das Token einmal gegen ein Zugriffstoken, gültig für 10 Minuten.

  5. Der Dienst ruft zurück

    Er liest und schreibt Datensätze über die Datensatz-API. nexxus prüft bei jeder Anfrage die Rechte.

Das Aufruf-Token beweist, dass nexxus den Aufruf geschickt hat. Das Zugriffstoken lässt den Dienst als Dienstkonto der Installation handeln, nie mit mehr Rechten.

Im Einzelnen, für einen Aufruf, der Datensätze liest und schreibt:

nexxus
Moduldienst
  1. 1POST /events oder /actions/<operation>, Bearer-Aufruf-Token (60 s)nexxus → Moduldienst
  2. 2GET /api/module-runtime/.well-known/jwks.json (zwischengespeichert)Moduldienst → nexxus
  3. 3Signatur, typ, iss, aud, exp prüfen und ob trigger zum Rumpf passtModuldienst
  4. 4POST /api/module-runtime/token: Aufruf-Token einmal eintauschenModuldienst → nexxus
  5. 5Zugriffstoken für das Dienstkonto (10 Min.)nexxus → Moduldienst
  6. 6GET, POST, PATCH /api/x/<modul>/<fachobjekt> mit dem ZugriffstokenModuldienst → nexxus
  7. 7Dienstkonto, zugestimmte Scopes und, falls gesetzt, die Person prüfennexxus
  8. 8Antwort 2xx: Das Ereignis gilt als zugestellt, eine Aktion bekommt ihr ErgebnisModuldienst → nexxus
Der Dienst hat keine eigenen Zugangsdaten. Jedes Token, das er nutzt, stammt aus diesem Aufruf und läuft nach Minuten ab.

Ein Dienst gehört zum Manifest. So meldet das Paket curiaos.kanzlei seinen Fristendienst an:

"services": [
{
"key": "fristendienst",
"label": { "de": "Fristendienst", "en": "Deadline service" },
"description": { "de": "Berechnet Fristen ...", "en": "Computes deadlines ..." },
"url": "http://localhost:8390/fristendienst",
"events": ["frist.created", "frist.updated", "frist.deleted", "aktendokument.created"],
"operations": ["berechnen", "eintragen"]
}
]
FeldTypPflichtBedeutung
keyTextjaName des Dienstes im Paket, z. B. fristendienst
label{de, en, pl?}jaErscheint im Katalog und in Fehlermeldungen
description{de, en, pl?}neinWas der Dienst tut
urlText, http(s)://jaStandardadresse. Eine Instanz kann sie überschreiben (siehe unten)
eventsListe von TextenneinDatensatz-Ereignisse, die der Dienst erhält: <fachobjekt>.created, .updated, .deleted der Fachobjekte des eigenen Pakets. Von anderen Paketen hört ein Dienst nichts
operationsListe von TextenneinOperationen, die Aktionen mit type: "service" aufrufen dürfen

Eine Aktion ruft eine Operation so auf:

{ "key": "frist-berechnen", "entity": "frist", "type": "service",
"service": "fristendienst", "operation": "berechnen",
"label": { "de": "Frist berechnen", "en": "Compute deadline" }, "aiTool": true }

Dauert die Operation länger, setzen Sie "async": true. nexxus antwortet der Person dann, sobald der Dienst den Aufruf angenommen hat, und zeigt „„<Aktion>“ läuft. Das Ergebnis erscheint, sobald der Dienst fertig ist.“

Welche Kerndaten außerhalb seines eigenen Moduls der Dienst anfassen darf, steht unter scopes, zum Beispiel core:dmsDocument:read. Jedem Scope und jedem Dienst samt Host stimmt die Administration beim Installieren oder Aktualisieren des Pakets zu. Die vollständige Liste der Scopes steht unter Paketaufbau.

Ein Paket mit Moduldienst, wie der Katalog es zeigt
Vor der Installation sieht die Administration den Dienst, seinen Host und die Ereignisse, die er erhält.

Jeder Aufruf ist ein POST mit Authorization: Bearer <Aufruf-Token> und einem JSON-Rumpf. Jeder Rumpf enthält die empfangende Installation mit Version und Einstellungen. In settings stehen nur die Schlüssel, die das Manifest unter settings anmeldet. Jede Antwort mit Status 2xx gilt als Erfolg.

POST <url>/events, wenn ein Datensatz eines abonnierten Fachobjekts angelegt, geändert oder gelöscht wurde:

{
"id": 4711,
"type": "frist.updated",
"module": "curiaos.kanzlei",
"entity": "frist",
"recordId": 42,
"data": { "titel": "Berufungsfrist", "faellig_am": "2026-11-16", "status": "offen" },
"occurredAt": "2026-10-09T08:15:00Z",
"installation": { "id": 7, "version": "1.0.0", "settings": { "bundesland": "by", "vorfrist_werktage": 3 } }
}

module ist das empfangende Paket selbst: Ein Dienst erfährt nur von Datensätzen seines eigenen Pakets. type ist immer <fachobjekt>.<vorgang> ohne Paketpräfix.

POST <url>/events, wenn die Installation installiert, aktualisiert oder aktiviert wurde, ganz ohne Abonnement:

{
"id": 4712,
"type": "installation.installed",
"module": "curiaos.kanzlei",
"installation": { "id": 7, "version": "1.0.0", "settings": { "bundesland": "by", "vorfrist_werktage": 3 } },
"occurredAt": "2026-10-09T08:20:00Z"
}

Die Typen heißen installation.installed, installation.upgraded und installation.activated. Mit ihnen holt ein Dienst nach, was er getan hätte, wäre er von Anfang an dabei gewesen, etwa ein berechnetes Feld für alle vorhandenen Datensätze füllen. previousVersion ist bei Aktualisierungen gesetzt und dann, wenn eine entfernte Installation wieder installiert wird.

POST <url>/actions/<operation>, wenn eine Person oder der KI-Assistent eine Aktion mit type: "service" ausführt:

{
"action": "frist-berechnen",
"recordId": 42,
"record": { "id": 42, "module": "curiaos.kanzlei", "entity": "frist", "data": { "titel": "Berufungsfrist" }, "version": 3 },
"payload": { "ereignisdatum": "2026-10-01", "regel": "berufung" },
"installation": { "id": 7, "version": "1.0.0", "settings": { "bundesland": "by", "vorfrist_werktage": 3 } }
}

Das JSON, mit dem Ihr Dienst antwortet, wird zum result der Aktion. nexxus wartet bis zu 30 Sekunden. Zum Ablehnen antworten Sie mit einem 4xx-Status und einem JSON-Objekt { "errorCode": "COMPOSE_STALE", "message": "...", "messages": { "de": "...", "en": "...", "pl": "..." } }; jeder Schlüssel ist optional, schicken Sie aber mindestens einen Code oder einen Text. nexxus antwortet der Person mit 422 SERVICE_REJECTED und gibt Ihren Code und Status daneben als serviceCode und serviceStatus weiter, so kann eine Ansicht auf COMPOSE_STALE reagieren, ohne den Text zu lesen. Der Text steht in der Sprache der Person: mit messages Ihr eigener Text in dieser Sprache (sonst Englisch, sonst Deutsch) ohne Vorsatz; mit nur einer message setzt nexxus „Der Dienst „<Label>“ hat die Aktion abgelehnt:“ davor, auf Englisch “The service “<Label>” refused the action:”, auf Polnisch „Usługa „<Label>” odrzuciła akcję:“. Texte kommen als reiner Text an, je höchstens 300 Zeichen.

POST <url>/portal/<key>, wenn eine Besucherin oder ein Besucher des Kundenportals des Mandanten einen Endpunkt nutzt, den das Paket unter portal.endpoints mit diesem Dienst anmeldet. Wer das Portal besucht, ist anonym: Das Token trägt keinen principal, sein Auslöser ist portal:<key>, ein Token für einen Endpunkt ruft also keinen anderen auf. nexxus reicht nur die angemeldeten params weiter (record als Zahl, die übrigen so, wie sie kamen) und bei einem POST das form, das nexxus schon geprüft hat (Typen, Pflichtfelder, E-Mail-Adresse, Einwilligung, Honeypot, Ratenlimit); ein GET schickt form: null. Vorher prüft nexxus auch, dass record ein öffentlicher Datensatz ist. locale ist die Sprache der Portalseite, de, en oder pl. Auch ein GET-Endpunkt erreicht den Dienst als POST. nexxus wartet bis zu 10 Sekunden.

{
"endpoint": "quote",
"method": "GET",
"params": { "record": 12, "check_in": "2026-11-12", "check_out": "2026-11-16", "guests": "4" },
"form": null,
"locale": "en",
"installation": { "id": 7, "version": "1.2.0", "settings": { "currency": "ZAR", "time_zone": "Africa/Johannesburg" } }
}

Prüfen Sie alles noch einmal: Der Aufruf kommt aus dem Internet. Das JSON einer 2xx-Antwort (höchstens 64 KB) geht unverändert an die Besucherin oder den Besucher, antworten Sie also nur mit dem, was die Seite zeigen darf. Eine Ablehnung, die dort gelesen werden soll, ist ein 400, 409 oder 422 mit { "errorCode": "...", "message": "...", "messages": { "de": "...", "en": "...", "pl": "..." }, "fieldErrors": { "<feld>": "..." } } (pl optional), die Texte in der Sprache der Seite; nexxus reicht sie mit demselben Status weiter (als reinen Text, je höchstens 300 Zeichen). Nehmen Sie 422 für falsche Eingaben und 409, wenn der Stand es nicht zulässt, etwa weil die Nächte inzwischen belegt sind. Einen Datensatz, den die Seite nicht zeigt, lehnt nexxus schon vor dem Weiterreichen mit 404 ab. Jeder andere Status und keine Antwort in der Zeit erreichen die Besucherin oder den Besucher als allgemeiner Fehler. So beantwortet der Higgins-Dienst availability, quote und booking_request der Buchungsseite.

nexxus signiert jeden Aufruf mit ES256. Der Kopf trägt kid und typ: nexxus-module-it. Die öffentlichen Schlüssel liegen unter GET <nexxus>/api/module-runtime/.well-known/jwks.json, ohne Sitzung abrufbar.

ClaimBedeutung
issnexxus
audDer Modulschlüssel, z. B. curiaos.kanzlei
submodule:<modulschlüssel>
tenantID des Mandanten
instID der Installation
verInstallierte Version des Moduls
triggerWas den Aufruf ausgelöst hat: event:<typ>, lifecycle:<typ> oder action:<schlüssel>
principal{userId, name} der Person, die die Aktion ausgeführt hat; fehlt bei Ereignissen
jti, iatToken-ID und Ausstellungszeit
expiat plus 60 Sekunden

Prüfen Sie typ, iss, aud, sub und exp, und prüfen Sie, dass trigger zum Aufruf passt: Ein Ereignis mit type frist.updated braucht event:frist.updated. So lässt sich ein Token für einen Aufruf nicht für einen anderen wiederverwenden. nexxus nimmt nur ES256 an und duldet 5 Sekunden Uhrenabweichung.

Beim ersten Rückruf tauscht der Dienst das Aufruf-Token gegen ein Zugriffstoken (RFC 8693):

POST /api/module-runtime/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<Aufruf-Token>
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 600,
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}

Dieselben Felder nimmt der Endpunkt auch als JSON an. Jedes Aufruf-Token lässt sich genau einmal tauschen, und nur solange die Installation aktiv ist. Fehler folgen RFC 6749: Status 400 mit {error, error_description}, error ist invalid_grant, invalid_request oder unsupported_grant_type.

Claim des ZugriffstokensBedeutung
typ (Kopf)nexxus-module-at
audnexxus
subID des Dienstkontos der Installation
act{sub: "module:<modulschlüssel>"}
tenant, instMandant und Installation
principalAus dem Aufruf-Token übernommen, falls eine Person gehandelt hat
scopeDie zugestimmten Scopes, durch Leerzeichen getrennt
expHöchstens 10 Minuten nach Ausstellung

nexxus nimmt das Zugriffstoken nur auf /api/x/** (Datensätze und Aktionen), /api/module-runtime/lookup und /api/module-runtime/documents/** an. Überall sonst antwortet es mit 401 und „Ein Modul-Token gilt nur für die Modul-Schnittstelle.“

Ein Dienst handelt als Dienstkonto seiner Installation. nexxus legt dieses Konto bei der Installation an, eines je Installation, mit einer Rolle, die alle Rechte des Moduls hält. Mit dem Konto kann sich niemand anmelden, und nexxus schaltet es ab, solange das Modul deaktiviert oder deinstalliert ist. Datensätze, die der Dienst schreibt, nennen das Konto als Ersteller. Es heißt in der Standardsprache des Mandanten, der Name des Moduls und dahinter „(Moduldienst)“ auf Deutsch, „(module service)“ auf Englisch oder „(usługa modułu)“ auf Polnisch, und nexxus benennt es bei jedem Installieren, Aktualisieren, Aktivieren und Wiederinstallieren neu. Seine Rolle heißt ebenso.

DatenZugriff
Datensätze des eigenen ModulsLesen und schreiben, auch Felder mit readonly
Kerndaten (Partner, Benutzer, Dokumente, ISMS-Maßnahmen)Nur im Rahmen der zugestimmten core:-Scopes
Dokumente (PDF-Inhalt)Mit core:dmsDocument:read, und nur Dokumente, auf die ein Datensatz des eigenen Moduls verweist

Hat eine Person die Aktion ausgeführt, trägt das Token sie als principal, und nexxus prüft zusätzlich die Rechte dieser Person. Beides muss erfüllt sein. Ereignis-Zustellungen tragen keine Person.

In den Quellen von nexxus liegt unter module-services/ ein Kit für Moduldienste: ein Prozess mit Bun und TypeScript, der je Dienst eine Gruppe von Routen bedient. Das Kit prüft die Tokens, bindet sie an den Aufruf, tauscht sie beim ersten Rückruf und protokolliert jede Anfrage ohne Tokens und ohne Rumpf. Sie schreiben die Handler.

Ein Dienst ist eine ModuleServiceDefinition:

EigenschaftBedeutung
nameOrdnername und Pfadpräfix: rueckgabe bedient POST /rueckgabe/events, POST /rueckgabe/actions/<operation> und POST /rueckgabe/portal/<key>
moduleKeyDas Modul, zu dem der Dienst gehört; das aud des Tokens muss es nennen
eventsHandler für Datensatz-Ereignisse
lifecycleHandler für installation.installed, .upgraded und .activated
actionsHandler je Operation, z. B. { zaehlen }
portalHandler der öffentlichen Portal-Endpunkte je Schlüssel, z. B. { quote }. Ein Handler darf mit einer eigenen Response antworten, um den Status einer Ablehnung zu wählen

Jeder Handler bekommt {claims, body, log, platform}. platform ist ein PlatformClient, der mit dem Zugriffstoken zurückruft:

MethodeTut
search(entity, {q?, ids?, filter?, sort?, from?, size?})Eine Seite Datensätze, {items, total}. ids beschränkt auf diese Datensätze, höchstens 200; mehr wirft einen Fehler, bevor etwas gesendet wird
searchAll(entity, query?)Alle passenden Datensätze, Seite für Seite (200 je Seite, nach ID sortiert). Nimmt beliebig viele ids und fragt sie zu je 200 ab
get(entity, id)Einen Datensatz
create(entity, data)Legt einen Datensatz an
patch(entity, id, data, version?)Ändert Felder; mit version antwortet ein neuerer Stand mit CONFLICT
documentContent(documentId)Den Inhalt eines Dokuments, auf das verwiesen wird

entity ist ein Fachobjekt des eigenen Moduls; ein anderes Paket erreicht das Kit nicht. Ein fehlgeschlagener Plattform-Aufruf wirft einen PlatformError mit status, errorCode und message. Mit HttpError(status, errorCode, message) antworten Sie selbst mit einem Fehler.

Dieser Dienst gehört zum Paket acme.leihgeraete aus dem Tutorial zum ersten Modul. Er gibt jedem verliehenen Gerät ohne Rückgabedatum eines in 14 Tagen und bietet eine Operation, die überfällige Geräte zählt.

import type { ModuleServiceDefinition } from '../kit/service.ts';
export function rueckgabedienst(): ModuleServiceDefinition {
return {
name: 'rueckgabe',
moduleKey: 'acme.leihgeraete',
async events({ body, platform, log }) {
if (body.type !== 'geraet.updated') return { ok: true, ignoriert: body.type };
// Ereignisse können spät oder doppelt kommen: den aktuellen Datensatz lesen statt body.data zu trauen.
const geraet = await platform.get('geraet', body.recordId);
if (geraet.data.status !== 'verliehen' || geraet.data.rueckgabe_am) return { ok: true };
const faellig = new Date(Date.now() + 14 * 86_400_000).toISOString().slice(0, 10);
await platform.patch('geraet', geraet.id, { rueckgabe_am: faellig }, geraet.version);
log.info('rueckgabedatum gesetzt', { recordId: geraet.id });
return { ok: true };
},
actions: {
async zaehlen({ platform }) {
const heute = new Date().toISOString().slice(0, 10);
const { total } = await platform.search('geraet', {
filter: { status: 'verliehen', rueckgabe_am: { to: heute } }, size: 1,
});
return { faellig: total };
},
},
};
}

Nehmen Sie ihn in src/main.ts in die Liste der Dienste auf und melden Sie ihn im Manifest an:

"services": [{
"key": "rueckgabe",
"label": { "de": "Rückgabedienst", "en": "Return service" },
"url": "http://localhost:8390/rueckgabe",
"events": ["geraet.updated"],
"operations": ["zaehlen"]
}],
"actions": [{
"key": "faellige-zaehlen", "entity": "geraet", "type": "service",
"service": "rueckgabe", "operation": "zaehlen",
"label": { "de": "Fällige zählen", "en": "Count due" }
}]

Sein eigenes patch kommt beim Dienst nicht als Ereignis an, er kann sich also nicht selbst auslösen.

Das Kit braucht eine Umgebungsvariable und hält keine Geheimnisse:

VariableStandardBedeutung
PLATFORM_URLPflichtBasis-URL des nexxus-Backends, wie der Dienst es erreicht, ohne Schrägstrich am Ende. Von dort holt er JWKS, Token-Tausch und Datensatz-API
PORT8390Port, auf dem der Dienst lauscht
Terminal-Fenster
bun install
PLATFORM_URL=https://nexxus.example.com bun dev # mit Neustart bei Änderungen
bun test
bun run typecheck

GET /health antwortet ohne Token mit {"status": "UP", "services": [...]}. Im Ordner liegt ein Dockerfile, das das Kit als Benutzer ohne Root-Rechte startet. Nur nexxus muss den Dienst erreichen, und der Dienst muss nexxus erreichen.

Das Kit ist kein Muss. Jeder HTTP-Dienst in jeder Sprache funktioniert, wenn er das Aufruf-Token wie oben beschrieben prüft, es tauscht und die Datensatz-API aufruft.

Die url im Manifest ist nur ein Standard, und localhost ist selten der Ort, an dem der Dienst läuft. nexxus nimmt die erste brauchbare Adresse aus

  1. der Betreiberkonfiguration der Instanz,
  2. der Installationseinstellung serviceUrls.<dienst>,
  3. der url aus dem Manifest.

Es zählen nur absolute http- und https-URLs; alles andere fällt auf die nächste Quelle durch.

Zeigt die url aus dem Manifest auf localhost oder 127.0.0.1 und setzt die Instanz nexxus.modules.local-service-base-url, ersetzt nexxus Schema, Host und Port durch diese Adresse; Pfad und Abfrage bleiben. Laufen alle Dienste einer Instanz hinter einer Adresse, etwa http://module-services:8390, braucht so nicht jeder Dienst eine eigene Zeile (Umgebungsvariable NEXXUS_MODULES_LOCALSERVICEBASEURL). Für die Zustimmung zählt der Host nach dem Ersetzen. Die Installationseinstellung bleibt, wie sie ist, auch wenn sie localhost nennt.

nexxus:
modules:
service-urls:
"[acme.leihgeraete.rueckgabe]": http://rueckgabe:8390/rueckgabe

Der Schlüssel ist <modulschlüssel>.<dienstschlüssel> in eckigen Klammern, weil Modulschlüssel einen Punkt enthalten. Als Umgebungsvariable lautet derselbe Schlüssel NEXXUS_MODULES_SERVICEURLS_ACME_LEIHGERAETE_RUECKGABE. Über die API setzt die Administration die Installationseinstellung mit PATCH /api/module-installations/<schlüssel>/settings und {"settings": {"serviceUrls": {"rueckgabe": "https://..."}}}.

Die Zustimmung gehört zum Host. Die Administration stimmt service:<schlüssel>@<host> zu (mit Port, wenn die URL einen nennt), im Installationsdialog zu lesen als „Dienst „<Label>“ unter <host> aufrufen und ihm Ereignisse schicken“. Zeigt ein Dienst auf einen anderen Host, braucht er deshalb eine neue Zustimmung. Setzt die Administration die URL über die Installationseinstellungen, gilt diese Änderung als Zustimmung. Eine Änderung des Hosts in der Betreiberkonfiguration wirkt genauso auf die Zustimmung: Solange sie nicht passt, verweigert nexxus den Dienst.

Zustellung, Wiederholungen und die Karte der Zustellungen

Abschnitt betitelt „Zustellung, Wiederholungen und die Karte der Zustellungen“

nexxus schreibt jedes Ereignis in derselben Transaktion wie die Änderung in einen Ausgang. Kein Ereignis geht verloren, wenn eine Änderung gespeichert wird, und keines geht hinaus, wenn die Änderung zurückgerollt wurde.

  • Alle 2 Sekunden verschickt nexxus die fälligen Ereignisse, mit einer Zeitgrenze von 10 Sekunden.
  • Die Zustellung erfolgt mindestens einmal und nicht in Reihenfolge. Ein wiederholtes Ereignis kann nach einem späteren ankommen.
  • Nach dem n-ten Fehlschlag wartet das Ereignis 10 s × 2n−1 (10, 20, 40, 80, 160, 320, 640 Sekunden). Nach dem 8. Fehlschlag gilt es als fehlgeschlagen, etwa 21 Minuten nach dem ersten Versuch.
  • Ist die Installation nicht mehr aktiv oder der Dienst nicht mehr zugestimmt, scheitert das Ereignis sofort mit „Installation nicht aktiv“ oder „Dienst „<key>“ ist nicht freigegeben“.
  • Ein Dienst erhält keine Ereignisse zu seinen eigenen Schreibvorgängen. Folgearbeit muss er in derselben Bearbeitung erledigen.
  • Bei einer Aktualisierung schickt jeder Migrationsschritt ein Ereignis je geändertem Datensatz, noch vor installation.upgraded. Mehr dazu unter Daten und Aktualisierungen.
  • Zugestellte und fehlgeschlagene Ereignisse bewahrt nexxus 30 Tage auf.

Schreiben Sie Handler also so, dass sie zweimal laufen dürfen: Erkennen Sie Wiederholungen an der id des Ereignisses, lesen Sie den aktuellen Datensatz, statt data zu vertrauen, und schreiben Sie nur Werte, die sich unterscheiden.

Die Zustellungen sieht die Administration auf der Paketseite unter AdministrationBetriebModule, Reiter Pakete, in der Karte Ereigniszustellungen: die letzten 100 Ereignisse mit Ereignis, Zustand (Wartet, Zugestellt oder Fehlgeschlagen), der Zahl der Versuche, dem letzten Fehler und Erzeugt. Aktualisieren lädt die Liste neu, außerdem aktualisiert sich die Karte alle 5 Sekunden selbst.

Diese Meldungen sieht eine Person, wenn eine Dienst-Aktion scheitert:

MeldungUrsacheAbhilfe
„Der Dienst „<Label>“ ist für diesen Mandanten nicht freigegeben. Eine Administratorin oder ein Administrator muss ihm unter System › Module zustimmen.“Die Zustimmung der Installation enthält den Dienst mit seinem aktuellen Host nichtDie Administration stimmt erneut zu: Das Setzen der Dienst-URL in den Installationseinstellungen gilt als Zustimmung, und der Aktualisierungsdialog einer neuen Version fragt danach
„Der Dienst „<Label>“ hat die Aktion abgelehnt: <message>“, für Personen, die auf Englisch oder Polnisch arbeiten, in deren Sprache, oder der eigene Text des Dienstes aus messagesDer Dienst hat mit 4xx und einer message oder messages geantwortetLesen Sie die Meldung; sie ist die Begründung des Dienstes
„Der Dienst „<Label>“ hat die Aktion abgelehnt (<Code>).“Der Dienst hat mit 4xx und einem errorCode ohne Text geantwortetSchicken Sie zum Code eine message oder messages, damit die Person den Grund erfährt
„Der Dienst „<Label>“ hat die Aktion abgelehnt (HTTP <status>).“Der Dienst hat mit 5xx geantwortet oder mit 4xx ohne Code und ohne TextBeheben Sie den Fehler im Dienst oder begründen Sie eine Ablehnung mit JSON {"errorCode": "...", "message": "..."}
„Der Dienst „<Label>“ ist nicht erreichbar.“ oder „Der Dienst „<Label>“ wurde nicht erreicht.“Keine Verbindung oder keine Antwort innerhalb von 30 SekundenPrüfen Sie, ob der Dienst läuft und ob die wirksame URL ihn erreicht
„Der Dienst „<Label>“ hat keine lesbare Antwort geliefert.“Die Antwort war kein JSONAntworten Sie mit JSON oder mit leerem Rumpf

Bei Ereignissen hilft die Karte der Zustellungen: Der letzte Fehler jedes Ereignisses steht in der Spalte Zustand. Prüfen Sie, ob der Dienst PLATFORM_URL erreicht und ob seine Uhr richtig geht, denn beide Tokens werden gegen ihre Ablaufzeit geprüft. Prüft ein Dienst Tokens gegen die Schlüssel einer anderen nexxus-Instanz, antwortet er mit 401 und kid unknown: Sein PLATFORM_URL zeigt auf die falsche Plattform.

Ereigniszustellungen, die nach einem abgelehnten Token auf den nächsten Versuch warten
Der letzte Fehler sagt, warum: Hier prüft der Dienst Tokens gegen die Schlüssel einer anderen Plattform.
SchlüsselStandardBedeutung
nexxus.modules.service-urls."[<modul>.<dienst>]"keinerWo ein Moduldienst läuft
nexxus.modules.local-service-base-urlkeinerErsetzt localhost und 127.0.0.1 in der url der Manifeste, etwa durch http://module-services:8390
nexxus.modules.tokens.invocation-ttl-seconds60Lebensdauer eines Aufruf-Tokens
nexxus.modules.tokens.access-ttl-seconds600Lebensdauer eines Zugriffstokens
nexxus.modules.tokens.clock-skew-seconds5Geduldete Uhrenabweichung
nexxus.modules.events.dispatcher-enabledtrueNur auf Instanzen abschalten, die nicht zustellen sollen
nexxus.modules.events.dispatch-interval-ms2000Wie oft fällige Ereignisse hinausgehen
nexxus.modules.events.batch-size20Ereignisse je Durchgang
nexxus.modules.events.max-attempts8Versuche, bevor ein Ereignis als fehlgeschlagen gilt
nexxus.modules.events.backoff-base-seconds10Wartezeit nach dem n-ten Fehlschlag: Basis × 2n−1
nexxus.modules.events.timeout-seconds10Zeitgrenze je Zustellung
nexxus.modules.events.retention-days30Wie lange zugestellte und fehlgeschlagene Ereignisse bleiben
nexxus.modules.actions.timeout-seconds30Zeitgrenze einer Dienst-Aktion

Die Signaturschlüssel für die Tokens erzeugt nexxus beim ersten Start und legt sie verschlüsselt in seiner Datenbank ab, gemeinsam für alle Instanzen der Plattform.

  • Ein Tausch, zehn Minuten. Ein Aufruf-Token lässt sich einmal tauschen, und das Zugriffstoken lässt sich nicht verlängern. Ein Aufruf kann also höchstens 10 Minuten mit nexxus arbeiten.
  • Lange Handler gelten als gescheitert. Ein Ereignis-Handler, der länger als 10 Sekunden braucht, zählt als fehlgeschlagener Versuch, auch wenn er später fertig wird. Halten Sie jeden Schreibvorgang idempotent, damit die Wiederholung die Arbeit erledigt vorfindet.
  • Eine Instanz des Kits. Das Kit merkt sich zugestellte Ereignis-IDs im Speicher. Zwei parallel laufende Instanzen könnten dieselbe Arbeit doppelt tun.