Worum es geht
CRMS erzeugt Berichte aus Vorlagen und den Daten einer Anwendung. Es kennt dieselben zwei Bauformen wie CIAS:
- eingebettet (
local): CRMS läuft im Prozess einer CDMS-Anwendung. Die Anwendung ruft CRMS über Java-Methoden auf. - als eigener Dienst (
remote): CRMS läuft in einem anderen Prozess. Die Anwendung ruft es über HTTP auf, mit dem kleinen Bausteincrms-client.
CRMS steht dabei immer neben CIAS, nie an seiner Stelle. Es verwaltet keine Personen, keine Mandanten und keine Rollen. Es fragt CIAS, wer gerade anfragt und für welchen Mandanten.
Die Bauform von CRMS ist eine eigene Entscheidung, unabhängig von der Bauform von CIAS. Was die beiden Arten im Einzelnen mitbringen, steht im CRMS-Modul unter Eingebettet oder als eigener Dienst.
Die Prozessbilder
Ein Prozess ist ein laufendes Programm. Die Bilder zeigen, was jeweils in einem Prozess liegt.
Wann: CRMS und CIAS laufen beide im Prozess der CDMS-Anwendung. So arbeitet das Hub-Backend.
flowchart LR
F["Frontend mit BFF"] -- "Bearer-Token" --> P
subgraph P["Ein Prozess"]
direction TB
FK["Filterkette"]
C["CDMS"]
R["CRMS"]
CI["CIAS-Module"]
FK --> C
C -->|"Methodenaufruf<br/>ReportRenderPort"| R
R -->|"Methodenaufruf<br/>Aufrufer, Mandanten"| CI
end
P --> DB[("System-DB + Mandanten-DBs<br/>CDMS-, CIAS- und CRMS-Tabellen")]
R --> S3[("S3-Bucket<br/>endgültige Dokumente")]
classDef client fill:#475569,stroke:#475569,color:#fff
classDef cdms fill:#1976d2,stroke:#1976d2,color:#fff
classDef cias fill:#8e24aa,stroke:#8e24aa,color:#fff
classDef crms fill:#ffc107,stroke:#b38600,color:#1f1a00
classDef db fill:#4d7c0f,stroke:#4d7c0f,color:#fff
class F client
class C cdms
class FK,CI cias
class R crms
class DB,S3 db
Ergebnis: Kein Netz zwischen den dreien. Ist der Prozess oben, sind alle drei oben.
Wann: CRMS liegt in der CDMS-Anwendung, CIAS ist ein eigener Dienst.
flowchart LR
F["Frontend mit BFF"] -- "Bearer-Token" --> P
subgraph P["Dienst 1: CDMS-Anwendung"]
direction TB
FK["Filterkette"]
TC["Mandanten-Client"]
C["CDMS"]
R["CRMS"]
FK --> C
C -->|"Methodenaufruf<br/>ReportRenderPort"| R
R -->|"Aufrufer"| FK
R -->|"Mandantenliste"| TC
end
TC -- "HTTP + Dienst-Token" --> CI["Dienst 2: CIAS"]
P --> DB[("System-DB + Mandanten-DBs<br/>CDMS- und CRMS-Tabellen")]
classDef client fill:#475569,stroke:#475569,color:#fff
classDef cdms fill:#1976d2,stroke:#1976d2,color:#fff
classDef cias fill:#8e24aa,stroke:#8e24aa,color:#fff
classDef crms fill:#ffc107,stroke:#b38600,color:#1f1a00
classDef db fill:#4d7c0f,stroke:#4d7c0f,color:#fff
class F client
class C cdms
class FK,TC,CI cias
class R crms
class DB db
Ergebnis: CRMS ruft CDMS weiter als Methode auf. Nur was CRMS von CIAS braucht und nicht in der Filterkette liegt, geht über HTTP.
Wann: Das CRMS-Modul liegt in einer anderen Laufzeit. Die CDMS-Anwendung ist nur Client.
flowchart LR
F["Frontend mit BFF"] -- "Bearer-Token" --> P
subgraph P["Dienst 1: CDMS-Anwendung"]
direction TB
FK["Filterkette"]
C["CDMS"]
CC["crms-client"]
FK --> C
C -->|"ReportRenderPort"| CC
end
subgraph Q["Dienst 2: CRMS"]
direction TB
FK2["Filterkette<br/>Token-Tausch"]
R["CRMS"]
FK2 --> R
end
CC -- "HTTP + Token der Person" --> FK2
Q -- "HTTP + Dienst-Token<br/>Mandant? Mandantenliste?" --> CI["CIAS"]
P --> DB1[("Datenbanken der Anwendung")]
Q --> DB2[("Datenbank des CRMS-Dienstes")]
classDef client fill:#475569,stroke:#475569,color:#fff
classDef cdms fill:#1976d2,stroke:#1976d2,color:#fff
classDef cias fill:#8e24aa,stroke:#8e24aa,color:#fff
classDef crms fill:#ffc107,stroke:#b38600,color:#1f1a00
classDef db fill:#4d7c0f,stroke:#4d7c0f,color:#fff
class F client
class C cdms
class FK,FK2,CI cias
class CC,R crms
class DB1,DB2 db
Ergebnis: Die Anwendung trägt von CRMS nur crms-client. Der CRMS-Dienst prüft das Token selbst und fragt CIAS über HTTP.
Im dritten Bild ist es für die CDMS-Anwendung gleich, ob CIAS bei ihr eingebettet ist oder nicht. Sie spricht mit CRMS nur über crms-client. Der eigenständige CRMS-Dienst fragt CIAS immer über HTTP, wie ein CDMS-Dienst mit getrenntem CIAS. „Dienst 2“ kann auch eine andere CDMS-Anwendung des Projekts sein, in die CRMS eingebettet ist. Dann gilt für sie das erste oder zweite Bild.
Die Kombinationen
Aus Sicht einer CDMS-Anwendung gibt es drei Fälle. Jeder Fall entsteht im Hub, nicht durch eine Zeile, die du von Hand schreibst.
| Alles eingebettet | CRMS eingebettet, CIAS getrennt | CRMS als eigener Dienst | |
|---|---|---|---|
| CRMS-Platzierung | local | local | remote |
| CIAS-Einbindung | EMBEDDED | REMOTE | EMBEDDED oder REMOTE, für CRMS ohne Bedeutung |
| Von CRMS im Prozess | Starter, die Dokumentablage, die Formatmodule und die HTTP-Schnittstelle von CRMS | wie links | Starter und crms-client, sonst nichts |
| Woher CRMS den Aufrufer kennt | aus der Filterkette im selben Prozess | aus der Filterkette im selben Prozess | im CRMS-Dienst, aus dessen eigener Filterkette |
| Woher CRMS die Mandantenliste kennt | von der Mandantenverwaltung von CIAS im selben Prozess, Methodenaufruf | vom CIAS-Dienst über HTTP, mit dem Dienst-Token der Anwendung | im CRMS-Dienst, vom CIAS-Dienst über HTTP |
| Wo die CRMS-Tabellen liegen | in den Datenbanken der Anwendung | in den Datenbanken der Anwendung | in der Datenbank des CRMS-Dienstes |
Eine Zeile fehlt absichtlich: CRMS ohne CIAS gibt es nicht. CRMS braucht CIAS, um den Aufrufer zu kennen und den Mandanten zu prüfen. Ein System, das nicht über CIAS anmeldet, bekommt kein eingebettetes CRMS.
Wer ruft wen wie
| Wer fragt | Wen | Was | Wie, und mit welchem Token |
|---|---|---|---|
| CDMS-Code | CRMS eingebettet | Bericht rendern, Dokument lesen | Methodenaufruf an ReportRenderPort oder DocumentArchivePort, kein Token |
| CDMS-Code | CRMS-Dienst | Bericht rendern, Dokument lesen | dieselben Ports, crms-client schickt HTTP mit dem Token der Person der laufenden Anfrage |
| CRMS | CIAS | Wer ruft gerade auf? | Methodenaufruf an die Filterkette im eigenen Prozess, in jeder Bauform |
| CRMS | CIAS | Welche Mandanten gibt es? (Zeitgeber) | eingebettet ein Methodenaufruf, getrennt HTTP mit einem Dienst-Token |
| Frontend | CRMS | Vorlagen bearbeiten, Dokumente lesen | HTTP an /crms/**, mit dem Token der Person: bei der Anwendung, wenn CRMS eingebettet ist, sonst beim CRMS-Dienst |
| CIAS | CRMS | Welche Rollen meldest du an? | eingebettet ein Methodenaufruf an die Bean crmsIdentityRegistry. Ein eigener CRMS-Dienst meldet nichts an |
Ein paar Wörter dazu:
- Ein Port ist eine Java-Schnittstelle, über die deine Anwendung CRMS aufruft. Welche es gibt, steht unter Der Client für den eigenen Dienst.
- Die Filterkette ist der Teil von CIAS, der bei jeder Anfrage das Token prüft und den Aufrufer festlegt. Siehe Die Beteiligten einer Anfrage.
- Die Rollen von CRMS sind Namen in seiner Konfiguration (
templateRoleUser,templateRoleTenant,documentRoleRead…). Siehe Die Rollen und CRMS beim Start.
So ruft eine CDMS-Anwendung CRMS auf
Du lässt dir den Port von Spring geben, zum Beispiel in einem eigenen Controller oder in einem Hook, und rufst ihn mit den Daten auf. Das Hub-Backend macht es so für seine Projektdokumente:
@RestController
@RequestMapping(path = "api/rest/cms/report")
public class ReportApi {
private final ReportRenderPort renderer; // embedded CRMS or crms-client
public ReportApi(ReportRenderPort renderer) {
this.renderer = renderer;
}
// ... builds a RenderRequest from the payload and calls renderer.render(request)
}
Derselbe Code läuft in beiden Bauformen. Welche Implementierung hinter ReportRenderPort steckt, entscheidet der Klassenpfad: eingebettet das CRMS selbst, sonst crms-client. Wie eine Render-Anfrage aussieht, steht unter Die Render-Anfrage.
Ein Bericht von vorn bis hinten
Wann: CRMS läuft in der CDMS-Anwendung.
sequenceDiagram
participant B as BFF
participant F as Filterkette (CIAS)
participant C as CDMS (Controller oder Hook)
participant R as CRMS
participant DB as Mandanten-DB
B->>F: POST /api/rest/… + Token (Person aus nordbau)
F->>F: Token prüfen, tauschen, Mandant nordbau zulassen
F->>C: RequestContext gefüllt
C->>R: render(request)
Note over C,R: Methodenaufruf, kein Netz
R->>F: Wer ruft auf? Person, nordbau, Rollen
R->>DB: Vorlage auflösen, bei FINAL ablegen
R-->>C: RenderedDocument
C-->>B: Dokument
Ergebnis: Eine Prüfung des Tokens, ein Prozess.
Wann: Die CDMS-Anwendung ruft einen CRMS-Dienst über crms-client.
sequenceDiagram
participant B as BFF
participant F as Filterkette (in CDMS)
participant C as CDMS (Controller oder Hook)
participant K as crms-client
participant S as CRMS-Dienst
participant I as CIAS
B->>F: POST /api/rest/… + Token (Person aus nordbau)
F->>F: Token prüfen, tauschen, Mandant nordbau zulassen
F->>C: RequestContext gefüllt
C->>K: render(request)
K->>S: POST /crms/render + dasselbe Token
S->>S: Token prüfen, gegen eigenen Client tauschen
S->>I: wird nordbau bedient? (Dienst-Token)
I-->>S: ja
S->>S: rendern wie eingebettet
S-->>K: Dokument
K-->>C: dasselbe RenderedDocument
C-->>B: Dokument
Ergebnis: Das Token wird zweimal geprüft: einmal von der Anwendung, einmal vom CRMS-Dienst. Jede Revision und jedes Dokument nennt trotzdem die Person, nicht einen Dienst.
Warum hier das Token der Person reist und bei den Lookups von CIAS ein Dienst-Token? Weil CRMS im Namen der Person handelt: Es legt Dokumente in ihrem Mandanten ab, prüft ihre Rollen und schreibt ihren Namen in die Kette. Ein Dienst-Token würde das verwischen. Die Lookups von CIAS fragen dagegen nach einem Sachverhalt, den der Dienst selbst wissen will. Siehe CIAS als eigener Service und Token-Tausch.
Der Hub legt die Platzierung fest
Ein Projekt hat höchstens ein CRMS-Modul. Wo du es im Hub hinlegst, also in welche Laufzeit (das Programm, das später läuft), bestimmt die Platzierung jeder CDMS-Anwendung des Projekts. Ein Feld für local oder remote gibt es nicht.
| CRMS-Modul im Projekt? | CIAS-Einbindung der Anwendung | Wo liegt das CRMS-Modul? | Ergebnis |
|---|---|---|---|
| nein | – | – | kein CRMS |
| ja | NONE | in einer anderen Laufzeit | kein CRMS in dieser Anwendung |
| ja | NONE | in ihrer eigenen Laufzeit | Build abgelehnt: CRMS braucht CIAS |
| ja | EMBEDDED oder REMOTE | in keiner Laufzeit | Build abgelehnt: das CRMS-Modul muss in eine Laufzeit |
| ja | EMBEDDED oder REMOTE | in ihrer eigenen Laufzeit | local: CRMS eingebettet |
| ja | EMBEDDED oder REMOTE | in einer anderen Laufzeit | remote: Client des CRMS dort |
Die Ableitung läuft genauso wie bei CIAS, siehe Was läuft, bestimmt die Konfiguration. Beide Entscheidungen fallen getrennt. Darum sind alle Kombinationen oben möglich, und zwei CDMS-Anwendungen desselben Projekts können CRMS verschieden nutzen: Die eine bettet es ein, die andere ruft es dort auf. Liegt das CRMS-Modul in einer Laufzeit ohne CDMS-Anwendung, wird daraus ein eigener CRMS-Dienst.
Der Build schreibt codamai.crms.placement in die application.yaml und setzt die passenden Jars in die POM. Beim Start prüft CRMS beides gegeneinander, siehe CRMS beim Start.
Gleich und nicht gleich
Für CRMS gilt dieselbe Regel wie für CIAS: Die Bauform ist eine Entscheidung über Betrieb, nicht darüber, was herauskommt. Ein Integrationstest schickt dieselben Anfragen an ein eingebettetes und an ein entferntes CRMS und verlangt dieselben Antworten.
- dieselben Ports und dieselben Objekte, etwa
RenderedDocument - dasselbe Dokument aus derselben Vorlagenrevision
- dieselben Ablehnungen mit denselben Message Keys
- dieselben Rollenprüfungen, dieselbe Person an jeder Revision
- Ausfall: nur entfernt gibt es
crms-unavailable - Zeitgeber: laufen dort, wo CRMS eingebettet ist, also in der Anwendung oder im CRMS-Dienst
- Datenbank: die der Anwendung oder eine eigene
- Client-Rollen: die am Client der Anwendung oder die am Client des CRMS-Dienstes
Mehr zum entfernten Weg steht unter Der Client für den eigenen Dienst.