Worum es geht
Du schreibst für ein Modell keine Entity, kein DTO, keinen Controller. Das erledigt der Codegenerator bei jedem Build. Er liest die Metadaten aus dem Hub und erzeugt daraus alle Klassen, die ein Modell braucht, von der REST-Schnittstelle bis zur Datenbank.
Der Generator ist ein Annotation-Processor. Das ist ein Programm, das der Java-Compiler während des Kompilierens aufruft und das selbst neue Quelldateien schreiben darf. Die neuen Dateien kompiliert der Compiler im selben Lauf gleich mit.
Der Ablauf
flowchart LR
H[("Hub")]
subgraph GS["Phase generate-sources"]
F["Metadaten holen<br/>CdmsMetadataFetcher"]
C[["YAML-Cache<br/>system, folders,<br/>models, enumerations"]]
P["POM pflegen"]
end
subgraph CO["Phase compile"]
G["Generator<br/>(Annotation-Processor)"]
J["javac"]
E["Entities anreichern<br/>(Hibernate)"]
end
subgraph PK["Phase package"]
B["Spring-Boot-JAR<br/>oder Native Image"]
end
H -- "REST + Token" --> F
F --> C
F --> P
C --> G
G -- "target/generated-sources" --> J
J --> E --> B
Den ganzen Ablauf erbst du vom Parent cdms-app-parent. In deiner eigenen pom.xml steht davon nichts. Das Profil wird aktiv, sobald es src/main/java gibt.
-
1Build→Keycloakholt ein Token (Client-Credentials) mit Client-ID und Secret aus der Konfiguration
-
2Build→Hubliest das System und fragt Ordner, Modelle, Felder, Hooks, Endpunkte und Enumerationen seitenweise ab
-
3Buildsetzt alles zusammen und schreibt den YAML-Cache:
system.yaml,folders.yaml,models.yaml,enumerations.yaml -
4Buildpflegt die
pom.xmlnach den Einstellungen des Systems, siehe Projektrahmen -
5Generatorliest den Cache und erzeugt die Klassen aller Modelle nach
target/generated-sources/annotations/Der Generator liest nur den Cache, keine Annotationen im Code. Das Java-Paket einer Klasse ist das Basispaket plus der Ordnerpfad aus dem Hub. -
6Buildkompiliert den generierten und den eigenen Code zusammen, reichert die Entities anErgebnis: Fertige Klassen in
target/classes, bereit zum Verpacken
Online, offline, überspringen
Wann: der Normalfall
Der Build holt ein Token und liest die Metadaten frisch aus dem Hub. Danach steht der aktuelle Stand im Cache.
Ergebnis: Code entspricht dem Modell im Hub
Wann: CODEGEN_OFFLINE=true
Kein Token, kein Aufruf beim Hub. Der Build verlangt, dass alle vier Cache-Dateien da sind, und generiert aus ihnen. Fehlt eine, bricht er ab mit dem Hinweis, einmal mit CODEGEN_OFFLINE=false zu bauen. Die POM wird trotzdem gepflegt.
Ergebnis: Code entspricht dem Stand im Cache
Wann: -Dcdms.generator.fetch.skip=true
Der ganze Schritt „Metadaten holen“ entfällt, also auch die POM-Pflege. Der Generator braucht dann nur noch models.yaml im Cache. So baut ein frischer Projektrahmen beim ersten Mal, ohne Secret.
Ergebnis: Code entspricht dem Stand im Cache
Wann: du willst den Cache ohne Build erzeugen
GET /api/rest/cms/system/{id}/export liefert ein ZIP mit den vier YAML-Dateien. Entpackt ins Cache-Verzeichnis, baust du danach offline. Auch der Projektrahmen enthält diesen Stand schon unter .cache/.
Die Konfiguration
Der Build braucht ein paar Angaben. Er sucht sie in dieser Reihenfolge und nimmt den ersten Treffer:
-
BuildUmgebungsvariable
CODEGEN_CLIENT_SECRET -
Buildabgeleitete Umgebungsvariable
CDMS_GENERATOR_CLIENT_SECRET -
BuildJVM-Property
-Dcdms.generator.clientSecret=… -
BuildDatei im Projekt
clientSecret=…incdms-generator.properties - sonst der Standardwert
| Einstellung | Umgebungsvariable | Standard |
|---|---|---|
Issuer des Generator-Tokens, mit /realms/<realm> | CODEGEN_KEYCLOAK_ISSUER | – (Pflicht) |
| Client-ID / Secret | CODEGEN_CLIENT_ID, CODEGEN_CLIENT_SECRET | – (Pflicht) |
| Hub-Adresse | CODEGEN_CDMS_URL | im Projektrahmen gesetzt |
| System-ID | CODEGEN_SYSTEM_ID | – (Pflicht) |
| Cache-Verzeichnis | CODEGEN_CACHE_DIR | target/cache (im Projektrahmen .cache) |
| offline | CODEGEN_OFFLINE | false |
| Basispaket | CODEGEN_BASE_PACKAGE | com.codamai.cdms |
| POM pflegen | CODEGEN_POM_GENERATE | true |
Die Pflichtangaben braucht nur der Online-Weg. Offline reichen Cache-Verzeichnis und Basispaket.
Was pro Modell entsteht
Für ein normales Modell entstehen 15 Klassen, für ein abstraktes Modell 10. Der Name ist immer der Modellname plus eine Endung. Für ein Modell Order:
OrderApi– der REST-ControllerOrderCreatePayloadOrderUpdatePayloadOrderPayload2DtoMapper
OrderSystem– Ablauf von Lesen und SchreibenOrderDtoOrderMetaService– die MetadatenOrderDto2EntityMapperOrderMapperService
OrderAuthorizationLayer– prüft RollenOrder{Feld}Filter– je Zugriffsfilter einer
OrderEntityOrderDatabaseOrderTupleMapperServiceOrderEntity2DtoMapperOrderMap2EntityMapper
| Fall | Klassen |
|---|---|
| normales Modell | 15, dazu eine je Zugriffsfilter |
| Singleton | 15. Api und System sind die Singleton-Varianten ohne id im Pfad |
| abstraktes Modell | 10. Es fehlen System, MapperService, AuthorizationLayer, Database, TupleMapperService, weil es keine eigenen Objekte hat. Seine Api sucht über alle Untertypen |
| Enumeration | ein Java-enum mit den Werten |
Wie die Gestalten Payload, DTO, Entity und Meta zusammenspielen, zeigt Ein Modell, vier Gestalten.
Was einmal pro Build und einmal überhaupt entsteht
| Datei | Ort | Wann |
|---|---|---|
| Klassen je Modell und Enumeration | target/generated-sources/ | bei jedem Build neu |
RoleRegistryService, EntityRegistryService, JpaNativeHints, JacksonNativeHints | target/generated-sources/ | einmal pro Build, über alle Modelle |
Start.java, OpenApiConfiguration, NativeHintsConfiguration | src/main/java/ | nur wenn die Datei fehlt, danach nie wieder |
pom.xml, Bereich zwischen den cdms-managed-Markern | Projektwurzel | bei jedem Online- oder Offline-Build nachgezogen |
RoleRegistryService kennt alle Rollen und Attribute aller Modelle und stellt sie CIAS als Deklaration bereit, siehe Rollen deklarieren. EntityRegistryService kennt alle Entities. Die beiden …NativeHints sagen dem Native-Image-Compiler, welche Klassen er zur Laufzeit per Reflection braucht.