Das Modell Entity Attribute Value als abstrakter Datentyp

Jedes Programm benötigt wahlfreien Zugriff, dafür gibt es Variablen und Datentypen, für den Transport hingegen wird serialisiert

Definition: Ein abstrakter Datentyp beschreibt nur die Art und Weise, wie der wahlfreie Zugriff erfolgt, währendem Variablen primitiver Datentypen direkt adressierbar sind, Beispiel:

# Integer, primitiver Datentyp
my $number = 123;

# direkt adressierbar
print $number * 2;

# abstrakter Datentyp
my $addrs = [
    {
        name  => 'Puffbohne',
        vname => 'Horst',
        ort   => 'Erfurt',
        plz   => '99099'
    },
    {
        name   => 'Fettgusche',
        vname  => 'Ernst',
        ort    => 'Gera',
        plz    => '07552',
        suburb => 'Langer Berg'
    }, {}, {}

];

# wahlfreier Zugriff über den Array-Index
# und Hash-Key
print $addrs->[0]{ort};

Das letzte Beispiel zeigt eine Datenstruktur, auf die das Muster Entity/Attribute/Value im Grunde genommen schon passt, mit der Besonderheit, dass die Entities anonym sind, also lediglich über einen Array-Index adressierbar sind. Ändern wir die Struktur wie folgt:

# Hashreferenz auf eine Datenstruktur
my $addrs = {
    Erfurter => {
        name  => 'Puffbohne',
        vname => 'Horst',
        ort   => 'Erfurt',
        plz   => '99099'
    },
    Geraer => {
        name   => 'Fettgusche',
        vname  => 'Ernst',
        ort    => 'Gera',
        plz    => '07552',
        suburb => 'Langer Berg'
    }, {}, {}

};
print $addrs->{Erfurter}{ort};

So hat jeder Eintrag einen eigenen Namen. Für den Transport, beispielsweise via HTTP oder Speichern in einer Datei gibt es mehrere Möglichkeiten:

# ini-Datei
[Erfurter]
name  = Puffbohne
vname = Horst
ort   = Erfurt
plz   = 99099

[Geraer]
name   = Fettgusche
vname  = Ernst
ort    = Gera
plz    = 07552
suburb = Langer Berg

# xml
<addrs>
    <Erfurter name='Puffbohne' vname='Horst' ort='Erfurt' plz='99099' />
    <Geraer name='Fettgusche' vname='Ernst' ort='Gera' plz='07552' suburb='Langer Berg' />
</addrs>

Und selbstverständlich kann die XML-Sequenz auch anders aufgebaut sein oder die EAV-Struktur wird gänzlich anders serialisiert. Auch hierbei haben wir wieder einen schlüssigen Beweis dafür vorliegen, dass eine Datenstruktur nicht etwa infolge eines Datei-Aufbaus bestimmt wird, sondern durch die Art und Weise des wahlfreien Zugriff. Das bedeutet im Umkehrschluss, dass nicht der Serializer über den Aufbau einer Datenstruktur entscheidet sondern der Entwickler. In der Regel ist eine Adressierung direkt über den Entity-Namen zweckmäßiger als über einen namenlosen Index.

Ein praktisches Beispiel

Aufgabe: Für einen HTTP-Client zum Dateiupload soll es die Möglichkeit zur Auswahl des Upload-Verzeichnisses geben. Des Weiteren soll der Anwender sofort nach der Auswahl seiner lokalen Dateien sehen, ob sie bereits auf dem Server liegen. Programmiertechnisch gestaltet es sich so, dass der Name des Uploadverzeichnisses(updir) vom lokalen Dateinamen(tabfile) getrennt vorliegt. Ergo bietet sich eine Datenstruktur wie folgt an für die Prüfung:

  if( DIRMAP[updir][tabfile] ){
    // Datei ist Online
  }
  else{
    // Datei ist hochzuladen
  }

Bevor wir einen solchen Komfort nutzen können, wäre noch zu klären, wie das Objekt DIRMAP mit Daten betankt wird und wie diese Daten zur Übertragung in einer AJAX-Response serialisiert werden können: Am Einfachsten per JSON was auch offensichtlich am Zweckmäßigsten erscheint. Aber ist das auch wirklich so? Betrachten wir den zu übertragenden Informationsgehalt, werden wir feststellen, dass es ja genügt, einfach eine Liste/Array mit voll qualifizierten Dateinamen zu übertragen. In diesem Fall wird das JavaScript-Objekt erst nach dem Empfang der Response zusammengebaut.

Eine andere Möglichkeit zum Verpacken der Daten wäre der gute alte HTTP QUERY_STRING wie folgt:

dira=datei1.jpg;dira=datei2.jpg;dirb=bild1.jpg;dirb=bild2.jpg
... usw.

Clientseitig wird die übertragene Sequenz deserialisiert und das JS-Objekt erzeugt:

DIRMAP = {
    dira: {
        'datei1.jpg' = true,
        'datei2.jpg' = true
    },
    dirb:{
        'bild1.jpg': true,
        'bild2.jpg': true
    }
};

Fazit EAV

In Perl ein Hash-of-Hashes, in PHP ein Array, in JavaScript eine Sammlung von Objekten, EAV ist nichts Neues. Der wahlfreie Zugriff (Random Access) auf die Daten wird über zwei Schlüssel erreicht, stets sind die Werte direkt addressierbar. Ein Vergleich EAV vs. XML ist unsinnig, weil das Eine die Datenstruktur und das Andere die Verpackung ist wobei Letztere auf ganz unterschiedliche Art und Weise erfolgen kann wie dieser Artikel zeigt. Über die Art und Weise der Verpackung für den Transport entscheiden Faktoren, die von der gewählten Datenstruktur unabhängig sind.

Serializer für EAV

Eine EAV-Datenstruktur bytesemantisch zu serialisieren ist extrem einfach und in jeder Programmiersprache umsetzbar. Untenstehend ein Beispiel in Perl:

use bytes;
sub eav2str{
    my $pkg = shift;
    my $ref = shift;
    my $CONTENT = '';
    foreach my $ent(keys %{$ref}){
        foreach my $att(sort {$b cmp $a} keys %{$ref->{$ent}}){
            my $val = $ref->{$ent}{$att} || '';
            $CONTENT .= pack("VVV", length $ent, length $att, length $val).$ent.$att.$val;
        }
    }
    return $CONTENT;
}

Das c-Äquivalent zur pack-Schablone "V" (Vax, Little Endian) ist der Datentyp uint32_t, d.h., daß beim Serializer die Byteorder zu berücksichtigen ist, weil die Längenangabe mit 4 Bytes kodiert wird. Infolge der Möglichkeit der Umkehrung der Byteorder ist es jedoch kein Problem, sich beim Serialisieren der Offsetangaben auf eine Byteorder festzulegen.

Untenstehend der JavaScript Code für einen Deserializer der die Networkorder (N Schablone, Big Endian) benutzt:

buffer2eav: function(buffer, raw){
        let eav = {};
        let dv = new DataView(buffer);
        let offs = 0;
        while (offs < dv.byteLength ){
            let elen = dv.getUint32(offs, false);
            offs += 4;
            let alen = dv.getUint32(offs, false);
            offs += 4;
            let vlen = dv.getUint32(offs, false);
            offs += 4;
            let ent = new StringView(buffer.slice(offs, offs + elen));
            offs += elen;
            let att = new StringView(buffer.slice(offs, offs + alen));
            offs += alen;
            let val = buffer.slice(offs, offs + vlen);
            offs += vlen;

            if(eav[ent] == null) eav[ent] = {};
            eav[ent][att] = raw != null ? val : new StringView(val);
        }
        return eav;
    }

Und das alles ist selbstverständlich auch recht einfach in jeder anderen Programmiersprache umsetzbar.


Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.