JSON-LD: Verlinkte Daten statt hart-kodierter Endpunkte

2 Kommentare

Mit JSON-LD können Abhängigkeiten zwischen Frontend und Backend reduziert werden. Im letzten Artikel habe ich gezeigt, wie sich die JSON-Bezeichner entkoppeln lassen. Als nächstes lernst du, wie ein Frontend Daten laden kann, ohne abhängig von bestimmten Endpunkten im Backend zu sein. Das Laden von Daten wird zum einen deutlich vereinfacht, zum anderen bleibt das Frontend stabiler gegenüber Änderungen im Backend.

Als Beispiel dient wieder ein Datensatz über eine Person:

{
    "id": "42",
    "firstname": "John",
    "lastname": "Doe",
    "address_id": "1337" 
}

Offenbar ist zu dieser Person auch eine Adresse unter der ID 1337 hinterlegt. Um diese Annahme zu bestätigen ist ein Blick in die API-Dokumentation nötig. Dort erfährt das Frontend-Team dann (hoffentlich) auch, wie genau die Adresse geladen werden kann. Ein URL-Template wie https://api.example/addresses/<id> wird dann oft fest im Code verdrahtet.

Mit JSON-LD verfolgen wir einen anderen Ansatz. Wie im letzten Artikel erwähnt, steht das „LD“ in JSON-LD für „Linked Data“. Statt nur lokal gültiger IDs verwenden wir global eindeutige, dereferenzierbare URIs, um Ressourcen im Web zu identifizieren. Die ID eines JSON-LD-Datensatzes ist immer ein vollständiger, internationalisierter URI (im Folgenden IRI: Internationalized Resource Identifier). Um den obigen Datensatz als JSON-LD abzubilden, fügen wir wieder einen Kontext hinzu:

{
  "@context": {
    "@vocab": "https://schema.org/",
    "@base": "https://api.example/",
    "firstname": "givenName",
    "lastname": "familyName",
    "id": "@id",
    "address_id": {
      "@id": "address",
      "@type": "@id"
    }
  },
  "id": "persons/42",
  "firstname": "John",
  "lastname": "Doe",
  "address_id": "addresses/1337"
}

Wie bereits im letzten Artikel bilden wir die vom Backend verwendeten JSON-Bezeichner auf das Vokabular von schema.org ab. Hinter firstname steckt also wieder ein https://schema.org/givenName und hinter lastname entsprechend ein https://schema.org/familyName. Bei address_id verwenden wir eine etwas komplexere Konfiguration. Zunächst legen wir mit "@id": "address" fest, dass es sich um eine https://schema.org/address handelt. Mit "@type": "@id" sagen wir zusätzlich, dass wir nur die ID der Adresse hinterlegt haben und kein komplettes Adress-Objekt.

An den Daten selbst hat sich diesmal eine Kleinigkeit geändert: Statt „42“ und „1337“ hinterlegen wir die relativen Pfade persons/42 und addresses/1337. Zusammen mit dem im Kontext unter @base hinterlegten Basis-IRI ergibt sich für die Adresse der vollständige IRI https://api.example/addresses/1337.

Im Frontend verwenden wir wieder einen eigenen Kontext und legen so die lokalen Bezeichner für die schema.org Eigenschaften fest:

const context = {
  "@vocab": "https://schema.org/",
  "first_name": "givenName",
  "last_name": "familyName",
  "postal_address": "address",
  "iri": "@id"
}

Indem wir den Frontend-Kontext anwenden, erhalten wir folgendes Objekt:

var person = {
  "@context": { /** ... */ },
  "iri": "https://api.example/persons/42",
  "postal_address": {
    "iri": "https://api.example/addresses/1337"
  },
  "last_name": "Doe",
  "first_name": "John"
}

Hier sehen wir nun leicht, wie wir die Adresse nachladen können, nämlich einfach von person.postal_address.iri. Es ist nicht nötig, vorab im Frontend einen Endpunkt für Adressen zu kennen und die ID korrekt in ein URL-Template oder Ähnliches einzubauen. Stattdessen können wir eine generische Ladelogik implementieren:

jsonld.compact(personDataFromBackend, context, (err, person) => {
  loadData(person.postal_address);
});
 
function loadData(resource) {
    console.log(`Loading ${resource.iri}`);
    // do http request ...
}

Gegenüber Änderungen des IRI ist das Frontend immun. Ändert dieser sich zum Beispiel zu https://api.example/person/42/address sind keine Anpassungen im Frontend nötig. Das Backend liefert dann "address_id": "person/42/address" und die Ladelogik funktioniert wie gehabt.

Selbst wenn sich das Backend-Team entscheidet, zusätzlich zur ID einige Adressdaten vorab mit zu senden, ändert sich im Frontend nichts. Lediglich das Backend ändert Daten und Kontext wie folgt:

{
  "@context": {
    "@vocab": "https://schema.org/",
    "@base": "https://api.example/",
    "firstname": "givenName",
    "lastname": "familyName",
    "street": "streetAddress",
    "id": "@id"
  },
  "id": "persons/42",
  "firstname": "John",
  "lastname": "Doe",
  "address": {
    "@id": "addresses/1337",
    "street": "Mainstreet 23"
  }
}

An der Semantik der Daten hat sich nichts geändert hat, daher funktioniert das Frontend wie gehabt weiter. Das Backend liefert ja weiterhin die ID der Adresse aus. Die hinzu gekommene https://schema.org/streetAddress können, aber müssen wir im Frontend nicht nutzen. Es ist aber generell empfehlenswert, das Frontend so zu implementieren, dass mitgelieferte Daten bereits verwendet werden, bis der Ladevorgang von ggf. zusätzlichen Daten abgeschlossen ist.

Wie wir sehen, gibt es mit JSON-LD keinen Grund mehr, „Endpunkte“ im Frontend hart zu kodieren. IDs werden zu abrufbaren IRIs – JSON wird zu Linked Data. In dieser Form werden Daten auch unabhängig von einer bestimmten JSON-Struktur und -Verschachtelung. Dies werde ich im nächsten Artikel näher beleuchten.

Ich habe auch wieder ein JSFiddle mit dem Beispiel aus diesem Artikel vorbereitet:

Angelo Veltens arbeitet als Web-Developer bei codecentric Hamburg. Er begeistert sich sowohl für die Frontend-Entwicklung mit JavaScript, als auch die Backend-seitige Entwicklung mit Frameworks wie Grails oder Spring Boot.
Testautomatisierung auf allen Ebenen von Unit- bis Akzeptanztests, sowie der Aufbau von Continuous-Delivery-Pipelines zählen dabei ebenfalls zu seinen Stärken.

Kommentare

  • Nils

    schema.org liefert ja nur ein eingeschränktes Vokabular. Sicher ist das nützlich, wenn Namen oder Postleitzahlen übergeben werden sollen. In meinen Projekten handelt es sich aber zu meist um semantisch einzigartige Daten, für die es keine schema.org-Entsprechung gibt. Kann hierfür JSON-LD auch einen Nutzen bringen? Könnte es z. B. Sinn machen, sich ein eigenes Hauptvokabular für die Schnittstelle zu definieren, das dann auf beiden Seiten adaptiert werden kann? Oder ist das unnütze Mehrarbeit?

    • Angelo Veltens

      13. Juli 2018 von Angelo Veltens

      Ich habe schema.org als Beispiel genutzt, weil es recht bekannt und sehr vielfältig ist. Dennoch stößt man damit natürlich an Grenzen. Im Web gibt es jedoch eine Vielzahl weiterer Ontologien, die alle mit JSON-LD genutzt werden können. Man kann natürlich auch ein eigenes Vokabular entwickeln, ich denke nicht dass dies unnütze Mehrarbeit ist, da man ohnehin über die Bedeutung der Daten reden muss die man austauschen möchte. Dies muss letztlich auch dokumentiert werden.

      Man kann aber sehr „lean“ anfangen, indem man einfach in @vocab eine beliebige IRI definiert, z.B.:

      {
        "@context": {
          "@vocab": "https://myorg.example/vocab/"
        },
        "blubb": 42,
        "bar": "lorem ipsum",
      }
      

      Nun kann man sich darüber einigen was denn genau ein „https://myorg.example/vocab/blubb“ bzw. „https://myorg.example/vocab/bar“ ist. Wenn sich dann eine Seite dafür entscheidet die JSON-Bezeichner zu ändern hat dies keine Auswirkungen auf die andere Seite. Die ändernde Seite muss lediglich ihren Kontext anpassen. Wenn sich tatsächlich die Semantik ändert, sich z.B. herausstellt,d ass ein „https://myorg.example/vocab/blubb“ eigentlich ein „https://myorg.example/vocab/blobb“ sein sollte müssen natürlich beide Seiten angepasst werden. Der Vorteil hier ist jedoch, dass es eine klare Trennung zwischen tatsächlicher fachlicher Änderung und reiner technischer Umbenennung gibt.

      Die Vorteile der Verlinkung lassen sich ebenfalls ohne größeren Overhead nutzen:

      {
        "@context": {
          "@vocab": "https://myorg.example/vocab/",
          "@base": "https://myorg.example/",
          "baz_id": {
            "@type": "@id"
          }
        },
        "@id": "foo/1",
        "blubb": 42,
        "bar": "lorem ipsum",
        "baz_id": "baz/1337"
      }

      Den Aufwand das Vokabular „https://myorg.example/vocab/“ tatsächlich semantisch voll auszuzeichnen würde ich mir erst machen,
      wenn ich mir der Domäne sehr sicher bin und nur noch sehr selten Änderungen zu erwarten sind.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.