Worum es geht
In einem CDMS-Projekt gibt es zwei Sorten Code. Den generierten Code erzeugt der Build aus dem Modell im Hub, siehe Codegenerierung im Build. Den eigenen Code schreibst du: Fachlogik, die sich nicht modellieren lässt.
Die beiden liegen an verschiedenen Orten und haben verschiedene Besitzer. Wer das trennt, kann jederzeit neu generieren, ohne etwas zu verlieren.
Wer wo wohnt
flowchart TB
subgraph T["target/generated-sources/ · generiert, bei jedem Build neu"]
A["OrderApi"] --> S["OrderSystem"] --> D["OrderDatabase"]
S --> AL["OrderAuthorizationLayer"]
M["OrderEntity, OrderDto, Payloads, Mapper, MetaService"]
end
subgraph SRC["src/main/java/ · deins, im Repository"]
H["OrderHook<br/>(Hook)"]
F["OrderFilter<br/>(Filter)"]
C["eigener Controller<br/>oder Service"]
ST["Start.java<br/>(einmal erzeugt)"]
end
subgraph R["src/main/resources/ · deins"]
Y["application.yaml"]
end
H -. "wird aufgerufen von" .-> S
F -. "wird gefragt von" .-> S
C -- "ruft auf" --> S
| Ort | Besitzer | ins Repository? | was passiert beim Build |
|---|---|---|---|
target/generated-sources/annotations/ | Generator | nein | wird neu erzeugt |
src/main/java/ | du | ja | wird kompiliert, nie überschrieben |
src/main/resources/ | du | ja | wird mitgepackt, nie überschrieben |
pom.xml zwischen den cdms-managed-Markern | Hub | ja | wird nachgezogen, siehe Projektrahmen |
Wohin mit meiner Logik?
| Was willst du tun? | Erweiterungspunkt |
|---|---|
| ein Feld, eine Regel, einen Endpunkt, eine Rolle ergänzen | Modell im Hub ändern und neu bauen, kein Code |
| vor oder nach dem Speichern etwas prüfen, berechnen, auslösen | Hook auf das Modell |
| Zeilen je nach Benutzerattribut ausblenden | Zugriffsfilter im Hub, bei Bedarf eigene Filterklasse |
| jede Suche auf ein Modell nach eigener Logik einschränken | eigener Filter |
| einen fachlichen Endpunkt anbieten, den CDMS nicht kennt | eigener Controller, der den System-Layer aufruft |
| Verhalten von CDMS einstellen | Konfiguration in application.yaml oder Umgebung |
| eine generierte Klasse ändern | geht nicht, beim nächsten Build ist die Änderung weg |
Hooks
Ein Hook ist eine Klasse, die CDMS bei jeder Operation auf ein Modell aufruft. Du schreibst sie für die Entity des Modells:
@Service
@Order(100)
public class OrderHook implements HookServiceInterface<OrderEntity> {
@Override
public void beforeDatabaseChange(OrderEntity item, CmsMethods method)
throws AbstractCodamaiException {
if (method == CmsMethods.CREATE && item.getOrderNr() == null) {
item.setOrderNr(nextNumber());
}
}
@Override
public void afterDatabaseChange(OrderEntity item, CmsMethods method)
throws AbstractCodamaiException {
// e.g. publish an event
}
@Override
public void fieldBeforeDatabaseChange(OrderEntity item, String field, CmsMethods method) { }
@Override
public void fieldOnDatabaseRead(OrderEntity item, String field, CmsMethods method) { }
}
-
1Entwicklerschreibt eine Klasse, die
HookServiceInterface<OrderEntity>implementiert -
2Entwicklermacht sie mit
@Servicezur Spring-Bean und gibt ihr ein@Order@Orderlegt die Reihenfolge fest, wenn es mehrere Hooks auf dasselbe Modell gibt. Kleinere Zahl zuerst. Setze es direkt an die Klasse. -
3CDMSsucht alle Hook-Beans für
OrderEntityund sortiert sie nach@Order -
4Hookwird bei jeder Operation aufgerufen, mit der Operation als
CmsMethodsCREATE,READ,UPDATE,PATCH,DELETE,ROLLBACK
Die Arbeit machen beforeDatabaseChange (vor dem Schreiben, in der Transaktion) und afterDatabaseChange (nach dem Schreiben, beim Lesen nach dem Laden). Wirft ein Hook eine AbstractCodamaiException, bricht die ganze Operation ab und die Transaktion wird zurückgerollt. Wann genau welcher Hook läuft, steht unter Hooks: Arten und Zeitpunkte und Reihenfolge der Hooks.
Eigene Filter
Ein Filter schränkt jede Suche auf ein Modell ein, bevor sie die Datenbank erreicht. Er arbeitet mit dem DTO des Modells. Der häufigste Fall ist ein Attributfilter: Jeder Benutzer sieht nur die Zeilen, deren Feld zu einem Attribut in seinem Token passt. Dafür gibt es die fertige Basisklasse AbstractAttributeFilter:
@Service
public class OrderFilter extends AbstractAttributeFilter<OrderDto> {
@Getter
private final String dataAttribute = "companyId"; // field on the model
@Getter
private final String profileAttribute = "company"; // attribute in the user's token
}
| Wert des Attributs im Token | Bedingung in der Suche |
|---|---|
ein Wert, z. B. c-17 | companyId = 'c-17' |
| mehrere Werte | companyId IN (…) |
* | kein Filter, alle Zeilen |
| fehlt oder leer | Anfrage wird abgelehnt |
Brauchst du eine andere Logik, implementierst du CdmsFilterInterface<OrderDto> direkt und lieferst in get() einen eigenen Suchfilter. Legst du im Hub einen Zugriffsfilter an, erzeugt der Generator die Filterklasse selbst. Mehr unter Attributfilter und Eigene Datenfilter.
Eigene Controller und Services
Für fachliche Abläufe, die über Anlegen, Lesen, Ändern und Löschen hinausgehen, schreibst du einen normalen Spring-@RestController. Er ruft die generierten System-Layer auf. So laufen auch seine Zugriffe durch Rechteprüfung, Validierung und Hooks.
Den generierten OrderApi erweiterst oder ersetzt du dagegen nicht. Die generierten Beans haben feste Namen. Eine zweite Bean mit demselben Namen verhindert den Start der Anwendung.
Konfiguration
Was sich an CDMS einstellen lässt, steht in application.yaml oder kommt aus der Umgebung:
| Property | wofür |
|---|---|
codamai.cdms.api.createReadMode | wie streng das Zurücklesen nach dem Anlegen ist (STRICT, LENIENT) |
codamai.cdms.cias.reader-roles | Rollen, die die Rollendeklaration lesen dürfen |
codamai.cdms.persistence.file.basePath | Ablageort für Dateien |
codamai.debug | Stacktrace in Fehlerantworten (im Betrieb false) |
CODAMAI_PERSISTENCE_TENANT_MODE | SINGLE oder MULTI |
CODAMAI_PERSISTENCE_DATABASE_* | Treiber, URL, Benutzer, Passwort der Datenbank |