Zum Hauptinhalt springen

Der Weg eines Antrags

Ein Antrag entsteht im Onlinedienst, reist verschlüsselt durch FIT-Connect, wird in der Verwaltung geprüft und beantwortet. Diese Seite geht den Weg einmal komplett ab — auf jeder Station steht, was beide Rollen tun und welches Rezept die Details hat. Was der Onlinedienst sendet, ist genau das, was die Verwaltung empfängt; deshalb lohnt es sich, beide Seiten nebeneinander zu sehen.

OnlinedienstTyp C · sendet Anträge, liest AntwortenVerwaltung / UnternehmenTyp A/B · empfängt Anträge, antwortetFIT-Connect · Vorgang · Ereignisprotokoll1Einrichten2Adressieren3Senden4Zustellen & Status5Empfangen & Prüfen6AntwortenAntwort lesen
Station anklicken, um zum passenden Rezept zu springen.

Die ganze Reise im Code​

Onlinedienst-Seite — jede Zeile ist eine Station:

FitConnectSdk sdk = FitConnectSdk.fromConfigYaml(Path.of("fitconnect.yaml")); // 1 Einrichten
OnlineService svc = sdk.onlineService(MY_ONLINE_SERVICE_ID, vault::find);

Participant amt = sdk.directory().forService(LEIKA_KEY, "Anmeldung", ars); // 2 Adressieren

SentSubmission sent = svc.send(OutgoingSubmission.to(amt) // 3 Senden
.setData(SubmissionData.json(formularJson, schemaUri)).build());

EventState status = svc.cases().logOf(sent).latest().state(); // 4 Status

for (ReplyForPickup r : svc.awaitingReplies()) svc.accept(svc.receive(r)); // Antwort lesen

Verwaltungs-Seite — abholen, prüfen, entscheiden, antworten:

Organisation amt = sdk.organisation(MY_DESTINATION_ID, keys); // 1 Einrichten

for (SubmissionForPickup waiting : amt.awaitingSubmissions()) { // 5 Empfangen
ReceivedSubmission received = amt.receive(waiting);
if (!received.acceptable()) {
amt.reject(received, received.report().asProblems());
continue;
}
amt.accept(received);
amt.send(OutgoingReply.answering(received) // 6 Antworten
.setData(SubmissionData.json(bescheidJson, bescheidSchemaUri)).build());
}

Station für Station​

1 · Einrichten Beide​

Abhängigkeit, Zugangsdaten, SDK-Instanz, Rolle — für beide Seiten fast identisch. Nur der Empfänger braucht Schlüssel: einen zum Entschlüsseln der Anträge, einen zum Signieren seiner Events (Regel 4). → Rezept: Einrichten

2 · Adressieren Onlinedienst​

Welche Behörde ist zuständig? Leistung (LeiKa-Schlüssel) plus Region (ARS, AGS oder AreaId) ergibt den Zustellpunkt — ohne Login, mit signierter Antwort, die das SDK prüft. Die Verwaltung tut hier nichts im Code, aber im Portal: Nur was dort an Leistungen und Gebieten hinterlegt ist, findet das Directory. → Rezept: Empfänger finden

3 · Senden Onlinedienst​

Daten, Schema, Anhänge in den Builder. Das SDK holt den öffentlichen Schlüssel des Empfängers, verschlüsselt Daten und jeden Anhang einzeln (JWE), schreibt SHA-512-Hashes in die Metadaten, lädt Anhänge hoch — bei Bedarf in Stücken — und reicht die validierten Metadaten ein. Ergebnis: SentSubmission mit submissionId und caseId. Wer später eine Antwort lesen will, gibt hier einen Rückkanal mit. → Rezept: Antrag senden

4 · Zustellen & Status Beide​

Der Antrag öffnet einen Vorgang. Ab jetzt ist das Ereignisprotokoll die gemeinsame Wahrheit beider Seiten: SUBMITTED schreibt die Plattform, ACCEPTED und REJECTED schreibt der Empfänger — signiert. Statt zu pollen kann sich jede Seite per Callback benachrichtigen lassen. → Rezept: Status verfolgen · Konzept: Vorgänge und Ereignisprotokoll

5 · Empfangen & Prüfen Verwaltung​

Abholen, entschlüsseln, Bericht lesen — und dann selbst entscheiden. Das SDK prüft Metadaten, Daten und Anhänge und legt einen Prüfbericht vor; es lehnt nie von allein ab. Annehmen oder Ablehnen schreibt ein signiertes Event in den Vorgang, beim Ablehnen mit den Gründen, die der Onlinedienst auf Station 4 liest. → Rezept: Anträge abholen und prüfen · Konzept: Der Prüfbericht

6 · Antworten Beide​

Der Bescheid geht als Antwort in den Vorgang — nicht an einen Zustellpunkt (Regel 3). Der Onlinedienst öffnet ihn mit dem Rückkanalschlüssel, den er sich beim Senden pro Vorgang gemerkt hat, und bestätigt den Empfang. → Rezept: Antworten senden und lesen

Die Stationen sind genau die Reihenfolge, in der eine Integration entsteht — und genau die Reihenfolge, in der Fehler auftreten. Wer bei Station 5 eine FitConnectReplyException sieht, weiß damit sofort, dass auf Station 3 der Rückkanal fehlte.

Variante: Verwaltung beteiligt Verwaltung (Prozessnachrichten)​

Nicht jeder Weg beginnt im Onlinedienst. Sobald ein Antrag bei der zuständigen Stelle liegt, muss diese oft weitere Behörden beteiligen — im Bauantrag etwa den Brandschutz oder die untere Naturschutzbehörde um eine Stellungnahme bitten. Das ist Kommunikation von Typ A/B zu Typ A/B, und sie läuft über denselben Weg, mit zwei Unterschieden:

  • Adressiert wird ein Prozessschritt, keine Verwaltungsleistung. Statt eines LeiKa-Schlüssels benennt der Absender Prozessmodell und Nachricht (processModelId, messageId) — das Verzeichnis liefert den Zustellpunkt, der diesen Schritt bedient.
  • Alle Nachrichten hängen an einem Vorgang. Die Beteiligungsanfrage tritt dem Vorgang des Bürgerantrags bei (inCase), die Stellungnahme kommt als weitere Prozessnachricht in denselben Vorgang zurück. So bleibt im Ereignisprotokoll nachvollziehbar, wer wann wen beteiligt hat.

Die Stellungnahme ist dabei kein Reply: Antworten (Regel 3) liest nur der Onlinedienst, der den Vorgang eröffnet hat. Zwischen zwei Verwaltungen ist jede Nachricht ein Antrag — mit Prüfbericht, Annahme und Ablehnung wie auf Station 5.

Bauaufsicht — hat den Bürgerantrag auf Station 5 angenommen und beteiligt eine Fachbehörde:

// Prozessmodell und Nachrichten-IDs gibt das Prozessmodell vor (hier Beispielwerte)
String PROCESS = "urn:example:prozess:baugenehmigung";

Participant naturschutz = sdk.directory().forProcessMessage( // 2 Adressieren
PROCESS, "beteiligung-anfordern", "DE147130000000");

SentSubmission anfrage = bauaufsicht.send(OutgoingSubmission.to(naturschutz) // 3 Senden
.inCase(received.caseId()) // dem Vorgang des Bürgerantrags beitreten
.setData(SubmissionData.xml(anfrageXml, xbauSchemaUri))
.addAttachment(Attachment.fromFile(lageplan, "application/pdf"))
.build());

Naturschutzbehörde — empfängt wie jeden Antrag, erkennt die Prozessnachricht und antwortet mit einer Prozessnachricht in denselben Vorgang:

for (SubmissionForPickup waiting : naturschutz.awaitingSubmissions()) { // 5 Empfangen
ReceivedSubmission in = naturschutz.receive(waiting);
if (!(in.addressing() instanceof Addressing.ByProcessMessage p)) continue; // hier: nur Prozessnachrichten
log.info("Beteiligung {} im Vorgang {}", p.processMessage().messageId(), in.caseId());
naturschutz.accept(in);

Participant bauaufsicht = Participant.of( // zurück an den Absender
in.senderDestinationId(), Addressing.toProcessMessage(PROCESS, "stellungnahme"));

naturschutz.send(OutgoingSubmission.to(bauaufsicht)
.inCase(in.caseId()) // derselbe Vorgang
.setData(SubmissionData.xml(stellungnahmeXml, xbauSchemaUri))
.build());
}

build() verlangt bei Prozessadressierung einen Vorgang: inCase(bestehende caseId) wie hier, oder openingCase(serviceIdentifier, serviceName), wenn die Prozessnachricht selbst einen neuen Vorgang eröffnen soll.

Warum keine Replies zwischen Behörden? Ein Reply ist an den Vorgang und dessen Rückkanal gebunden — und den Rückkanalschlüssel hält nur, wer den Vorgang eröffnet hat (der Onlinedienst). Eine Prozessnachricht ist dagegen ein vollwertiger Antrag an einen Zustellpunkt vom Typ A/B: mit eigenem Schlüssel des Empfängers, eigenem Prüfbericht und eigener Annahme. Das Prozessmodell ersetzt nur die Verwaltungsleistung als Adresse — sonst ändert sich nichts.

Die Adressierung im Detail: Rezept: Empfänger finden → Prozessnachrichten.

Weiter geht's​