Blog Detail

Der OParl Update-Mechanismus: schnelles Aktualisieren eines lokalen Bestandes

05 Sep 17
Ernesto Ruge
6 comments

Der Fokus von OParl 1.0 liegt auf einem schnellen und effizienten Abruf von Daten. Dies beinhaltet auch das schnelle und effiziente Aktualisieren eines lokalen Bestandes. Bei einer täglichen Synchronisation kann so mit einer geringen Anzahl an Anfragen an den OParl-Server ein Update durchgeführt werden. Diese Funktionsweise und die Nutzung dieses Mechanismus soll in diesem Artikel genauer beleuchtet werden.

UPDATE: entstanden durch diese Diskussion auf Github wurden Teile des Artikels verändert, da wir dank der Weiterentwicklung der Ratsinformationssysteme nun einen besseren Update-Mechanismus nutzen können. Dadurch wird der Absatz „Modified-Handling bei eingebetteten Objekten“ obsolet.

Dies bedeutet auch, dass der Update-Mechanismus erst ab OParl 1.1 funktionieren wird. Mit OParl 1.1 werden wir Objektlisten aller Objekte von Body aus einführen, und alle Objekte erhalten die Attribute created und modified.

Erstabruf von Daten

Möchte man den gesamten Bestand aktualisieren, so muss man lediglich die von Body ausgehenden URLs aufrufen. Beginnt man also mit dem Abruf des Bodys:

$ curl -s https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1 | json_pp

so erhält man die gewünschten Links:

{
    id: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1",
    type: "https://schema.oparl.org/1.0/Body",
    system: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/system",
    name: "Gemeinde Vettweiß",
    ags: "05358060",
    contactEmail: "sdnetrim@kdvz-frechen.de",
    contactName: "Gemeinde Vettweiß",
    organization: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/organization",
    person: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/person",
    meeting: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/meeting",
    paper: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/paper",
    legislativeTerm: [ ],
    location: {
        id: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/location/0-1",
        streetAddress: "Gereonstraße 14",
        postalCode: "52391"
    }
}

Um nun also für die erste Synchronisation alle Paper eines OParl Bodies abzurufen, ruft man die Paper-URL ohne Parameter auf:

$ curl -s https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/paper | json_pp

Als Antwort erhält man eine externe Objektliste (vgl. Kapitel 2.5.3), in der sämtliche Daten der Paper enthalten sind:

{
    data: [
        {
            id: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/paper/13",
            type: "https://schema.oparl.org/1.0/Paper",
            body: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1",
            name: "Unterschutzstellung des Bildstockes Ecke Triftstraße/Antoniusstraße in der Ortschaft Ginnick",
            reference: "V-4/2007",
            date: "2007-06-14",
            paperType: "Vorlage",
            mainFile: {
                id: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/file/1-127",
                type: "https://schema.oparl.org/1.0/File",
                body: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1",
                name: "Vorlage (Unterschutzstellung des Bildstockes Ecke Triftstraße/Antoniusstraße in der Ortschaft Ginnick)",
                mimeType: "application/pdf",
                accessUrl: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/files/rim4992/UGhVM0hpd2NXNFdFcExjZZLUWrBty0zU28z_p4UFrKwU9pu0Gz_iPO0hlnPSFDZ8/Vorlage_V-4-2007.pdf",
                downloadUrl: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/files/rim4992/UGhVM0hpd2NXNFdFcExjZZLUWrBty0zU28z_p4UFrKwU9pu0Gz_iPO0hlnPSFDZ8/Vorlage_V-4-2007.pdf",
                created: "2010-01-20T17:32:45+01:00",
                modified: "2010-01-20T17:32:45+01:00"
            },
            consultation: [
                {
                    id: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/consultation/8",
                    type: "https://schema.oparl.org/1.0/Consultation",
                    agendaItem: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/agendaitem/373",
                    meeting: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/meeting/257",
                    organization: [
                        "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/organization/184"
                    ]
                },
                {
                    id: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/consultation/10",
                    type: "https://schema.oparl.org/1.0/Consultation",
                    agendaItem: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/agendaitem/396",
                    meeting: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/meeting/249",
                    organization: [
                        "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/organization/183"
                    ]
                }
            ],
            created: "2010-01-20T17:32:45+01:00",
            modified: "2010-01-20T17:32:45+01:00"
        },
        [...]
    ],
    pagination: {
        totalElements: 977,
        elementsPerPage: 25,
        currentPage: 1,
        totalPages: 40
    },
    links: {
        first: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/paper?page=1",
        self: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/paper?page=1",
        last: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/paper?page=40",
        next: "https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/paper?page=2"
    }
}

Mit den Links in dem pagination-Block können dann die weiteren Seiten aufgerufen werden, so dass man Seite für Seite den kompletten Datenbestand herunterladen kann. Mit der stabilen Sortierung (vgl. Kapitel 2.5.4) wird sichergestellt, dass keine Elemente verloren gehen.

Update eines Datenbestandes

Da ein Ratsinformationssystem üblicherweise aus sehr vielen Objekten besteht, dauert eine Synchronisation wie die oben beschriebene doch etwas Zeit. Hat man also ein mal einen Bestand heruntergeladen, so möchte man nur noch die Änderungen Abfragen, die mit weit weniger Anfragen auskommen. Dies wird über die Filter (vgl. Kapitel 2.5.5) realisiert.

Hierzu speichert man Datum und Uhrzeit der letzten Synchronisation ab und nutzt dieses für die nächste Synchronisation als URL-Parameter. Da man alle Änderungen seit der letzten Synchronisation haben möchte, nutzt man den Parameter modified_since:

$ curl -s https://sdnetrim.kdvz-frechen.de/rim4992/webservice/oparl/v1/body/1/paper?modified_since=2017-09-01T00%3A00%3A00%2B01%3A00 | json_pp

Als Antwort erhält man die oben beschriebene externe Objektliste, lediglich gefiltert um alle Objekte nach dem übergebenen Zeitpunkt, was für viel weniger Objekte und damit in vielen Fällen nur eine einzige Seite sorgt. Bei einem regelmäßigen Update sind so lediglich der Aufruf des Bodys und der davon abgehenden Objektlisten notwendig. Die so deutlich reduzierten Anfragen gegenüber einer vollen Synchronisation sparen Zeit und Ressourcen.

 

Empfehlungen für stabile Sortierung

Ein sauberes Update kann nur dann funktionieren, wenn die Paginierung stabil ist und sich so die Liste nicht zwischen zwei Abrufen verändert. Dies klingt einfacher, als es tatsächlich ist. Unsere Behandlung gelöschter Objekte vereinfacht dies jedoch erheblich. In der Praxis haben sich je nach Datenbank-System zwei Konzepte als sinnvoll herausgestellt:

  1. Sortierung nach ID: im Fall einer SQL-Datenbank mit Auto-Increment-Index (z.B. MySql und Postgres) eignet sich dieser Index für eine Sortierung, da neue Datensätze dann immer am Ende eingefügt werden.
  2. Sortierung nach createdWenn wie zum Beispiel in der NoSQL-Datenbank MongoDB kein Auto-Increment-Index zur Verfügung steht, eignet sich dascreated-Attribut für eine Sortierung. Wenn created sauber implementiert wird, so ist ein neuer Wert in der Datenbank zwangsweise nach allen anderen, da created nach der Erstellung des Objekts nicht mehr verändert werden darf.

Beachtet man die Regeln für gelöschte Werte nicht, so rutscht die gesamte Liste bei einem Löschvorgang zwischen zwei Client-Requests die gesamte Liste nach Vorne, und der Client bekommt Objekte nicht mit. Deswegen ist es absolut elementar, die gelöschten Objekte wie in der Spezifikation beschrieben einzusortieren.

Weitere Fragen und Antworten

Wenn bei dem Update-Mechanismus oder anderen Details der Spezifikation Fragen auftreten, so kommentieren Sie gerne diesen Blogbeitrag oder schreiben Sie eine Mail!

6 Comments

  1. Andreas Kuckartz Oktober 1, 2017 at 11:20 pm Reply

    Der Aufruf des Beispiels unter „Update eines Datenbestandes“ funktioniert nicht:
    {
    „error“ : „Die angeforderte Ressource wurde nicht gefunden.“,
    „type“ : „SD.NET RIM Webservice“,
    „code“ : 802
    }
    Wenn ich den Wert des „modified_since“-Parameters zwei Monate vorverlege, dann geht es. Anscheinend fehlen auf dem Server gegenwärtig aktuellere Daten.

  2. Andreas Kuckartz Oktober 2, 2017 at 7:54 am Reply

    Eine Frage zu „Der oben beschriebene Update-Mechanismus kann nur dann funktionieren, wenn man … die Modified-Werte auch bei den „Eltern“ aktualisiert.“

    Verstehe ich das richtig, dass diese modified-Werte redundante Daten enthalten sollen oder müssen? Falls ja, dann verstehe ich nicht warum die Ausgabe und Übertragung dieser redundanten Informationen notwendig sein soll.

  3. Ernesto Ruge Oktober 2, 2017 at 9:26 am Reply

    Das erste Problem ist ein offensichtlicher Bug von SD.NET. Ich habe den Hersteller mal darüber informiert. Die Schnittstelle ist (wie die Schnittstellen aller Hersteller) aber eh noch in der Beta-Phase, so dass so etwas vorkommen kann. Im Rahmen von „Politik bei Uns 2“ betreiben wir zur Zeit eine schrittweise Verbesserung der Schnittstellen aller Hersteller, von daher: danke für diesen Bugreport.

    [admin]Der nachfolgende Teil dieses Kommentars ist durch die in https://github.com/OParl/spec/issues/375 entstandene Entscheidung obsolet.[/admin]

    Das zweite ist allerdings Absicht. Hintergrund ist, dass kein RIS-Hersteller die Created- und Modified-Daten zu den internen Objekten wie z.B. AgendaItem, Consultation oder Membership abspeichert. Hätten wir nun vorausgesetzt, dass von Body aus jedes Objekt – also auch z.B. die Consultation – einzeln als Liste zur Verfügung stehen muss, so hätte kein RIS-Hersteller dies umsetzen können, da die Daten eben schlicht nicht vorliegen. Somit war die Konstruktion der internen Ausgabe von bestimmten Objekten ein sehr gut funktionsfähiger Weg, um doch einen sauber funktionierenden Update-Mechanismus mit den real existierenden Daten aufzubauen.
    Dies ist wie auch die vielen optionalen Felder Ausdruck unserer Grundlage, dass wir einen pragmatischen Weg bevorzugen, der auch mit real existierenden Daten und real existierender Software ohne extreme Investitionen funktioniert.

  4. Andreas Kuckartz Oktober 3, 2017 at 10:37 pm Reply

    Die Antwort scheint auf eine Frage einzugehen, die ich nicht gestellt hatte. Selbstverständlich sollen keine Daten ausgegeben werden, die nicht vorhanden sind. (Ob Änderungen in gegenwärtig durch Kommunen verwendeter Software „extreme“ Investitionen erfordern würden möchte ich hier nicht thematisieren.)

    Das erklärt aber nicht diesen Satz (und die angeführten Aktualisierungsregeln):

    „Es ist daher also zwingend erforderlich, dass der modified-Wert des Paper aktualisiert wird, wenn das darin enthaltene File, die darin enthaltene Consultation oder andere interne Objekte eine Änderung erfahren.“

    Welche Information wird dadurch kodiert, dass an den Kindern befindliche Daten zusätzlich auch als Kopie den Eltern mitgegeben werden? Anders gefragt: Warum wird die Kopie (Zeile 34 in dem Beispiel) nicht schlicht weggelassen?

    Ist der Grund dafür dieser Satz in Abschnitt „3.3.6 modified“ der Spezifikation: „Diese Eigenschaft muss – genau wie created – in allen Objekttypen angegeben werden, die nicht in anderen Objekten intern ausgegeben werden.“? Oder gibt es dafür einen anderen Grund?

    Hintergrund der Frage ist, dass ich OParl-Daten in Linked Data/RDF konvertieren möchte und ich deshalb Missverständnisse der Spezifikation (und Ergänzungen wie auf dieser Seite) vermeiden möchte.

  5. Ernesto Ruge Oktober 4, 2017 at 4:30 pm Reply

    [admin]Dieser Kommentar ist durch die in https://github.com/OParl/spec/issues/375 entstandene Entscheidung obsolet.[/admin]
    Die Antwort erklärt den Hintergrund dieser (und vieler weiterer) Design-Entscheidungen, von da aus ist das ja schon relevant.
    Zu dem konkreten Fall: die modified-Werte der internen Objekte (Plural) sind ja nicht zwangsweise alle identisch (bzw: dies ist sogar eher selten der Fall). Somit ist es durchaus eine wertvolle Information, wann ein File zuletzt modifiziert wurde, und daher ist es durchaus empfehlenswert, diese Information auch auszugeben.
    Beispiel: das File in Paper->mainFile wurde modifiziert, dadurch erhält auch Paper ein neuen modified-Timestamp. Ein ebenso vorhandenes Paper->auxiliaryFile bleibt aber unangetastet, so dass dort modified auf dem alten Wert bleibt (und so z.B. die Datei nicht neu gedownloaded werden muss).

  6. Ralf Sternberg Oktober 11, 2017 at 1:49 pm Reply

Leave A Comment