Serwisy RESTowe powinny podawać dane semantyczne. To czwarty i najwyższy stopień dojrzałości naszego API zwany HATEOAS. Aby nie być związanym z XMLem dziś zajmiemy się semantyczną stroną danych w JSON czyli JSON Linked Data.
W poprzednim artykule opisałem semantyczność RDF. Na koniec zbudowaliśmy przykładowy dokument opisujący zbiór dwóch aut marki Opel. Dziś będziemy korzystać z tego przykładu ale najpierw krótkie wprowadzenie.
JSON i semantyka
Załóżmy, że mamy serwis, którego API podaje dane w formie JSON. Idąc dalej w przykłady motoryzacyjne niech to będzie np. komis samochodowy. Taki komis wystawia api, które pozwala na wyszukiwanie co jest do sprzedaży. Pod adresem komis-u-janusza/niemiec-plakal-jak-sprzedawal/1234 będzie dostępny taki pojazd:
{ "model": "opel-corsa", "productionDate": "2006-01-01" }
Mamy prosty dokument JSON, ale znów nie wiemy co może oznaczać każde pole. Z pomocą przychodzi JSON-LD. Możemy odwołać się do słownika, który powie nam co oznaczają wszystkie pola. Na początek:
{ "@context": "http://adam.wroclaw.pl/opels.jsonld#", "model": "opel-corsa", "productionDate": "2006-01-01" }
Czym jest tajemniczy kontekst? To trochę jak przestrzeń nazw w RDF. Od tej pory nazwy będą oznaczały to, co zdefiniowaliśmy w kontekście. I tak np „opel-corsa” daje nam „http://adam.wroclaw.pl/opels.jsonld#opel-corsa” i podobnie. Nasz adres to definicja z poprzedniego przykładu przekształcona do formatu JSON-LD.
Ale mamy tu nieścisłość. Przecież w naszej definicji nie wiemy co oznacza „name” albo „productionDate”. Musimy rozszerzyć nasz kontekst:
{ "@context": { "model": "@type", "productionDate":"http://purl.org/vso/ns#productionDate", "opel-corsa": "http://adam.wroclaw.pl/files/opels.jsonld#opel-corsa" }, "@id": "1234", "model": "opel-corsa", "productionDate": "2006-01-01" }
Co tu się stało? Idziemy po kolei:
Najpierw dodaliśmy element „@context”. Dzięki temu parser semantyczny będzie wiedział w jakim kontekście ma rozumieć dane.
"model": "@type",
W pierwszym wierszu kontekstu zdefiniowaliśmy, że nazwa „model” oznacza tak naprawdę typ (klasę, zbiór) elementu.
"opel-corsa": "http://adam.wroclaw.pl/files/opels.jsonld#opel-corsa"
Następnie podaliśmy adres typu. Rozwiązujemy nazwę „opel-corsa” na pełen adres. Pod podanym adresem jest definicja z poprzedniego artykułu.
"productionDate":"http://purl.org/vso/ns#productionDate",
Powiedzieliśmy, że nazwę „productionDate” trzeba rozumieć według definicji zawartej w http://purl.org/vso/ns#productionDate.
"@id": "1234",
Na koniec dodaliśmy jeszcze identyfikator. W ten sposób semantycznie wiemy, że nasz obiekt posiada unikalny identyfikator.
Nie musimy używać kontekstu. To samo moglibyśmy zapisać bezpośrednio podając pełne identyfikatory w postaci IRI:
{ "@id": "http://adam.wroclaw.pl/opels.jsonld#opel-corsa", "http://purl.org/vso/ns#productionDate": "2006-01-01" }
Oba zapisy znaczą to samo. Ale w pierwszym nie musieliśmy zmieniać naszych danych. Często mamy już gotowe serwisy RESTowe i gotowych klientów. Zmiana formatu danych to duży kłopot. JSON-LD pozwala nam dodać kontekst czyli zdefiniować semantykę bez dotykania samych danych.
Różne formaty JSON-LD
JSON-LD to nie jeden ustalony format. Możemy używać przedrostków podobnie jak używaliśmy przestrzeni nazw w RDF. Możemy całość zapisać jako zestaw wierzchołków i powiązań w grafie. Możemy wprost podawać wszystkie adresy (identyfikatory) w elementach albo korzystać z kontekstu.
Przykładowo jeśli chcemy użyć przedrostków, możemy napisać tak:
{ "@context": { "vso":"http://purl.org/vso/ns#", "opels":"http://adam.wroclaw.pl/files/opels.jsonld#" }, "@id": "1234", "@type": "opels:opel-corsa", "vso:productionDate": "2006-01-01" }
Całość przypomina nam przestrzenie nazw w XML. Dodatkowy bonus – w JSON-LD w wartościach też możemy używać przedrostków. XML nam na to nie pozwalał i musieliśmy korzystać z pomocy encji.
Możemy również zapisać ten sam dokument jako listę elementów grafu:
{ "@graph": [ { "@id": "1234", "@type": "http://adam.wroclaw.pl/files/opels.jsonld#opel-corsa", "http://purl.org/vso/ns#productionDate": "2006-01-01" } ] }
Tutaj mamy tylko jeden element z zadanym id oraz dwiema relacjami. Nic nie stoi na przeszkodzie aby takich elementów było wiele. Co więcej, definicję „@graph” też możemy poprzedzić kontekstem, gdzie ustawimy przestrzenie nazw (przedrostki).
Warto pobawić się w JSON-LD Playground, gdzie możemy wpisać nasz kod i obejrzeć go w kilku układach JSON-LD.
JSON-LD a RDF
Dokumenty RDF można łatwo zamienić na JSON-LD. Wystarczy skorzystać z translatora RDF dostępnego online. W ten sposób RDF z poprzedniego artykułu został zamieniony na JSON-LD dostępny tutaj.
W wyniku dostajemy dokument JSON-LD w zapisie grafowym. Możemy teraz skorzystać z JSON-LD Playground aby zamienić go na dowolną formę.
Podsumowanie
W przykładzie zbudowaliśmy dokument semantyczny nie zmieniając zupełnie samej treści JSONa. Wszystkie aplikacje korzystające z naszego api będą nadal działać (o ile ignorują nieznane im elementy (np „@context” i „@id”).
Tworząc od nowa serwis RESTowy będziemy budować go inaczej. Najpierw stworzymy naszą ontologię czyli słownik znaczeń. Potem w kontekście odwołamy się do niego. Na sam koniec będziemy podawać dane, których semantyka będzie już znana.
W ten właśnie sposób łączymy HATEOAS czyli semantyczne dane z danymi w JSON. Korzystając z wybranych ontologii możemy tworzyć linki, których znaczenie będzie znane automatom.