layline.io Blog

Verdammt sei die Datenformat-Hölle.

Der Umgang mit komplexen Datenformaten und -änderungen kann entmutigend sein. Erfahren Sie, wie layline.io diese Herausforderung mit einer konfigurierbaren Grammatiksprache meistert.

May 2, 2022
Reading time: 10 min.

Einstellung

Sie sind der Mann/die Frau, der/die sich um die Datenverarbeitungsinfrastruktur der ACME Corp. kümmert und diese betreibt. Das ist die komplexe und empfindliche Maschinerie, die die ganze schwere Datenarbeit in Ihrem Unternehmen erledigt, aber nie eine Belohnung dafür erhält. Es ist die Pumpe, die nicht aufhören darf zu pumpen, es ist der Saft, der die Dinge am Laufen hält.

Doch wenn Ihre Eltern, Freunde oder sogar Kollegen Sie fragen, was Sie bei ACME tun, schlafen sie mitten in Ihrer begeisterten Erklärung ein.

Machen wir uns nichts vor: Du bekommst nicht genug Anerkennung für das, was du für die Welt tust 🙃.

Herausforderungen

In Ihrem Job müssen Sie zwar nicht in einem Mechaniker-Outfit herumlaufen, aber es fühlt sich so an, als müssten Sie ständig beobachten, ausbessern, korrigieren oder einfach nur neuen Schwung in die datenverarbeitende Infrastruktur bringen.

Es gibt zwar viele alltägliche Herausforderungen, aber eine davon ist die VERÄNDERUNG. Man sagt zwar, dass man niemals ein laufendes System ändern sollte, aber so ist die Welt nicht. Eine der häufigeren Änderungen ist ...

Änderung des Datenformats

AAHHHH! Roter Alarm ... Sirenen ertönen ...! Wenn es um die Änderung von Datenformaten geht, kommen oft mehrere Herausforderungen gleichzeitig auf uns zu:

  1. Datenschnittstellen sind oft fest in einem Quellcode kodiert. Man kann sie nicht einfach "ändern" (wie der Chef vorschlägt), oder zumindest ist es nicht so einfach.
  2. Während der Migration werden die Daten sowohl im alten als auch im neuen Format empfangen. Selbst mit einer kleinen Änderung sind das immer noch zwei verschiedene Formate, die parallel verarbeitet werden müssen.
  3. Es geht nicht nur um ein weiteres Feld. Manchmal handelt es sich um eine ganz neue Datenstruktur und eine Reihe anderer Dinge, die gleichzeitig bearbeitet werden müssen.
  4. Geänderte Daten erfordern möglicherweise die interne Verarbeitung zusätzlicher Informationen usw. Auch hier kann es sein, dass einige Dinge fest kodiert sind und daher der Code geändert werden muss, um sie zu berücksichtigen.
  5. Sie haben keinen Kaffee mehr. Das ist keine Hilfe.

Wir haben hier zwei Hauptprobleme, nämlich

  1. Änderung des Codes und
  2. Handhabung der Formatmigration.

Dies kann ziemlich viel Kopfzerbrechen bereiten und lange Zyklen der Planung, Entwicklung, Freigabe, Prüfung und schließlich Bereitstellung erfordern.

Wenn es nur einen besseren Weg gäbe, dies schnell und einfach zu erledigen ...

Generische Datenformate als Retter

Natürlich ist dies ein Blog über layline.io und wie großartig es ist, falls Sie das noch nicht wussten (siehe unten). Werfen wir also einen Blick darauf, wie layline.io diese Herausforderung angeht:

layline.io bietet Generische Datenformate, die nicht alle, aber die meisten der oben genannten Herausforderungen lösen können.

Was sind generische Datenformate?

Wie der Name schon sagt, erlaubt dieses Konzept, Datenformate auf generische Weise zu definieren. Zu diesem Zweck bietet es

  1. eine Sprache, um die Struktur (Grammatik) des Formats zu definieren, um das Sie sich bemühen.
  2. Diese Sprache verwendet reguläre Ausdrücke, um einzelne Elemente einer Struktur zu definieren und zu identifizieren, und dann
  3. die Unterelemente dieser Struktur, usw..
  4. Sie ist insofern objektorientiert, als dass man Element-Strukturen durchgängig definieren und wiederverwenden kann.

Wie sieht diese Sprache aus?

Schauen wir uns das mal an. Zu diesem Zweck werden wir mit einem supereinfachen Datenformat arbeiten, das kommagetrennt ist, einen Header-Datensatz, 1..n Detail-Datensätze und einen Trailer-Datensatz haben muss. Ein Format, das wir alle auf die eine oder andere Weise kennen.

Beispieldaten für ein einfaches Bank-Transaktionsprotokoll:

H;Sample Bank Transactions
D;20-Aug-2021;NEFT;23237.00;00.00;37243.31
D;21-Aug-2021;NEFT;00.00;3724.33;33518.98
T;100

Und hier ist, wie dieses Format in layline.io unter Verwendung der generischen Grammatiksprache definiert wird:

format
{
    name = "Bank Transactions"
    description = "Random bank transactions"

    start - element = "File"
    target - namespace = "BankIn"

    elements = [
        // #####################################################################
        // ### File sequence
        // #####################################################################
        {
            name = "File"
            type = "Sequence"
            references = [
                {
                    name = "Header"
                    referenced-element = "Header"
                },
                {
                    name = "Details"
                    max-occurs = "unlimited"
                    referenced-element = "Detail"
                },
                {
                    name = "Trailer"
                    referenced-element = "Trailer"
                }
            ]
        },

        // #####################################################################
        // ### Header record 
        // #####################################################################
        {
            name = "Header"
            type = "Separated"
            regular-expression = "H"
            separator-regular - expression = ";"
            separator = ";"
            terminator-regular - expression = "\n"
            terminator = "\n"

            mapping = {
                message = "Header"
                element = "BT_IN"
            }

            parts = [
                {
                    name = "RECORD_TYPE"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value.type = "Text.String"
                },
                {
                    name = "FILENAME"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value.type = "Text.String"
                }
            ]
        },
        // #####################################################################
        // ### Detail record
        // #####################################################################
        {
            name = "Detail"
            type = "Separated"
            regular-expression = "D"
            separator-regular - expression = ";"
            separator = ";"
            terminator-regular - expression = "\n"
            terminator = "\n"

            mapping = {
                message = "Detail"
                element = "BT_IN"
            }

            parts = [
                {
                    name = "RECORD_TYPE"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value.type = "Text.String"
                },
                {
                    name = "DATE"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value = {
                        type = "Text.DateTime"
                        format = "dd-MMM-uuuu"
                    }
                },
                {
                    name = "DESCRIPTION"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value = {
                        type = "Text.String"
                    }
                },
                {
                    name = "DEPOSITS"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value = {
                        type = "Text.Decimal"
                    }
                },
                {
                    name = "WITHDRAWALS"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value.type = "Text.Decimal"
                },
                {
                    name = "BALANCE"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value = {
                        type = "Text.Decimal"
                    }
                }
            ]
        },
        // #####################################################################
        // # Trailer record                                         
        // #####################################################################
        {
            name = "Trailer"
            type = "Separated"
            regular-expression = "T"
            separator-regular - expression = ";"
            separator = ";"
            terminator-regular - expression = "\r?\n"
            terminator = "\n"

            mapping = {
                message = "Trailer"
                element = "BT_IN"
            }

            parts = [
                {
                    name = "RECORD_TYPE"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value.type = "Text.String"
                },
                {
                    name = "RECORD_COUNT"
                    type = "RegExpr"
                    regular-expression = "[^;\n]*"
                    value.type = "Text.Integer"
                }
            ]
        }
    ]
}

Sie ist ziemlich selbsterklärend und sieht einer JSON-formatierten Datei sehr ähnlich. Aber auf den zweiten Blick ist es kein JSON. Schauen wir uns einige Details an.

Das "Format"-Element

Alles beginnt mit dem Top-Level-Element "format":

Formatelement Railroad-Diagramm
Formatelement Railroad-Diagramm

Neben einem "Namen" und einer "Beschreibung" hat es auch eine Reihe von "Elementen". Diese Elemente definieren eine Reihe von Unterelementen (Klassen), die von unterschiedlichem Typ sein können und die dann das gesamte Format bilden. In unserem Beispiel ist dies nur ein Element mit dem Namen "File" und dem Typ "Sequence".

Das File-Element vom Typ Sequence

Dieses Element definiert eine logische Struktur von Unterelementen in seiner references Struktur mit den Namen "Header", "Detail", und "Trailer".

Sequenzelement Railroad Diagramm
Sequenzelement Railroad Diagramm

Sie können vielleicht erkennen, dass wir hier einen Baum bauen:

Elementbaum für Bankgeschäfte
Elementbaum für Bankgeschäfte

Das Header Element vom Typ Separated

Bislang hatten wir nur logische Elemente, die eine Struktur definieren. Das Element Header vom Typ Separated definiert nun zum ersten Mal, wie Daten in der Datei tatsächlich identifiziert werden können.

...
// #####################################################################
// ### Header record 
// #####################################################################
{
    name = "Header"
    type = "Separated"
    regular - expression = "H"
    separator - regular - expression = ";"
    separator = ";"
    terminator - regular - expression = "\n"
    terminator = "\n"

    mapping = {
        message = "Header"
        element = "BT_IN"
    }
...

Ein Element des Typs Separated hat eine Eigenschaft regular-expression, die zur Angabe eines regulären Ausdrucks (tataa!) zur Identifizierung des Elements im Datenstrom verwendet wird. In unserem Fall ist dies einfach "H", um einen Header-Datensatz zu identifizieren. Siehe die Beispieldatei oben. Es hat auch einen terminator-regular-expression, der auf "\n" gesetzt wird, um das Ende des Elements zu markieren. In unserem Fall ist das resultierende Element dann eine ganze Zeile von "H" bis zum Zeilenvorschub am Ende der ersten Zeile.

H;Sample Bank Transactions
...

Sie verstehen die Idee.

Der "Mapping"-Teil teilt layline.io mit, wie dieses Element in einem layline.io Workflow referenziert werden kann, sobald die Daten geparst sind. Dazu kommen wir etwas später.

Außerdem finden wir in der Header-Deklaration:

...
parts = [
    {
        name = "RECORD_TYPE"                    // name of the element
        type = "RegExpr"                        // pick a type
        regular-expression = "[^;\n]*"      // this is how we identify the first field (element)
        value.type = "Text.String"              // this is the field type
    },
    {
        name = "FILENAME"
        type = "RegExpr"
        regular-expression = "[^;\n]*"
        value.type = "Text.String"
    }
]
...

Hier werden nun einzelne Teile (wiederum Elemente) des Headers definiert. Im Fall des Headers sind das die beiden Elemente RECORD_TYPE und FILENAME. Beide sind vom Typ RegExpr, was bedeutet, dass sie wiederum durch einen regulären Ausdruck innerhalb des Header-Datensatzes identifiziert werden können. Es gibt zwei weitere Typen Calculated und Fixed zur Auswahl.

Die Elemente Detail und Trailer vom Typ Separated

Wie beim Header fahren wir einfach damit fort, die Struktur mit den Detail- und Trailer-Elementen zu definieren, auf die wir in der obigen File-Sequence verwiesen haben.

Am Ende erhalten wir eine fertige Struktur wie diese:

Bankgeschäfte vollständiger Elementbaum
Bankgeschäfte vollständiger Elementbaum

Vorläufige Schlussfolgerung

Wir haben gelernt, dass die Grammatik folgendermaßen funktioniert:

  1. Eine Grammatik besteht aus einer Reihe von Elementen,
  2. Diese Elemente gibt es in verschiedenen Arten, die unterschiedlichen Zwecken dienen,
  3. Das erste Element ist format, das auf ein Startelement verweist, das Sie ebenfalls definieren,
  4. Sie können eine beliebige Anzahl von zusätzlichen Elementen definieren,
  5. Einige Elemente können dann auf andere Elemente verweisen. Elemente können als wiederverwendbare Klassen betrachtet werden.

Das bietet EINE MENGE an Möglichkeiten, wie eine Grammatik auf reale Datenformate abgebildet werden kann

Wie sieht es mit anderen, komplexeren Formaten aus?

Wir wissen natürlich, dass die Datenformate in IHRER Welt nicht so einfach sind wie das, das wir in unserem Beispiel gewählt haben. Hier ist, was sonst noch funktioniert:

  • Sehr komplexe ASCII/Unicode-Formate, z.B. alle Arten von verschiedenen Satztypen, hierarchische Strukturen.
  • Bedingtes Parsen von Daten durch Verwaltung des bedingten Parser-Status, z.B. "Satztyp B darf nur direkt nach A kommen".
  • Binäre Strukturen.
  • Eine Mischung aus ASCII- und Binärformaten.

Wir glauben, dass dies mehr als 80% aller Datenaustauschformate abdeckt. Es gibt natürlich immer noch andere, wie z.B. ASN.1-basierte Formate u.a. layline.io verfügt auch über andere, bessere Parser für diese Typen.

Unterstützung mehrerer Formate

Sie können beliebig viele Formate definieren. layline.io kompiliert alle Formate zur Laufzeit zu einem "Superformat". Dadurch können Sie

  • alle Formate von überall referenzieren,
  • Daten von einem Format auf ein anderes abbilden,
  • neue Nachrichteninstanzen auf Basis eines bestimmten Formats zu erzeugen,
  • einzelne Elementstrukturen innerhalb einer Nachricht erstellen oder zerstören und
  • Daten in jedem der definierten Formate einlesen oder ausgeben.

Wo konfiguriere ich das alles?

Um Sie nicht noch mehr zu verärgern, haben wir eine schöne Benutzeroberfläche bereitgestellt, die Ihnen bei der Konfiguration hilft und Sie aufmuntert. Sie finden diese im webbasierten layline.io Configuration Center unter Projekt --> Formate --> Generisches Format:

Hier geben Sie die gesamte Grammatik ein:

Generic Grammar Editor
Generic Grammar Editor

Und das ist noch nicht alles. Während Sie Ihre Grammatik definieren, können Sie eine Beispieldatei hochladen und Seite an Seite sehen, ob Ihre Grammatik mit der Dateistruktur übereinstimmt:

Grammar Sample File Viewer
Grammar Sample File Viewer
Grammar Sample Messages Viewer
Grammar Sample Messages Viewer

Ziemlich cool, oder?

Referenzierung von Daten innerhalb der Logik

Es macht keinen Sinn, Grammatiken zu definieren, wenn Sie nicht vorhaben, Daten innerhalb Ihrer Verarbeitungskette zu referenzieren und ggf. zu manipulieren. Nachdem Sie eine oder mehrere Grammatiken in layline.io definiert haben, können Sie auf einzelne Elemente und Strukturen innerhalb dieser Grammatiken zugreifen wie folgt:

Beispiel: Datenzugriff im Mapping-Asset

Datenzugriff im Mapping Asset
Datenzugriff im Mapping Asset

Beispiel: Datenzugriff in Javascript Asset

**Beispiel: Datenzugriff in Javascript Asset**
**Beispiel: Datenzugriff in Javascript Asset**

Umgang mit Formatänderungen

Wenn wir uns die Herausforderungen ansehen, über die wir zu Beginn gesprochen haben, sollte jetzt klarer sein, wie einfach es ist, sich an Formatänderungen anzupassen.

  • Sie brauchen ein weiteres Feld? Fügen Sie es einfach in die Grammatik ein.
  • Sie brauchen eine andere Datensatzstruktur? Wiederum fügen Sie es einfach der Grammatik hinzu.
  • Müssen Sie eine alte und eine neue Version eines Detaildatensatzes gleichzeitig unterbringen? Kein Problem: Sie können ein "Choice"-Element einfügen, um entweder eine "Detail-Old"- oder eine " Detail-New"-Version eines Datensatzes zu ermöglichen, solange sie durch unterschiedliche reguläre Ausdrücke identifiziert werden können.
  • Sie müssen Inhalte auf der Grundlage anderer Inhalte berechnen. Fügen Sie einfach Formeln hinzu.

Das ist alles schon erledigt.

Und was dann?

Datenformat-Konfigurationen sind ein wichtiger Bestandteil bei der Definition der Datenverarbeitung in layline.io. Sie sind in der Regel Teil eines

  • layline.io-Projekts, das besteht aus
  • einer Reihe von Workflows, die alle mit unterschiedlichen Formaten arbeiten können
  • vielen verschiedenen Formaten oder sogar demselben arbeiten.

All diese Workflows können dann über das layline.io Configuration Center in den hoch belastbaren und skalierbaren layline.io Reactive Cluster deployt werden. Und genau dort findet die Magie statt.

Mehr ...

Es gibt hier noch viel mehr zu erzählen. Zum Beispiel, was ist layline.io überhaupt? In den untenstehenden Links können Sie mehr über layline.io erfahren und auch tiefer in die Dokumentation eintauchen, wie diese spezielle Grammatik in layline.io funktioniert.

Sie können sich auch eine kostenlose Kopie von layline.io von unserer Website herunterladen und mit einem Beispielprojekt arbeiten, das Sie ebenfalls herunterladen können.

Weitere Informationen finden Sie in der Dokumentation oder kontaktieren Sie uns einfach unter hello@layline.io.

Vielen Dank für die Lektüre!

Ressourcen

#Ressource
1Dokumentation: Erste Schritte
2Dokumentation: Generic Format Asset?
3Beispielprojekt: Ausgabe in Kafka