Data Engine: Unterschied zwischen den Versionen

Aus Dokument
Zur Navigation springen Zur Suche springen
Die Seite wurde neu angelegt: „ == Übersicht == Dieser Artikel dokumentiert ein Rust-Datenmodell, das die zentralen Entity-Konzepte von Drupal (Node, User, Taxonomy Term, Media, Paragraph) als <code>struct</code>/<code>enum</code>-Typen abbildet. Das Modell enthält bewusst '''keine Logik''' (keine <code>impl</code>-Methoden) – es dient als reine Datenrepräsentation. Eine (De-)Serialisierung (z. B. mit [https://serde.rs/ serde] für Drupal-JSON:API-Antworten) ist bewusst noch nich…“
 
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
= Rust-Datenmodell für Drupal-Entities =


== Übersicht ==
== Übersicht ==
Zeile 4: Zeile 5:
Dieser Artikel dokumentiert ein Rust-Datenmodell, das die zentralen
Dieser Artikel dokumentiert ein Rust-Datenmodell, das die zentralen
Entity-Konzepte von Drupal (Node, User, Taxonomy Term, Media, Paragraph)
Entity-Konzepte von Drupal (Node, User, Taxonomy Term, Media, Paragraph)
als <code>struct</code>/<code>enum</code>-Typen abbildet. Das Modell enthält bewusst '''keine
als <pre>struct</pre>/<pre>enum</pre>-Typen abbildet. Das Modell enthält bewusst '''keine
Logik''' (keine <code>impl</code>-Methoden) – es dient als reine Datenrepräsentation.
Logik''' (keine <pre>impl</pre>-Methoden) – es dient als reine Datenrepräsentation.
Eine (De-)Serialisierung (z. B. mit [https://serde.rs/ serde] für
Eine (De-)Serialisierung (z. B. mit [https://serde.rs/ serde] für
Drupal-JSON:API-Antworten) ist bewusst noch nicht Teil dieses Modells
Drupal-JSON:API-Antworten) ist bewusst noch nicht Teil dieses Modells
Zeile 13: Zeile 14:


Drupal modelliert Inhalte über ein generisches Entity-System:
Drupal modelliert Inhalte über ein generisches Entity-System:
* '''Entity-Typen''' (<code>node</code>, <code>user</code>, <code>taxonomy_term</code>, <code>media</code>, …) definieren die grundlegende Art eines Objekts.
* '''Entity-Typen''' (<pre>node</pre>, <pre>user</pre>, <pre>taxonomy_term</pre>, <pre>media</pre>, …) definieren die grundlegende Art eines Objekts.
* '''Bundles''' (bei Nodes „Content Types" genannt, z. B. <code>article</code>, <code>page</code>) spezialisieren einen Entity-Typ.
* '''Bundles''' (bei Nodes „Content Types" genannt, z. B. <pre>article</pre>, <pre>page</pre>) spezialisieren einen Entity-Typ.
* '''Felder''' (<code>field_*</code>) sind pro Bundle konfigurierbar, typisiert und können einfach- oder mehrwertig (Kardinalität) sein.
* '''Felder''' (<pre>field_*</pre>) sind pro Bundle konfigurierbar, typisiert und können einfach- oder mehrwertig (Kardinalität) sein.


Um mit einem Drupal-Backend aus Rust heraus zu arbeiten, braucht man
Um mit einem Drupal-Backend aus Rust heraus zu arbeiten, braucht man
Zeile 23: Zeile 24:
== Grundtypen ==
== Grundtypen ==


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntityType {
pub enum EntityType {
Zeile 44: Zeile 45:


pub type LangCode = String;
pub type LangCode = String;
</syntaxhighlight>
</pre>


<code>EntityType</code> bildet die von Drupal vordefinierten Entity-Typen ab; der
<pre>EntityType</pre> bildet die von Drupal vordefinierten Entity-Typen ab; der
<code>Custom</code>-Zweig deckt eigene, per Modul definierte Entity-Typen ab, ohne
<pre>Custom</pre>-Zweig deckt eigene, per Modul definierte Entity-Typen ab, ohne
das Enum ändern zu müssen.
das Enum ändern zu müssen.


== Feldwerte: <code>FieldValue</code> ==
== Feldwerte: <pre>FieldValue</pre> ==


Drupal-Felder sind stark typisiert (Text, Zahl, Datum, Referenz, Bild, …).
Drupal-Felder sind stark typisiert (Text, Zahl, Datum, Referenz, Bild, …).
Dies wird über ein Enum mit Payload pro Variante abgebildet:
Dies wird über ein Enum mit Payload pro Variante abgebildet:


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone)]
#[derive(Debug, Clone)]
pub enum FieldValue {
pub enum FieldValue {
Zeile 78: Zeile 79:


pub type FieldMap = HashMap<String, FieldValue>;
pub type FieldMap = HashMap<String, FieldValue>;
</syntaxhighlight>
</pre>


* <code>List(Vec<FieldValue>)</code> bildet mehrwertige Felder ab (Kardinalität > 1).
* <pre>List(Vec<FieldValue>)</pre> bildet mehrwertige Felder ab (Kardinalität > 1).
* <code>FieldMap</code> ist die generische Ablage für alle <code>field_*</code>-Werte eines Bundles, adressiert über den Feldmaschinennamen (z. B. <code>field_tags</code>).
* <pre>FieldMap</pre> ist die generische Ablage für alle <pre>field_*</pre>-Werte eines Bundles, adressiert über den Feldmaschinennamen (z. B. <pre>field_tags</pre>).


== Entity-Typen im Detail ==
== Entity-Typen im Detail ==
Zeile 87: Zeile 88:
=== Gemeinsame Basis ===
=== Gemeinsame Basis ===


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone)]
#[derive(Debug, Clone)]
pub struct EntityBase {
pub struct EntityBase {
Zeile 98: Zeile 99:
     pub changed: DateTime<Utc>,
     pub changed: DateTime<Utc>,
}
}
</syntaxhighlight>
</pre>


<code>EntityBase</code> fasst Eigenschaften zusammen, die praktisch jede
<pre>EntityBase</pre> fasst Eigenschaften zusammen, die praktisch jede
Content-Entity in Drupal besitzt, und wird per Komposition in konkrete
Content-Entity in Drupal besitzt, und wird per Komposition in konkrete
Entity-Structs eingebettet (siehe <code>Node</code>, <code>Media</code>). Beim späteren
Entity-Structs eingebettet (siehe <pre>Node</pre>, <pre>Media</pre>). Beim späteren
Ergänzen von serde bietet sich dafür <code>#[serde(flatten)]</code> an, damit die
Ergänzen von serde bietet sich dafür <pre>#[serde(flatten)]</pre> an, damit die
Felder von <code>EntityBase</code> beim (De-)Serialisieren auf derselben Ebene
Felder von <pre>EntityBase</pre> beim (De-)Serialisieren auf derselben Ebene
wie die restlichen Felder erscheinen.
wie die restlichen Felder erscheinen.


=== Node ===
=== Node ===


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone)]
#[derive(Debug, Clone)]
pub struct Node {
pub struct Node {
Zeile 118: Zeile 119:
     pub fields: FieldMap,
     pub fields: FieldMap,
}
}
</syntaxhighlight>
</pre>


=== User ===
=== User ===


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone)]
#[derive(Debug, Clone)]
pub struct User {
pub struct User {
Zeile 134: Zeile 135:
     pub fields: FieldMap,
     pub fields: FieldMap,
}
}
</syntaxhighlight>
</pre>


<code>User</code> nutzt bewusst kein <code>EntityBase</code>, da User-Entities in Drupal kein
<pre>User</pre> nutzt bewusst kein <pre>EntityBase</pre>, da User-Entities in Drupal kein
<code>bundle</code>/<code>langcode</code> im klassischen Sinn besitzen und stattdessen
<pre>bundle</pre>/<pre>langcode</pre> im klassischen Sinn besitzen und stattdessen
<code>roles</code> und einen einfachen <code>status: bool</code> (aktiv/blockiert) haben.
<pre>roles</pre> und einen einfachen <pre>status: bool</pre> (aktiv/blockiert) haben.


=== TaxonomyTerm ===
=== TaxonomyTerm ===


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone)]
#[derive(Debug, Clone)]
pub struct TaxonomyTerm {
pub struct TaxonomyTerm {
Zeile 154: Zeile 155:
     pub fields: FieldMap,
     pub fields: FieldMap,
}
}
</syntaxhighlight>
</pre>


Bildet Taxonomiebegriffe inkl. hierarchischer Eltern-Referenz
Bildet Taxonomiebegriffe inkl. hierarchischer Eltern-Referenz
(<code>parent_id</code>) und Sortiergewicht (<code>weight</code>) ab.
(<pre>parent_id</pre>) und Sortiergewicht (<pre>weight</pre>) ab.


=== Media & Paragraph ===
=== Media & Paragraph ===


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone)]
#[derive(Debug, Clone)]
pub struct Media {
pub struct Media {
Zeile 176: Zeile 177:
     pub fields: FieldMap,
     pub fields: FieldMap,
}
}
</syntaxhighlight>
</pre>


<code>Paragraph</code> ist bewusst schlank gehalten, da Paragraphs in Drupal keine
<pre>Paragraph</pre> ist bewusst schlank gehalten, da Paragraphs in Drupal keine
eigenständigen, direkt aufrufbaren Entities mit URL/Alias sind, sondern
eigenständigen, direkt aufrufbaren Entities mit URL/Alias sind, sondern
stets über eine Host-Entity referenziert werden.
stets über eine Host-Entity referenziert werden.
Zeile 188: Zeile 189:
zu spiegeln oder Formulare dynamisch zu generieren:
zu spiegeln oder Formulare dynamisch zu generieren:


<syntaxhighlight lang="rust">
<pre>
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cardinality {
pub enum Cardinality {
Zeile 211: Zeile 212:
     pub fields: Vec<FieldDefinition>,
     pub fields: Vec<FieldDefinition>,
}
}
</syntaxhighlight>
</pre>


== Design-Entscheidungen ==
== Design-Entscheidungen ==
Zeile 219: Zeile 220:
! Entscheidung !! Begründung
! Entscheidung !! Begründung
|-
|-
| <code>FieldValue</code> als Enum mit Payload pro Variante || Bildet Drupals starke Feldtypisierung typsicher ab, statt alles als <code>String</code> zu behandeln.
| <pre>FieldValue</pre> als Enum mit Payload pro Variante || Bildet Drupals starke Feldtypisierung typsicher ab, statt alles als <pre>String</pre> zu behandeln.
|-
|-
| <code>FieldMap = HashMap<String, FieldValue></code> || Bundles sind zur Compile-Zeit nicht bekannt; generische Map bleibt flexibel für beliebige <code>field_*</code>-Namen.
| <pre>FieldMap = HashMap<String, FieldValue></pre> || Bundles sind zur Compile-Zeit nicht bekannt; generische Map bleibt flexibel für beliebige <pre>field_*</pre>-Namen.
|-
|-
| <code>EntityBase</code> als Kompositionsfeld || Vermeidet Duplikation gemeinsamer Properties zwischen Node/Media, ohne Vererbung (die es in Rust nicht gibt).
| <pre>EntityBase</pre> als Kompositionsfeld || Vermeidet Duplikation gemeinsamer Properties zwischen Node/Media, ohne Vererbung (die es in Rust nicht gibt).
|-
|-
| Kein <code>impl</code>-Block || Modell bleibt reine Datenschicht; Geschäftslogik (Validierung, API-Calls) gehört in separate Module.
| Kein <pre>impl</pre>-Block || Modell bleibt reine Datenschicht; Geschäftslogik (Validierung, API-Calls) gehört in separate Module.
|-
|-
| <code>EntityType::Custom(String)</code> || Deckt durch Contrib-/Custom-Module definierte Entity-Typen ab, ohne das Enum erweitern zu müssen.
| <pre>EntityType::Custom(String)</pre> || Deckt durch Contrib-/Custom-Module definierte Entity-Typen ab, ohne das Enum erweitern zu müssen.
|}
|}


== Offene Erweiterungspunkte ==
== Offene Erweiterungspunkte ==


* Weitere <code>FieldValue</code>-Varianten je nach genutzten Feldtypen (z. B. <code>Geolocation</code>, <code>Address</code>, <code>File</code> losgelöst von <code>Image</code>).
* Weitere <pre>FieldValue</pre>-Varianten je nach genutzten Feldtypen (z. B. <pre>Geolocation</pre>, <pre>Address</pre>, <pre>File</pre> losgelöst von <pre>Image</pre>).
* Übersetzungen (<code>translations: HashMap<LangCode, FieldMap></code>), falls Drupal-Mehrsprachigkeit (Content Translation) abgebildet werden soll.
* Übersetzungen (<pre>translations: HashMap<LangCode, FieldMap></pre>), falls Drupal-Mehrsprachigkeit (Content Translation) abgebildet werden soll.
* Konkretes Mapping auf die JSON:API-Hüllstruktur (<code>data</code>, <code>attributes</code>, <code>relationships</code>, <code>included</code>), falls direkt gegen die Drupal-JSON:API deserialisiert werden soll, statt ein eigenes flaches Modell zu pflegen.
* Konkretes Mapping auf die JSON:API-Hüllstruktur (<pre>data</pre>, <pre>attributes</pre>, <pre>relationships</pre>, <pre>included</pre>), falls direkt gegen die Drupal-JSON:API deserialisiert werden soll, statt ein eigenes flaches Modell zu pflegen.


== Verwendete Crates ==
== Verwendete Crates ==


* [https://crates.io/crates/chrono chrono] – Zeitstempel (<code>created</code>, <code>changed</code>)
* [https://crates.io/crates/chrono chrono] – Zeitstempel (<pre>created</pre>, <pre>changed</pre>)
* [https://crates.io/crates/uuid uuid] – Entity-UUIDs
* [https://crates.io/crates/uuid uuid] – Entity-UUIDs



Version vom 12. August 2026, 01:17 Uhr

Rust-Datenmodell für Drupal-Entities

Übersicht

Dieser Artikel dokumentiert ein Rust-Datenmodell, das die zentralen Entity-Konzepte von Drupal (Node, User, Taxonomy Term, Media, Paragraph)

als

struct

/

enum

-Typen abbildet. Das Modell enthält bewusst keine Logik (keine

impl

-Methoden) – es dient als reine Datenrepräsentation.

Eine (De-)Serialisierung (z. B. mit serde für Drupal-JSON:API-Antworten) ist bewusst noch nicht Teil dieses Modells und folgt in einem späteren Schritt.

Motivation

Drupal modelliert Inhalte über ein generisches Entity-System:

  • Entity-Typen (
    node
    ,
    user
    ,
    taxonomy_term
    ,
    media
    , …) definieren die grundlegende Art eines Objekts.
  • Bundles (bei Nodes „Content Types" genannt, z. B.
    article
    ,
    page
    ) spezialisieren einen Entity-Typ.
  • Felder (
    field_*
    ) sind pro Bundle konfigurierbar, typisiert und können einfach- oder mehrwertig (Kardinalität) sein.

Um mit einem Drupal-Backend aus Rust heraus zu arbeiten, braucht man typisierte Gegenstücke zu diesen Konzepten. Das hier beschriebene Modell bildet diese Struktur 1:1 in Rust ab.

Grundtypen

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntityType {
    Node,
    User,
    TaxonomyTerm,
    Media,
    Paragraph,
    File,
    Menu,
    Block,
    Custom(String),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PublishStatus {
    Published,
    Unpublished,
}

pub type LangCode = String;
EntityType

bildet die von Drupal vordefinierten Entity-Typen ab; der

Custom

-Zweig deckt eigene, per Modul definierte Entity-Typen ab, ohne

das Enum ändern zu müssen.

Feldwerte:
FieldValue

Drupal-Felder sind stark typisiert (Text, Zahl, Datum, Referenz, Bild, …). Dies wird über ein Enum mit Payload pro Variante abgebildet:

#[derive(Debug, Clone)]
pub enum FieldValue {
    Text(String),
    TextLong { value: String, format: Option<String> },
    Integer(i64),
    Decimal(f64),
    Boolean(bool),
    DateTime(DateTime<Utc>),
    Email(String),
    Link { uri: String, title: Option<String> },
    EntityReference { target_id: u64, target_type: EntityType },
    Image {
        target_id: u64,
        alt: Option<String>,
        title: Option<String>,
        width: Option<u32>,
        height: Option<u32>,
    },
    List(Vec<FieldValue>),
}

pub type FieldMap = HashMap<String, FieldValue>;
  • List(Vec<FieldValue>)
    bildet mehrwertige Felder ab (Kardinalität > 1).
  • FieldMap
    ist die generische Ablage für alle
    field_*
    -Werte eines Bundles, adressiert über den Feldmaschinennamen (z. B.
    field_tags
    ).

Entity-Typen im Detail

Gemeinsame Basis

#[derive(Debug, Clone)]
pub struct EntityBase {
    pub id: u64,
    pub uuid: Uuid,
    pub entity_type: EntityType,
    pub bundle: String,
    pub langcode: LangCode,
    pub created: DateTime<Utc>,
    pub changed: DateTime<Utc>,
}
EntityBase

fasst Eigenschaften zusammen, die praktisch jede

Content-Entity in Drupal besitzt, und wird per Komposition in konkrete

Entity-Structs eingebettet (siehe

Node

,

Media

). Beim späteren Ergänzen von serde bietet sich dafür

#[serde(flatten)]

an, damit die Felder von

EntityBase

beim (De-)Serialisieren auf derselben Ebene

wie die restlichen Felder erscheinen.

Node

#[derive(Debug, Clone)]
pub struct Node {
    pub base: EntityBase,
    pub title: String,
    pub status: PublishStatus,
    pub author_id: u64,
    pub fields: FieldMap,
}

User

#[derive(Debug, Clone)]
pub struct User {
    pub id: u64,
    pub uuid: Uuid,
    pub name: String,
    pub mail: String,
    pub status: bool,
    pub roles: Vec<String>,
    pub created: DateTime<Utc>,
    pub fields: FieldMap,
}
User

nutzt bewusst kein

EntityBase

, da User-Entities in Drupal kein

bundle

/

langcode

im klassischen Sinn besitzen und stattdessen

roles

und einen einfachen

status: bool

(aktiv/blockiert) haben.

TaxonomyTerm

#[derive(Debug, Clone)]
pub struct TaxonomyTerm {
    pub id: u64,
    pub uuid: Uuid,
    pub vocabulary: String,
    pub name: String,
    pub description: Option<String>,
    pub parent_id: Option<u64>,
    pub weight: i32,
    pub fields: FieldMap,
}

Bildet Taxonomiebegriffe inkl. hierarchischer Eltern-Referenz

(

parent_id

) und Sortiergewicht (

weight

) ab.

Media & Paragraph

#[derive(Debug, Clone)]
pub struct Media {
    pub base: EntityBase,
    pub name: String,
    pub fields: FieldMap,
}

#[derive(Debug, Clone)]
pub struct Paragraph {
    pub id: u64,
    pub uuid: Uuid,
    pub bundle: String,
    pub fields: FieldMap,
}
Paragraph

ist bewusst schlank gehalten, da Paragraphs in Drupal keine

eigenständigen, direkt aufrufbaren Entities mit URL/Alias sind, sondern stets über eine Host-Entity referenziert werden.

Schema-Introspektion (optional)

Für Anwendungsfälle, in denen Feld- und Bundle-Definitionen selbst (nicht nur Werte) abgebildet werden müssen – etwa um ein Drupal-Schema zu spiegeln oder Formulare dynamisch zu generieren:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cardinality {
    Single,
    Multiple,
    Unlimited,
}

#[derive(Debug, Clone)]
pub struct FieldDefinition {
    pub field_name: String,
    pub field_type: String,
    pub cardinality: Cardinality,
    pub required: bool,
}

#[derive(Debug, Clone)]
pub struct BundleDefinition {
    pub entity_type: EntityType,
    pub bundle: String,
    pub label: String,
    pub fields: Vec<FieldDefinition>,
}

Design-Entscheidungen

Entscheidung Begründung
FieldValue
als Enum mit Payload pro Variante
Bildet Drupals starke Feldtypisierung typsicher ab, statt alles als
String
zu behandeln.
FieldMap = HashMap<String, FieldValue>
Bundles sind zur Compile-Zeit nicht bekannt; generische Map bleibt flexibel für beliebige
field_*
-Namen.
EntityBase
als Kompositionsfeld
Vermeidet Duplikation gemeinsamer Properties zwischen Node/Media, ohne Vererbung (die es in Rust nicht gibt).
Kein
impl
-Block
Modell bleibt reine Datenschicht; Geschäftslogik (Validierung, API-Calls) gehört in separate Module.
EntityType::Custom(String)
Deckt durch Contrib-/Custom-Module definierte Entity-Typen ab, ohne das Enum erweitern zu müssen.

Offene Erweiterungspunkte

  • Weitere
    FieldValue
    -Varianten je nach genutzten Feldtypen (z. B.
    Geolocation
    ,
    Address
    ,
    File
    losgelöst von
    Image
    ).
  • Übersetzungen (
    translations: HashMap<LangCode, FieldMap>
    ), falls Drupal-Mehrsprachigkeit (Content Translation) abgebildet werden soll.
  • Konkretes Mapping auf die JSON:API-Hüllstruktur (
    data
    ,
    attributes
    ,
    relationships
    ,
    included
    ), falls direkt gegen die Drupal-JSON:API deserialisiert werden soll, statt ein eigenes flaches Modell zu pflegen.

Verwendete Crates

  • chrono – Zeitstempel (
    created
    ,
    changed
    )
  • uuid – Entity-UUIDs

Hinweis: serde (für die (De-)Serialisierung) ist bewusst noch nicht Teil dieses Modells und wird in einem späteren Schritt ergänzt.

Siehe auch