Entwicklung eines SDK-basierten Syncler-Systems

Mit dem Konnektor „SDK“ können Sie in Syncler eigene Systeme per C#-Skripten implementieren. Die Skripte werden zur Laufzeit kompiliert und ausgeführt und bilden die Brücke zu externen Diensten/Apps. Aus fertig konfigurierten SDK-Systemen lassen sich System-Vorlagen erstellen, die wiederverwendbar sind und zentral aktualisiert werden können.


Architektur & Bausteine

Ein SDK-System besteht aus bis zu vier voneinander unabhängigen Skripten:

  1. Schema-Skript – beschreibt die verfügbaren Schemaobjekte (Tabellen/Entitäten) als JSON-Schema.
  2. Lese-Skript – liefert Daten für ein Schemaobjekt (inkl. Delta-/Seitenabruf).
  3. Schreib-Skript – schreibt/aktualisiert/löscht Datensätze für ein Schemaobjekt.
  4. Abfrage-Skript – führt freie Abfragen (Query) aus und liefert Schema + Daten.

Die Skripte teilen keine gemeinsame Codebasis. Gemeinsamer Kontext ist der SDK-Helper (siehe unten), der System-/Sync-Parameter, Paging-Infos, Logging-Methoden und Utility-Funktionen bereitstellt.

Unterstützte Libraries

  • .NET-Basis (eingeschränkt, je nach Cloud/On-Prem).
  • Newtonsoft.Json (JObject, JArray, Serialisierung) – vom Helper ebenfalls genutzt.
  • Eigene Entwicklung in Visual Studio ist möglich; binden Sie dafür SynclerPublic.dll ein und übertragen Sie den Code anschließend in die Oberfläche.

Datenformat

  • Ein- und Ausgaben erfolgen durchgängig als JSON (Strings).
  • Skripte liefern i. d. R. JArray von JObject (als String).

Der SDK-Helper (Kurzüberblick)

Der Helper steht in jedem Skript zur Verfügung und stellt u. a. bereit:

  • Kontext-/Steuerwerte: Helper.TargetObject, Helper.Page, Helper.Statement, Helper.DeleteSetObject, …
  • Objektzugriffe: Helper.GetObject (Quelle im Datensatzskript), Helper.SetObject / Helper.SetObjectChanges (Ziel im Schreibskript), Helper.SourceSchemaObject / Helper.TargetSchemaObject.
  • Parameter-Store: GetParam(...), SetParam(...) (serialisierbar; über Skriptaufrufe/Paging hinweg).
  • HTTP-Call: InvokeUrl(url, method, header, data, base64, contentType).
  • Logging: InsertMessage(text, level) und InsertLog(text, level) inkl. Überladung mit Objekt (z. B. ScriptLog).

Eine ausführliche Auflistung finden Sie in der separaten SDK-Helper-Dokumentation.


1) Schema-Skript

Das Schema-Skript liefert ein JSON-Array von Schemaobjekten (als String). Die Notation orientiert sich an JSON-Schema (https://json-schema.org/).

Objekteigenschaften

  • title (string, Pflicht) – eindeutiger Name des Objekts.
  • uniqueidentifier (string[]) – Namen der ID-Felder (auf oberster Ebene).
  • properties (object, Pflicht) – Felddefinitionen (siehe unten).
  • required (string[]) – Pflichtfelder des Objekts.
  • updated (string) – Feldname für „Änderungsinformation“ (Delta-Feld).

Feldeigenschaften (Auszug)

  • type: string (Default), number, integer, boolean, object, array
    • object → Objektgruppe (kein Feld);
    • array → Liste/Objektgruppe (items = Schema des Kindelements, optional ischildlist für Positionslisten).
  • title – Anzeigename.
  • description – Hilfetext (u. a. in Feldzuordnung sichtbar).
  • readOnly – Feld ist schreibgeschützt.
  • format – z. B. date-time, date, object (JSON im String), array (JSON-Array im String).
  • maxLength – maximale Länge für Strings.
  • enum – Werteliste für Auswahldaten.

Beispiel

[
  {
    "title": "address",
    "properties": {
      "street": {
        "title": "Street",
        "type": "string",
        "maxLength": 60,
        "description": "Hint for user"
      },
      "housenumber": {
        "type": "integer",
        "title": "Housenumber"
      },
      "addressid": {
        "type": "integer",
        "title": "Record ID",
        "readOnly": true
      },
      "updateddate": {
        "title": "Updated at",
        "type": "string",
        "format": "date-time",
        "description": "Last updated date"
      },
      "country": {
        "title": "Land",
        "type": "string",
        "enum": ["DE", "AT", "CH"]
      },
      "geo": {
        "title": "Coordinates",
        "type": "object",
        "properties": {
          "lat": { "title": "Latitude", "type": "number" },
          "lng": { "title": "Longitude", "type": "number" }
        }
      },
      "floors": {
        "title": "Floors",
        "type": "array",
        "ischildlist": false,
        "items": {
          "title": "Floors",
          "type": "object",
          "properties": {
            "appartments": { "title": "Appartments", "type": "number" },
            "renter": { "title": "Renter", "type": "number" }
          }
        }
      },
      "colors": {
        "title": "Colors",
        "type": "array"
      }
    },
    "required": ["street"],
    "updated": "updateddate",
    "uniqueidentifier": ["addressid"]
  }
]

Hinweis – Nachverarbeitung & OAuth

Nach Ausführung des Schema-Skripts wird das System intern erneut gespeichert. Hier können Sie z. B. OAuth-Tokens initialisieren/aktualisieren:

string oAuthCode = Helper.GetParam("OAuthCode");
string authorizationHeader = Helper.GetParam("OAuthClientId") + ":" + Helper.GetParam("OAuthSecret");
// ... Token holen/erneuern ...
string refreshToken = TokenObject["refresh_token"].ToString();
Helper.SaveParameter(new SisParam { Name = "MyRefreshToken", Value = refreshToken });
// Code zurücksetzen
Helper.SetParam("OAuthCode", "");

2) Lese-Skript

Das Lese-Skript liefert passend zu Helper.TargetObject Datensätze als JArray (String). Wichtige Parameter:

  • Helper.TargetObjectSchemaobjektname der Anforderung.
  • Helper.SourceSchemaObject – Schema als JObject (optional nutzbar).
  • Delta/Paging:
    • Helper.Page – aktuelle Seite (0-basiert).
    • Helper.GetParam("LAST_SYNC_DATE") / Helper.GetParam("LAST_SYNC_VERSION") – Grenzwerte.
    • Max. 50 Page-Aufrufe je Anfrage.
  • Einzel/Liste:
    • Helper.GetParam("GETDATA_ID") – Einzelabruf.
    • Helper.GetParam("GETDATA_WHERE") – Filterbedingung.

Persistente Laufzeit-Daten (über Paging hinweg)

Nutzen Sie GetParam/SetParam mit serialisierbaren Typen (z. B. JArray) um prozessweite Daten (z. B. Duplikatlisten) zu halten:

if (string.IsNullOrEmpty(Helper.GetParam("COMPANY_ID")))
    Helper.SetParam("COMPANY_ID", new JArray());

JArray companyIdList = JArray.Parse(Helper.GetParam("COMPANY_ID"));
// ... bearbeiten ...
Helper.SetParam("COMPANY_ID", companyIdList);

Minimalbeispiel (vereinfacht)

public class MyReader
{
    public SisHelper Helper { get; set; }

    public string Execute()
    {
        var result = new JArray();

        string obj = Helper.TargetObject; // z. B. "address"
        int page = Helper.Page;           // 0,1,2,...

        string id = Helper.GetParam("GETDATA_ID");             // optional
        string where = Helper.GetParam("GETDATA_WHERE");       // optional
        DateTime? from = Helper.GetParamOrNull<DateTime>("LAST_SYNC_DATE");

        // 1) URL/Query zusammenstellen (inkl. Paging/Delta)
        // 2) Remote-Service abrufen (Helper.InvokeUrl)
        // 3) Response in result (JArray<JObject>) schreiben

        return result.ToString();
    }
}

3) Schreib-Skript

Das Schreib-Skript erhält das Zielobjekt als Helper.SetObject (vollständig) und – sofern vorhanden – tatsächliche Änderungen als Helper.SetObjectChanges (reduziert). Das Ziel-Schema steht in Helper.TargetSchemaObject. Löschanforderungen erkennen Sie an Helper.DeleteSetObject == true.

Wichtig: Syncler erwartet als Rückgabe das gespeicherte Objekt (inkl. ID und neuer Änderungsinfo). Nur so können Datensatz-Abbildungen/Delta korrekt funktionieren.

Minimalbeispiel (vereinfacht)

public class MyWriter
{
    public SisHelper Helper { get; set; }

    public string Execute()
    {
        var target = (JObject)Helper.SetObject ?? new JObject();
        var changes = (JObject)Helper.SetObjectChanges; // kann null sein
        bool delete = Helper.DeleteSetObject;

        if (delete)
        {
            // DELETE auf Remote-System
            // ...
            // Rückgabe minimal mit ID
            return new JObject { ["id"] = target["id"] }.ToString();
        }
        else
        {
            // INSERT/UPDATE auf Remote-System (bevorzugt changes verwenden)
            // ...
            // Antwort des Systems (inkl. ID/Updated) zurückgeben:
            var saved = new JObject(target)
            {
                ["id"] = target["id"] ?? "12345",
                ["updated"] = DateTime.UtcNow.ToString("o")
            };
            return saved.ToString();
        }
    }
}

4) Abfrage-Skript (Query)

Das Abfrage-Skript wird sowohl für Schema- als auch für Datenabruf genutzt. Unterscheidung z. B. über:

bool isSchemaRequest = Helper.GetParam<bool>("GetQuerySchema");
  • Statement: Helper.Statement enthält das Abfrage-Statement (frei definierbar; kann auch CSV/JSON beschreiben).
  • Daten-Antwort: JArray flacher JObject-Zeilen (als String).
  • Schema-Antwort: JArray von Spaltendefinitionen.

Beispiel – Schemaantwort

JArray schemaColumns = new JArray
{
    new JObject { ["Name"] = "referenceType",  ["ColumnType"] = "String"   },
    new JObject { ["Name"] = "trackingNumber", ["ColumnType"] = "String"   },
    new JObject { ["Name"] = "eta",            ["ColumnType"] = "Datetime" },
    new JObject { ["Name"] = "vesselName",     ["ColumnType"] = "String"   }
};

return schemaColumns.ToString();

Nachrichten & Protokolle

Zur Nachvollziehbarkeit sollten Sie Meldungen (flüchtig) und Protokolle (persistiert) setzen:

// UI-Nachricht (Broadcast, begrenzter Stack)
Helper.InsertMessage("Start page 3", 5); // 1=Error, 2=Warning, 3=Message, 4=Feedback, 5=Debug

// Persistentes Protokoll
Helper.InsertLog("Wrote 50 records", 3);

// Objekt-Variante (z. B. ScriptLog mit Record-Bezug)
Helper.InsertLog(new ScriptLog { RecordType = "address", RecordId = "A-123", LogMessage = "Updated" });

Hinweis: Fehler-Logs führen automatisch zu fehlgeschlagenen Ausführungen im Sync.


Externe Services aufrufen

Nutzen Sie InvokeUrl(...) für HTTP-Aufrufe:

// GET mit Header
var headers = new JObject { ["Authorization"] = "Bearer " + Helper.GetParam("AccessToken") };
string json = Helper.InvokeUrl("https://api.example.com/items?page=1", "GET", headers, null);

// POST mit JSON-Body
var payload = new JObject { ["name"] = "Test", ["active"] = true };
string resp = Helper.InvokeUrl(
    "https://api.example.com/items",
    "POST",
    headers,
    payload.ToString(),
    false,
    "application/json"
);

// Binär als Base64 holen
string pdfBase64 = Helper.InvokeUrl("https://api.example.com/doc.pdf", "GET", null, null, true, "application/pdf");

Parameter:
InvokeUrl(string url, string method, JObject? header, string data, bool base64 = false, string contentType = "application/json")


Best Practices

  • Delta & Paging: Definieren Sie im Schema ein updated-Feld und nutzen Sie LAST_SYNC_DATE / LAST_SYNC_VERSION. Paginieren Sie mit Helper.Page.
  • Serialisierbare Param-Daten: Für prozessweite Zustände (z. B. Duplikatlisten) JArray/JObject statt .NET-Listen verwenden.
  • Fehlerrobustheit: Exceptions stets loggen (InsertLog) und aussagekräftige Meldungen setzen.
  • Rückgaben vollständig: Schreib-Skript muss ID + Änderungsinfo zurückgeben, sonst funktionieren Datensatz-Abbildungen/Delta nicht.

Kurzer Implementierungs-Workflow

  1. SDK-System anlegen und Parameter (z. B. Endpunkte, Keys) definieren.
  2. Schema-Skript erstellen und testen (Objekte, IDs, updated).
  3. Lese-/Schreib-Skripte umsetzen (Delta, Paging, Changes, Delete).
  4. Abfrage-Skript (optional) für besondere Reports/Lookups.
  5. Sync(s) konfigurieren: Quelle/Ziel, Feldzuordnung, Transformation, Konfliktverhalten.
  6. Testlauf (manuell/Adhoc), Protokolle prüfen, Grenzwerte setzen.
  7. Vorlage erzeugen (optional, System-Template), um Deployments zu standardisieren.