Das ist eine für den Ausdruck optimierte Ansicht des gesamten Kapitels inkl. Unterseiten.
Druckvorgang starten.
Zur Standardansicht zurückkehren.
Architektur im Detail
Technische Detaildokumentation für Entwickler: Systemkontext, Bicep-Infrastruktur, Anwendungsaufbau, HTTP- und Nachrichten-Schnittstellen, Datenflüsse, Abhängigkeiten und Fähigkeiten-Katalog.
Dieser Abschnitt beschreibt die dsyr.DataBridge so detailliert, dass sie ohne Lesen des
Quellcodes verstanden werden kann. Er richtet sich an Entwickler, die Connectoren,
Resolver oder Services erweitern, und an technische Reviewer.
Die Übersicht für Administratoren steht unter
Dokumentation → Überblick → Architektur. Dort sind
C4-Diagramme, Queue-Struktur und Connector-Hierarchie beschrieben. Die Seiten hier setzen
darauf auf und gehen eine Ebene tiefer: Sequenzdiagramme, Bicep-Module, DI-Verdrahtung,
Request- und Nachrichten-Schemata.
| Seite | Inhalt |
|---|
| Systemkontext und Prinzipien | Zweck, Hauptfunktionen, Architekturprinzipien, Phasen, Betriebs- und Skalierungsmodell |
| Infrastruktur (Bicep) | Module, Ressourcen, App-Settings, Rollen, Alerts, Marketplace-Artefakte |
| Anwendungsarchitektur | Projekte, DI, host.json, Functions, Services, Connector-Basisklassen, Transformations-Pipeline, Domänenobjekte |
| API- und Nachrichten-Referenz | HTTP-Endpunkte mit Request/Response, Trigger-Bindungen, Service-Bus-Nachrichtenschema, Fehlerverhalten |
| Datenflüsse | Sequenzdiagramme für Erkennung, Verarbeitung, Response-Jobs, Fehlerpfad; Persistenz; Watermark-Logik |
| Abhängigkeiten | Projekt- und Azure-Abhängigkeiten, externe Systeme, architekturkritische Kopplungen |
| Fähigkeiten-Katalog | Alle Fähigkeiten mit ihrer technischen Umsetzung |
Stand
Die Inhalte entsprechen dem Code-Stand von Version 2.3. Quellen sind src/, bicep/
und cicd/ im Repository. Wo eine Aussage eine Annahme ist, steht das im Text.
1 - Systemkontext und Prinzipien
Zweck, Hauptfunktionen, Architekturprinzipien, die zwei Verarbeitungsphasen sowie Betriebs- und Skalierungsmodell der dsyr.DataBridge.
Zweck
Die dsyr.DataBridge ist eine serverlose Datenintegrations-Bridge auf Azure Functions
(.NET 10, Isolated Worker). Sie synchronisiert Datensätze nahezu in Echtzeit zwischen
heterogenen Geschäftssystemen, ohne diese direkt zu koppeln. Quelle und Ziel sind über
Azure Service Bus entkoppelt, Änderungen werden über Watermarks change-getrieben
übertragen.
Die Bridge hält keine Fachdaten dauerhaft. Sie speichert nur Konfiguration und
Synchronisationsstände in Azure Table Storage; Nachrichten liegen transient im Service Bus.
Hauptfunktionen
| Funktion | Beschreibung |
|---|
| Change Detection | Periodisches Pollen der Quellsysteme (Timer GetChanges, optional Durable-Orchestrierung) |
| Asynchrone Entkopplung | Erkannte Änderungen landen als Nachrichten in priorisierten Queues (Low / Standard / High) |
| Feld-Mapping und Transformation | Konfigurierbare Zuordnung inkl. Typ-Coercion, Default-Werten, Option-Mapping, Lookups, Source-/Target-Operationen |
| Multi-System-Konnektivität | D365 BC, CE/Dataverse, NAV, generische REST-APIs über OpenAPI-Spezifikation, SQL-Datenbanken (MSSQL, PostgreSQL, MySQL, Oracle) |
| Idempotente Zielverarbeitung | Upsert/Delete über Primär-, Custom- oder Filter-Keys, mehrere Schlüssel mit Rangfolge |
| Aktions-Jobs | InvokeAction ruft benannte Nicht-CRUD-Endpunkte auf (OpenAPI operationId, CE OrganizationRequest) |
| Watermarking | Persistierter Stand pro Job, nur neue Änderungen werden übertragen |
| Response-Jobs | Rückschreiben von im Ziel erzeugten Schlüsseln in die Quelle |
| Diagnose und KI-Anbindung | HTTP-Endpunkte (CheckList, Validate, JobApi, MappingApi) und ein read-only MCP-Server |
| Selektive Telemetrie | Gepuffertes Logging pro Invocation, Dependency-Sampling, pre-aggregierte Custom Metrics |
| Fehlerresilienz | Backoff-Retry, Transitive-Queues, Dead-Letter-Queues, Alerts |
Zielgruppen
| Zielgruppe | Nutzen |
|---|
| Integrations-/ERP-Berater | Konfigurieren Connections, Jobs und Mappings in Table Storage, ohne Code zu deployen |
| Cloud-/DevOps-Engineers | Deployen via Bicep oder Marketplace, betreiben und überwachen die Function App |
| Entwickler | Erweitern Connectoren, Resolver und Source-Operationen in DataBridge.Core |
| KI-Assistenten | Lesen Konfiguration und Queue-Zustand über den MCP-Server |
Architekturprinzipien
| Prinzip | Umsetzung |
|---|
| Separation of Concerns | Host (Trigger) ↔ Core (Logik) ↔ Connectoren (System-Anbindung) ↔ Resolver/Mapper (Transformation) |
| Configuration over Code | Connections, Jobs, Mappings, Watermarks liegen in Table Storage |
| Lazy Initialization | Services sind DI-Singletons; die Konfiguration wird einmalig und thread-sicher beim ersten Aufruf geladen |
| Asynchrone Entkopplung | Service Bus zwischen Lese- und Schreibseite; jede Nachricht wird isoliert verarbeitet |
| Idempotenz | Upsert/Delete über definierte Target-Keys |
| Resilienz | Backoff, Lock-Renewal, Transitive-Queues, Dead-Letter-Queues, DeadLetterMover |
| Erweiterbarkeit | Source-Operation-Handler werden per Reflection registriert; Connectoren über ConnectionTypes gemappt; Resolver-Registries je Connector als statische FrozenDictionary |
| Observability first | Gepuffertes Logging, Requests und Exceptions nie gesampelt, Custom Metrics ohne Sampling |
Die zwei Phasen
flowchart LR
SRC["Quellsystem"] -->|"1. Erkennung (Pull)"| FUNC1["GetChanges<br/>oder Orchestrator + Activity"]
FUNC1 -->|"QueueMessage"| SB[("Service Bus<br/>Low / Standard / High")]
SB -->|"2. Verarbeitung (Push)"| FUNC2["Process*QueueItems"]
FUNC2 -->|"Upsert / Delete / Action"| TGT["Zielsystem"]
FUNC1 -.->|"Watermark"| WM[("watermark")]
FUNC2 -.->|"Response-Job / Transitive / Ignored"| SB- Erkennung:
GetChanges (Timer) liest Änderungen aus den Quellsystemen und
enqueued sie. Die Durable-Orchestrierung ist eine Alternative, die nur per HTTP
gestartet wird und im Regelbetrieb nicht läuft. - Verarbeitung: Drei Service-Bus-Trigger (je Priorität) dequeuen und schreiben ins
Zielsystem.
Diese Entkopplung erlaubt unabhängige Skalierung beider Seiten und überbrückt temporäre
Ausfälle des Zielsystems. Details in Datenflüsse.
Serverless-Modell
- Azure Functions v4, .NET 10, Isolated Worker (
FUNCTIONS_WORKER_RUNTIME=dotnet-isolated). - App Service Plan Y1 (Consumption, Windows) als Standard, optional EP1–EP3 über den
Bicep-Parameter
appServiceSku. functionAppScaleLimit begrenzt die Instanzen (Default 12).- Durable Functions mit Azure-Storage-Provider (
DurableFunctionsHub). - Auslieferung als Run-from-Package-Zip; alternativ als Container-Image (siehe
Container-Betrieb).
Betriebsmodell
- Trigger-gesteuert: Timer, Service Bus, HTTP, Durable.
- Single-Message-Processing:
autoCompleteMessages = false, manuelles Complete/Abandon;
maxConcurrentCalls = 12 je Instanz. - Function-Timeout: 10 Minuten (
host.json). - Einzelne Functions abschaltbar über
AzureWebJobs.<Name>.Disabled; Bicep setzt das
über den Parameter disabledFunctions. - Konfigurations-Cache pro Prozess: Änderungen an den Tabellen greifen erst nach
einem Neustart der Function App.
Skalierungsmodell
| Stellschraube | Wirkung |
|---|
appServiceSku | Consumption vs. Premium (kein Cold Start, mehr RAM/CPU) |
functionScaleLimit | Maximale parallele Instanzen |
maxConcurrentCalls (host.json) | Parallel verarbeitete Nachrichten je Instanz |
| Priorisierte Queues | Drei getrennte Trigger für unabhängige Lastpfade |
maxPageSize in der Connection, MaxBatchSize in der Activity | Seitengröße beim Abruf |
Order und Priority am Job | Reihenfolge und Priorität |
Service Bus Basic
Die SKU Basic unterstützt keine Topics, Sessions, Partitionierung und keine Duplicate
Detection. Die Architektur nutzt ausschließlich Queues. Ein Wechsel auf Topics oder Sessions
würde ein SKU-Upgrade und Code-Änderungen erfordern.
Abgrenzung
- Keine eigene Benutzeroberfläche. Konfiguration erfolgt in Table Storage, Diagnose über
HTTP-Endpunkte und MCP.
- Kein ETL- oder Data-Warehouse-Werkzeug für Massenläufe, sondern inkrementelle Synchronisation.
- Transaktionen und fachliche Konsistenz bleiben in den angebundenen Systemen.
2 - Infrastruktur (Bicep)
Aufbau des Bicep-Deployments: Module und Parameterfluss, Ressourcen, App-Settings der Function App, Managed Identity und Rollen, Alerts, Marketplace-Artefakte.
Einstiegspunkt ist bicep/main.bicep. Das Deployment läuft im Scope einer bestehenden
Resource Group (Bicep-Default, kein explizites targetScope), im Modus Incremental.
Die Parameter aus Anwendersicht sind unter
Installation beschrieben; hier geht es um den inneren
Aufbau.
Module und Parameterfluss
flowchart TD
MAIN["main.bicep"]
SA["storageaccount.bicep"]
AI["applicationinsights.bicep"]
ASP["appserviceplan.bicep"]
SB["servicebus.bicep"]
ALERT["metricalerts.bicep<br/>if enableAlerts"]
DASH["dashboard.bicep<br/>if enableGrafanaDashboard"]
FUNC["azurefunction.bicep"]
ROLES["roleassignments.bicep<br/>if enableManagedIdentity"]
MAIN --> SA & AI & ASP & SB & ALERT & DASH & FUNC & ROLES
SA -->|"id, name"| FUNC
AI -->|"connectionString, id"| FUNC
AI -->|"id"| DASH
ASP -->|"id"| FUNC
SB -->|"authorizationRuleId, name"| FUNC
SB -->|"id"| ALERT
FUNC -->|"principalId"| ROLES
SA -->|"id"| ROLES
SB -->|"id"| ROLESDie Abhängigkeiten ergeben sich über Modul-Outputs, es gibt keine Zyklen. Zusätzlich legt
main.bicep ein leeres Tracking-Deployment für die Microsoft-Partner-Attribution an.
Ressourcen
| Ressource | Typ | SKU / Eigenschaften | Modul |
|---|
Storage Account st<name> | Microsoft.Storage/storageAccounts | StorageV2, Standard_LRS, TLS 1.2 | storageaccount |
| Table Service mit vier Tabellen | …/tableServices/tables | connections, jobs, mappings, watermark | storageaccount |
Application Insights appi<name> | Microsoft.Insights/components | Retention 90 Tage, klassisch (ohne eigenen Log-Analytics-Workspace) | applicationinsights |
App Service Plan asp<name> | Microsoft.Web/serverfarms | Y1 (capacity 0) oder EP1–EP3, Windows | appserviceplan |
Service Bus Namespace sb<name> | Microsoft.ServiceBus/namespaces | Basic | servicebus |
| Sieben Queues | …/namespaces/queues | siehe unten | servicebus |
Authorization Rule sbexportFunction | …/authorizationRules | Send + Listen | servicebus |
Function App func<name> | Microsoft.Web/sites | kind functionapp, FTPS only, netFrameworkVersion 10.0, System-Identity immer aktiv | azurefunction |
| Grafana-Dashboard | Microsoft.Dashboard/dashboards | eingebettete JSON-Definition | dashboard |
| Action Group + fünf Metric Alerts | Microsoft.Insights/actionGroups, metricAlerts | nur mit enableAlerts | metricalerts, dlqmetricalert |
| Rollenzuweisungen | Microsoft.Authorization/roleAssignments | nur mit enableManagedIdentity | roleassignments |
Queues
Alle Queues haben deadLetteringOnMessageExpiration = true, TTL defaultMessageTimeToLive
(Default P14D) und lockDuration (Default PT30S).
| Queue | Zweck | MaxDeliveryCount |
|---|
sbqhighexport, sbqexport, sbqlowexport | Verarbeitung nach Priorität | maxDeliveryCount (Default 3) |
sbqtransitivehigh, sbqtransitivestandart, sbqtransitivelow | Ablage bei Konflikten im Ziel (HTTP 409); kein Trigger | maxDeliveryCount × 2 |
sbqignored | Veraltete Nachrichten bei SkipOutdatedMessages; kein Trigger | maxDeliveryCount |
Die Schreibweise standart ist in Code und Bicep konsistent und darf nicht geändert werden,
weil bestehende Deployments die Queue unter diesem Namen besitzen.
App-Settings der Function App
| Setting | Quelle |
|---|
APPLICATIONINSIGHTS_CONNECTION_STRING | Output des AI-Moduls |
AzureWebJobsStorage bzw. AzureWebJobsStorage__accountName | Key-basiert per listKeys() oder identitätsbasiert, je nach enableManagedIdentity |
ExportQueueServiceBus bzw. ExportQueueServiceBus__fullyQualifiedNamespace | analog für den Service Bus |
TARGET_BASED_SCALING_ENABLED | 0, nur im schlüsselbasierten Modus (ohne enableManagedIdentity) |
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING | immer key-basiert (Content Share) |
FUNCTIONS_EXTENSION_VERSION, FUNCTIONS_WORKER_RUNTIME | ~4, dotnet-isolated |
WEBSITE_RUN_FROM_PACKAGE | 1 |
GetChangesTimerExpression | Parameter, Default */15 * * * * * |
SkipOutdatedMessages | Parameter, Default false |
TELEMETRY_CONTROL, TELEMETRY_INFO_SAMPLE_RATE, TELEMETRY_DEPENDENCY_SAMPLE_RATE, TELEMETRY_DEPENDENCY_SLOW_MS | Parameter, siehe Logging |
AzureWebJobs.<Name>.Disabled | aus dem Array disabledFunctions |
alwaysOn wird nicht gesetzt. Für Premium-Pläne kann das manuell nachgezogen werden.
Managed Identity und Rollen
Die System-Identity der Function App wird immer angelegt. Mit
enableManagedIdentity = true entfallen die Schlüssel in den App-Settings, stattdessen
weist roleassignments.bicep folgende Rollen zu:
| Rolle | Ressource | Grund |
|---|
| Storage Table Data Contributor | Storage Account | Konfigurationstabellen, Durable-Tabellen |
| Storage Blob Data Owner | Storage Account | Durable-Hub-Blobs |
| Storage Queue Data Contributor | Storage Account | Durable-Hub-Queues |
| Azure Service Bus Data Sender | Namespace | Enqueue |
| Azure Service Bus Data Receiver | Namespace | Dequeue |
| Azure Service Bus Data Owner | Namespace | Queue-Laufzeitdaten für die MCP-Tools list_queue_statuses und get_queue_status |
SettingsService liefert Connection-Strings tolerant (null statt Exception);
StorageService und ServiceBusService bauen ihre Clients aus dem Connection-String, wenn
vorhanden, sonst über DefaultAzureCredential.
Alerts
Mit enableAlerts entstehen eine Action Group (E-Mail an notificationEmail), vier
DLQ-Alerts auf DeadletteredMessages (Info ≥ 1, Warning > 50, Error > 200, Critical > 500,
Schwellen parametrisierbar) und ein Queue-Längen-Alert auf ActiveMessages > 300000. Alle
Alerts sind namespace-weit (EntityName = *). Anwendersicht:
Monitoring & Alerts.
Netzwerk
Es gibt keine VNet-Integration, keine Private Endpoints und keine IP-Restriktionen.
Storage, Service Bus und Function App sind öffentlich erreichbar; Transport ist TLS 1.2
bzw. HTTPS. Netzwerk-Isolation wäre eine Erweiterung des Bicep-Deployments.
Parameterdateien
bicep/params/databridge.sample.bicepparam ist die Vorlage mit allen Parametern.
Kundenspezifische Parameterdateien liegen daneben im Repository und werden von
cicd/manage/deploy_bicep.yml verwendet. storageKind erlaubt nur StorageV2; ältere
Parameterdateien mit Storage schlagen an der Validierung fehl.
Marketplace-Artefakte
bicep/marketplace/mainTemplate.json wird mit GenerateMarketplace.sh aus main.bicep
erzeugt und muss nach jeder Bicep-Änderung neu generiert und committet werden.createUiDefinition.json ist Handarbeit und bildet die Parameter auf den
Marketplace-Assistenten ab.
CI/CD
cicd/manage/build_and_deploy_functionapp.yml baut das Zip, setzt die Version
(VersionMajor.VersionMinor.yyyy.MMdd) und veröffentlicht ein globales Artefakt.cicd/manage/deploy_bicep.yml rollt Infrastruktur und Zip je Kunde aus.cicd/manage/build_docfx_and_publish.yml baut und veröffentlicht diese Dokumentation
(der Dateiname stammt noch aus der DocFX-Zeit).
3 - Anwendungsarchitektur
Projekte, Dependency Injection, host.json, Functions, Service-Schicht, Connector-Basisklassen, Transformations-Pipeline und Domänenobjekte.
Projekte (src/DataBridge.slnx)
| Projekt | Typ | Zweck |
|---|
DataBridge | Azure Functions, net10.0, isolated | Host: Trigger, DI, Telemetrie-Bootstrap, Durable Functions, MCP-Endpunkt |
DataBridge.Core | Class Library | Services, Connectoren, Resolver, Mapper, Source-Operationen, Modelle, Telemetrie |
DataBridge.Test | xUnit v3 + Moq (Microsoft Testing Platform) | Unit-Tests; Ordnerstruktur spiegelt DataBridge.Core |
DeadLetterMover | Konsolenanwendung | Requeue von Dead-Letter-Nachrichten |
Dependency Injection (Program.cs)
FunctionsApplication.CreateBuilder(args) (moderner Einstiegspunkt statt new HostBuilder()),
mit builder.UseMiddleware<InvocationContextMiddleware>(), die jede Invocation in einen
InvocationContext einbettet und am Ende das gepufferte Log flusht.- Application Insights über
AddApplicationInsightsTelemetryWorkerService, Adaptive
Sampling deaktiviert; DependencySamplingProcessor per
AddApplicationInsightsTelemetryProcessor. Der Standard-AI-Logger-Provider ist auf
LogLevel.None gesetzt, einziger Pfad nach AI ist der BufferingLoggerProvider. - Alle Services sind Singletons:
MetricsService, SettingsService, StorageService,
ConnectionService, MappingService, JobService, WatermarkService,
ServiceBusService, ProcessQueueMessageService,
ProcessJobsForRetrievalInOrderService, CheckListService, InitializationService,
McpToolService. - Log-Level: Kategorien
DataBridge und Function auf Trace, der Puffer-Provider
filtert auf Debug.
host.json
| Bereich | Einstellung | Wert |
|---|
| Allgemein | functionTimeout | 10 min |
| AI-Sampling | excludedTypes | Request;Exception |
| Durable | hubName | DurableFunctionsHub, Azure-Storage-Provider, Distributed Tracing V2 |
| Storage Queues | batchSize / maxDequeueCount | 1 / 5 (nur für Durable relevant) |
| Service Bus | autoCompleteMessages | false |
| Service Bus | maxConcurrentCalls | 12 |
| Service Bus | maxAutoLockRenewalDuration | 5 min |
Functions
| Function | Trigger | Aufgabe |
|---|
GetChanges | Timer %GetChangesTimerExpression% | Initialisiert thread-sicher über InitializationService und ruft ProcessJobsForRetrievalInOrderService.Execute auf |
ProcessStandardQueueItems, ProcessHighPriorityQueueItems, ProcessLowPriorityQueueItems | Service Bus sbqexport, sbqhighexport, sbqlowexport | Verarbeiten je eine Nachricht end-to-end über ProcessQueueMessageService.Execute |
ExecuteJob | HTTP POST | Stößt einen Job per Name über ProcessJobsForRetrievalInOrderService.ExecuteByJobName an |
ExecuteHttpRequest | HTTP POST | Ad-hoc-HTTP-GET mit eigenem HttpClient (NTLM oder anonym), unabhängig von den Connectoren |
GetInfo | HTTP GET | Assembly-Version |
CheckList | HTTP GET | InitializationService.CheckAsync(): Mappings gegen Live-Metadaten der Systeme |
Validate | HTTP GET | Erzwingt die Initialisierung, liefert das Fehlerlog |
GetJobs, GetJobByRowKey | HTTP GET job, job/{rowKey} | Geladene Job-Konfiguration |
GetMappings, GetMappingByRowKey | HTTP GET mapping, mapping/{name} | Geladene Mappings, Suche per Name mit Fallback auf RowKey |
Mcp, McpGet | HTTP POST/GET mcp | MCP-Server; GET antwortet mit 405, weil SSE-Streams nicht unterstützt werden |
StartGetChangesOrchestration, GetChangesOrchestrator, GetChangesActivity, JobState | HTTP, Orchestration, Activity, Entity | Optionale Durable-Variante der Erkennung |
Details zu Request und Response: API-Referenz.
Besonderheiten der Trigger
- Einmal-Initialisierung:
GetChanges, ProcessQueueItems, JobApi, MappingApi und
McpToolService halten je eine eigene statische InitializationGuard-Instanz
(DataBridge.Core/Services/Initialization/InitializationGuard.cs) — sie kapselt das
Lock+Flag-Muster, das vorher in jeder Klasse dupliziert war. Bewusst nicht geteilt:
ein Fehler in einer Function zwingt keine der anderen zur Neu-Prüfung. Bei Fehlern wird
InitializationService.ResetInitialization() gerufen und beim nächsten Aufruf neu
initialisiert (Self-Repair). GetChanges fängt dafür zusätzlich EndpointNotFoundException
und CommunicationException. - Lock-Renewal:
ProcessQueueItems erneuert den Service-Bus-Lock in einem
Hintergrund-Task alle 30 Sekunden, bis die Nachricht abgeschlossen ist. - Fehlerstempel: Bei Verarbeitungsfehlern werden
ErrorMessage, ErrorTimeStamp,
ErrorStackTrace und OperationId als Application Properties gesetzt und die Nachricht
abandoned. Nach maxDeliveryCount Versuchen landet sie in der Dead-Letter-Subqueue.
Durable-Variante
flowchart TD
HTTP["StartGetChangesOrchestration"] --> ORCH["GetChangesOrchestrator"]
ORCH -->|"je Job aus GetJobsToRun"| CHK{"JobState.InProgress?"}
CHK -->|ja| SKIP["überspringen"]
CHK -->|nein| ADD["JobState.Add"]
ADD --> LOOP["CallActivity(GetChangesActivity)"]
LOOP -->|HasMoreRecord| LOOP
LOOP -->|fertig| REM["JobState.Remove"]
ORCH -. Fehler .-> REMDie Activity liest seitenweise (MaxBatchSize = 3) und sendet jede Nachricht einzeln.
Jobs laufen sequentiell. Der Durable-Pfad schreibt die Watermark nicht zurück und ist
im Timer-Betrieb nicht aktiv.
Service-Schicht
InitializationService (bis v2.3 CoreService) ist der Einstieg. Er lädt beim ersten Aufruf
Storage → Connections → Mappings → Jobs und stellt nur noch den Ready/Error-Zustand bereit.
Er ist kein Service-Locator mehr: Functions, die z. B. ProcessJobsForRetrievalInOrderService
oder ProcessQueueMessageService brauchen, injizieren diese direkt statt über
InitializationService zu routen.
| Service | Verantwortung | Wichtige Methoden |
|---|
InitializationService | Initialisierung, Ready/Error-Zustand | EnsureInitializedAsync, CheckAsync, ResetInitialization, GetErrorLogs, HasError |
SettingsService | App-Settings, Connection-Strings | GetConnectionString mit Fallback-Kette ConnectionStrings: → SERVICEBUSCONNSTR_ → SQLCONNSTR_ → CUSTOMCONNSTR_ → App-Setting; GetStorageAccountName, GetServiceBusFullyQualifiedNamespace |
StorageService | Table Storage | ValidateAndInitialize, GetTableEntries<T>, UpdateTableEntry<T> (Upsert Merge, ohne ETag) |
ConnectionService | Connector je Connection | Initialize mit Verbindungstest, IsValidConnection, GetConnector |
MappingService | Mappings gruppiert nach Name | Initialize, GetMapping, ValidateMappings |
JobService | Jobs, Validierung, Cron | Initialize, GetJobsToRun (NCrontab, 6 Felder), GetJobByName |
WatermarkService | Persistenz des Stands | GetWatermarkByJobName, GetAllWatermarks (Bulk), UpdateWatermarkByJobName; Wertlogik in WatermarkValue |
ServiceBusService | Enqueue | SendMessageAsync, AddMessageOrSendAsync (Batch bis 200 oder Größenlimit), FlushBatch; Priorität → Queue |
ProcessJobsForRetrievalInOrderService | Erkennung | Execute (aktive Jobs nach Order), ExecuteByJobName, ExecuteJob |
ProcessQueueMessageService | Verarbeitung | Execute: Deserialisierung, Backoff, Upsert/Delete/Action, Response, Transitive, Ignored |
CheckListService | Diagnose | Check() gegen Live-Metadaten der Connectoren |
MetricsService | Custom Metrics | pre-aggregiert über GetMetric, kein Sampling |
McpToolService | MCP-Tool-Logik | read-only, siehe MCP-Server |
flowchart TB
INIT["InitializationService"] --> STORE["StorageService"] & CONN["ConnectionService"] & MAP["MappingService"] & JOB["JobService"] & CHK["CheckListService"]
GC["GetChanges/ExecuteJob"] --> INIT & PJRS["ProcessJobsForRetrievalInOrderService"]
PQI["ProcessQueueItems"] --> INIT & PQMS["ProcessQueueMessageService"]
PJRS --> JOB & MAP & CONN & WM["WatermarkService"] & SBS["ServiceBusService"]
PQMS --> JOB & MAP & CONN & SBS
JOB --> WM
WM --> STORE
CONN --> CONNECTORS["Connectoren"]
MCP["McpToolService"] --> INIT & JOB & MAP & CONN & SBSConnector-Basisklassen
Die Hierarchie ist unter Architektur → Connector-Hierarchie
abgebildet. Aufgaben der Basisklassen:
ConnectorBase<TRecord, TConnectionSetting>: Logger, ConnectionSetting,
Lookup-Cache (ConcurrentDictionary<ThreeStringKey,string>), Culture, generische
Serialize-Pipeline, Filter-Templating ({{…}}), optionale Key-Vault-Auflösung der
Settings über keyVaultUrl + DefaultAzureCredential, GetTableMetadata.
Vertragsmethoden: Connect, RetrieveChangesAsync, Upsert, Delete,
ProcessMappingSerialization, Key-Aufbau.HttpConnectorBase: HTTP-Client mit Kompression, Basic, OAuth2 Client Credentials
(konfigurierbare tokenUrl), API-Key oder Managed Identity (nur bei authmethod: ManagedIdentity,
Scope aus der Connection), Token-Refresh.SqlConnectorBase<TConnection>: typisierte DbCommand/DataAdapter, SELECT 1
als Verbindungstest, seitenweises SELECT mit ORDER BY watermarkField, Metadaten über
INFORMATION_SCHEMA (Oracle: USER_*/ALL_*), Schema-Cache. Upsert und Delete
werfen NotImplementedException; SQL ist nur Quelle.CeConnector: Dataverse ServiceClient, RetrieveEntityChangesRequest mit
DataVersion-Token, Batch-Pre-Caching von Lookups, EmbedChildren.OpenApiConnector: Pfade, Wrapper-Schlüssel und Typen aus der OpenAPI-Spezifikation,
pro Prozess gecacht.
ConnectionTypes | Connector |
|---|
D365CE | CeConnector |
D365BC | BcConnector |
D365NAV | NavConnector |
OPENAPI | OpenApiConnector |
MSSQL, PGSQL, MYSQL, ORACLE | MsSqlConnector, PostgreSqlConnector, MySqlConnector, OracleConnector |
flowchart LR
REC["Quell-Record<br/>JObject / DataRow / Entity"] --> KEY["Key-Aufbau → QueueMessage.Id"]
KEY --> FM{"SourceField<br/>beginnt mit '_'?"}
FM -->|ja| SOP["SourceOperation-Handler<br/>LookupValue, ReadFromMessage,<br/>ActivityParty, LinkToRecord, EmbedChildren"]
FM -->|nein| RES["TargetValueMapper → AttributeResolver<br/>Typ-Coercion nach Metadaten"]
SOP --> ATTR["QueueMessage.Attributes"]
RES --> ATTR- AttributeResolver (
Resolvers/Common, D365Bc, D365Ce, D365Nav): Typ-Coercion
auf String-Basis (Value + TypeName in der Nachricht). CE hat zusätzlich Picklist,
Lookup, Money, State/Status, MultiSelect. - TargetValueMapper (
Connectors/<X>/<X>TargetValueMapper.cs,
SqlTargetValueMapper<T>): wählt anhand des Ziel-Metadatentyps den Resolver. Registry
einmalig als FrozenDictionary; Coverage-Tests in DataBridge.Test machen fehlende
Registrierungen zum Build-Fehler. - Source-Operationen (
ISourceOperationHandler): per Reflection registriert,
Konstruktor (TConnector, ILogger). Felder mit _-Präfix sind virtuell.
Domänenobjekte
| Modell | Zweck |
|---|
ConnectionTableEntry | PartitionKey connection, RowKey = Name, Type, Json = ConnectionSetting |
JobTableEntry / Job | PartitionKey job; Source/Target-Connection, Order, Filter, Mapping, WatermarkType/WatermarkField, Schedule, Action, Priority, ResponseJob, Source/Target (job-eigene Pfade) |
MappingTableEntry / TableMapping + FieldMapping | Feldzuordnung, TargetKey/TargetKeys (mit KeyRank), SourceOperation, TargetOperation |
WatermarkTableEntry | PartitionKey watermark, RowKey = Job-Name, Watermark, LastCronRun |
QueueMessage + QueueMessageProperty | Transportobjekt, siehe Nachrichtenschema |
TableMetadata / ColumnMetadata | Schema-Informationen für MCP und CheckList |
| Enums | ConnectionTypes, JobActionType, JobPriorityType, TargetKeyType, LookupType, WatermarkType |
Querschnitt
- Telemetrie (
DataBridge.Core/Telemetry): BufferingLoggerProvider,
InvocationContext (AsyncLocal, operation_id), DependencySamplingProcessor,
MetricsService. Verhalten und Steuerung: Logging. - Konstanten (
DataBridge.Core/Constants): QueueNames, StorageTables,
MessageProperties, EnviromentVariables, Errors, MappingDefaultValues. - Exceptions:
TransitiveException steuert die Ablage in Transitive-Queues.
4 - API- und Nachrichten-Referenz
HTTP-Endpunkte mit Request und Response, Trigger-Bindungen, Schema der Service-Bus-Nachrichten und Fehlerverhalten.
Authentifizierung
Alle HTTP-Functions verwenden AuthorizationLevel.Function. Der Funktionsschlüssel wird
als Query-Parameter code oder Header x-functions-key übergeben. Es gibt keine
Easy-Auth- oder Entra-Konfiguration im Deployment. Timer-, Service-Bus- und
Durable-Trigger haben keinen HTTP-Endpunkt.
Routen ohne Route-Attribut folgen der Konvention /api/<FunctionName>.
HTTP-Endpunkte
GET /api/GetInfo
Liefert die Assembly-Version, keine Gesundheitsprüfung.
{ "Version": "2.3.2026.0926" }
POST /api/ExecuteJob
Stößt einen Job aus der Tabelle jobs manuell an (Erkennungsphase).
{ "JobName": "<RowKey des Jobs>" }
Ablauf: JobName prüfen → EnsureInitializedAsync → ProcessJobForRetrieval.
Antwort 200 OK; Fehler propagieren als 500. Der Aufruf läuft ohne Job-Locking parallel
zum Timer, siehe Datenflüsse → Watermark.
POST /api/ExecuteHttpRequest
Diagnose-Endpunkt: setzt eine rohe HTTP-GET-Anfrage mit eigenem HttpClient ab,
unabhängig von den konfigurierten Connectoren.
| Feld | Default | Beschreibung |
|---|
BaseAddress | erforderlich | Basis-URL |
Url | erforderlich | relativer Pfad inkl. Query |
Method | GET | nur GET implementiert |
Authentication | NTLM | NTLM mit UserName/Password, sonst ohne Credentials |
ODataMaxVersion, ODataVersion | 4.0 | OData-Header |
Prefer | odata.include-annotations="*" | Prefer-Header |
MediaTypeWithQuality | application/json | Accept-Header |
Antwort: 200 OK mit dem rohen Antwort-Body des Zielservers.
GET /api/CheckList
EnsureInitializedAsync → InitializationService.CheckAsync(). Prüft Connections, Job-Connections,
eindeutige Order, Mapping-Existenz sowie Tabellen und Felder gegen die Live-Metadaten
der Connectoren. Antwort 200 OK mit serialisiertem CheckResponse, auch wenn Befunde
vorliegen.
GET /api/Validate
Erzwingt die Initialisierung. 200 OK bei Erfolg, sonst 500; Body text/plain mit
InitializationService.GetErrorLogs().
GET /api/job, GET /api/job/{rowKey}
Geladene Jobs (alle bzw. einer). 404, wenn der RowKey unbekannt ist.
GET /api/mapping, GET /api/mapping/{name}
Geladene Mappings. Die Suche läuft über den Mapping-Namen (PartitionKey), Fallback
RowKey.
POST /api/mcp
Streamable-HTTP-Endpunkt des MCP-Servers. GET /api/mcp antwortet mit 405, weil kein
SSE-Stream angeboten wird. Tools und Nutzung: MCP-Server.
GET /api/StartGetChangesOrchestration
Startet die Durable-Orchestrierung. Antwort 202 Accepted mit der Instance-ID und den
Standard-Status-URLs des Durable-Clients.
Trigger-Bindungen
| Function | Bindung | Konfiguration |
|---|
GetChanges | Timer | %GetChangesTimerExpression%, Bicep-Default */15 * * * * *; RunOnStartup nur im Debug-Build |
ProcessStandardQueueItems | Service Bus | Queue sbqexport, Connection ExportQueueServiceBus, AutoCompleteMessages = false |
ProcessHighPriorityQueueItems | Service Bus | Queue sbqhighexport |
ProcessLowPriorityQueueItems | Service Bus | Queue sbqlowexport |
GetChangesOrchestrator | Orchestration | Durable |
GetChangesActivity | Activity | MaxBatchSize = 3 |
JobState | Entity | Add, Remove, InProgress, Reset |
Die Datei sample.local.settings.json setzt lokal * */2 * * * *. Effektiv gilt immer
der App-Setting-Wert der jeweiligen Umgebung.
Nachrichtenschema (Service Bus)
Body: JSON-serialisiertes QueueMessage (Newtonsoft.Json).
| Feld | Typ | Beschreibung |
|---|
Id | string | Schlüssel des Quell-Datensatzes |
JobName | string | erzeugender Job |
ActionCode | JobActionType | NewUpdate, Delete, TimeStamp, DeleteOnUpdate oder ein InvokeAction-Name |
Attributes | Dictionary<string, QueueMessageProperty> | gemappte Werte als Value (string) + TypeName |
ModifiedOn | datetime? | Änderungszeitpunkt der Quelle, Basis für SkipOutdatedMessages |
ChangeTracker | string | [JsonIgnore], nur für die Watermark während der Erkennung |
NeedToIgnore | bool | [JsonIgnore] |
Application Properties:
| Property | Wann gesetzt |
|---|
JobName, SourceKey, Timestamp | beim Senden |
ErrorMessage, ErrorTimeStamp, ErrorStackTrace, OperationId | beim Abandon nach einem Fehler |
SourceKey nutzt der DeadLetterMover zur Deduplizierung. OperationId ist die
W3C-Root-ID und erlaubt die Korrelation mit Application Insights.
Typisierung der Attribute
Alle Werte werden heute als String plus Typname transportiert. Eine Umstellung auf echte
JSON-Typen mit symmetrischen Resolvern ist entworfen, aber nicht umgesetzt. Sie wäre ein
Breaking Change für bereits gequeute Nachrichten.
Fehlerverhalten
| Situation | Verhalten |
|---|
ExecuteJob ohne JobName | Exception → 500 |
| Initialisierungsfehler | Validate liefert 500 mit Log; Timer- und Queue-Trigger setzen die Initialisierung zurück und versuchen es beim nächsten Aufruf erneut |
Verarbeitungsfehler in ProcessQueueItems | Abandon mit Fehler-Properties; nach maxDeliveryCount in die Dead-Letter-Subqueue |
TransitiveException (HTTP 409 im Ziel) | Nachricht wird in die Transitive-Queue der Priorität kopiert und im Original abgeschlossen; kein Trigger auf der Transitive-Queue |
Veraltete Nachricht bei SkipOutdatedMessages | Kopie nach sbqignored, Original abgeschlossen |
| Wiederholte Zustellung | ProcessQueueMessageService wartet DeliveryCount² · 1000 ms vor dem Versuch |
EndpointNotFoundException, CommunicationException in GetChanges | Reset der Initialisierung, nächster Timer-Lauf versucht erneut |
5 - Datenflüsse
Sequenzdiagramme für Erkennung (Timer und Durable), Verarbeitung, Response-Jobs und Fehlerpfad; Persistenz der Daten; Watermark-Logik; DeadLetterMover.
Die konzeptuelle Beschreibung steht unter Wie es funktioniert.
Hier folgen die Abläufe auf Klassenebene.
Erkennung (Timer)
sequenceDiagram
autonumber
participant Timer as GetChanges (Timer)
participant Init as InitializationService
participant PJ as ProcessJobsForRetrievalInOrderService
participant Job as JobService
participant WM as WatermarkService
participant Conn as Source-Connector
participant SBS as ServiceBusService
participant SB as Service Bus
Timer->>Init: EnsureInitializedAsync()
Note over Init: einmalig: Storage → Connections → Mappings → Jobs
Timer->>PJ: Execute()
PJ->>Job: GetJobsToRun()
Note over Job,WM: GetAllWatermarks() (Bulk) für LastCronRun
loop je Job nach Order
PJ->>WM: GetWatermarkByJobName(job)
PJ->>Conn: RetrieveChangesAsync(mapping, job, watermark, filter)
loop seitenweise
Conn-->>PJ: QueueMessage (serialisiert)
PJ->>SBS: AddMessageOrSendAsync(msg, priority)
alt Batch voll (200) oder Größenlimit
SBS->>SB: SendMessagesAsync(batch)
end
PJ->>PJ: lastChangeTracker = msg.ChangeTracker
end
PJ->>SBS: FlushBatch()
PJ->>WM: UpdateWatermarkByJobName(newWatermark, lastCronRun)
end- Die Serialisierung (Key-Aufbau → Feld-Mappings → Source-Operationen / Resolver) passiert
im Connector (
ConnectorBase.Serialize). - Die Watermark wird erst am Ende eines Jobs geschrieben. Bricht der Lauf vorher ab,
wird beim nächsten Lauf erneut ab dem alten Stand gelesen;
SkipOutdatedMessages fängt
Doppel-Nachrichten im Ziel ab. - Bei reinem Zeitstempel-Vorrücken ohne fachliche Änderung wird
JobActionType.TimeStamp
verwendet. MetricsService zählt ChangesDetected je Job.
Erkennung (Durable)
sequenceDiagram
autonumber
participant HTTP as StartGetChangesOrchestration
participant Orch as GetChangesOrchestrator
participant State as JobState (Entity)
participant Act as GetChangesActivity
participant Conn as Source-Connector
participant SBS as ServiceBusService
HTTP-->>HTTP: 202 + InstanceId
loop je Job aus GetJobsToRun
Orch->>State: InProgress(jobName)?
alt in Bearbeitung
Orch->>Orch: überspringen
else
Orch->>State: Add(jobName)
loop do … while HasMoreRecord
Orch->>Act: CallActivity(JobActivityParam)
Act->>Conn: RetrieveChangesAsync(…, MaxBatchSize=3)
Act->>SBS: SendMessage(record, priority)
Act-->>Orch: Page, Cookie, Watermark, HasMoreRecord
end
Orch->>State: Remove(jobName)
end
endDer Durable-Pfad persistiert die Watermark nicht und ist im Timer-Betrieb nicht aktiv.
Verarbeitung (Service Bus → Zielsystem)
sequenceDiagram
autonumber
participant SB as Service Bus Queue
participant PQI as Process*QueueItems
participant Init as InitializationService
participant PQM as ProcessQueueMessageService
participant Conn as Target-Connector
participant SBS as ServiceBusService
SB->>PQI: Nachricht
PQI->>PQI: Lock-Renewal-Task starten (30 s)
PQI->>Init: EnsureInitializedAsync()
PQI->>PQM: Execute(busMessage)
PQM->>PQM: QueueMessage deserialisieren, Job + Mapping laden
alt DeliveryCount > 1
PQM->>PQM: Backoff DeliveryCount² · 1000 ms
end
alt SkipOutdatedMessages und Quelle älter als Ziel
PQM->>SBS: SendMessage → sbqignored
else NewUpdate
PQM->>Conn: Upsert(message, mapping[, responseMapping, job])
opt Response vorhanden
PQM->>SBS: SendMessage(response, priority)
end
else Delete
PQM->>Conn: Delete(message, mapping)
else InvokeAction
PQM->>Conn: InvokeAction(message, job)
end
alt Erfolg
PQI->>SB: CompleteMessageAsync()
else TransitiveException
PQM->>SBS: SendMessage → sbqtransitive*
PQI->>SB: CompleteMessageAsync()
else andere Exception
PQI->>SB: AbandonMessageAsync(+ Error-Properties)
Note over SB: nach maxDeliveryCount ⇒ Dead-Letter
end
PQI->>PQI: Lock-Renewal stoppenMetricsService zählt MessagesProcessed mit dem Ausgang (Success, Transitive, Ignored,
Failed).
Response-Job
sequenceDiagram
autonumber
participant ProcA as Process*QueueItems (Job A: CRM → BC)
participant BC as Target-Connector BC
participant SB as Service Bus
participant ProcB as Process*QueueItems (ResponseJob: BC → CRM)
participant CRM as Target-Connector CRM
ProcA->>BC: Upsert(message, mapping, responseMapping, job)
BC-->>ProcA: QueueMessage mit erzeugtem Ziel-Schlüssel
ProcA->>SB: SendMessage(responseMessage, priority)
SB->>ProcB: Response-Nachricht
ProcB->>CRM: Upsert (z. B. BC-Belegnummer zurückschreiben)Fehlerpfad
flowchart TD
MSG["Queue-Nachricht"] --> PROC["ProcessQueueMessageService.Execute"]
PROC -->|Erfolg| DONE["Complete"]
PROC -->|TransitiveException| TRANS["sbqtransitive*<br/>kein Trigger, liegt bis TTL"]
PROC -->|veraltet| IGN["sbqignored<br/>kein Trigger"]
PROC -->|andere Exception| AB["Abandon + Error-Properties"]
AB --> RETRY{"DeliveryCount<br/>< maxDeliveryCount?"}
RETRY -->|ja| MSG
RETRY -->|nein| DLQ["Dead-Letter-Subqueue"]
DLQ -.->|manuell| DLM["DeadLetterMover"]
TRANS -.->|manuell| DLM
DLM -.->|Resubmit ohne Error-Properties| MSGTransitive- und Ignored-Queues werden nicht automatisch weiterverarbeitet. Sie dienen
der Isolation und der Analyse. Ein Requeue erfolgt über den DeadLetterMover.
DeadLetterMover
Konsolenanwendung unter src/DeadLetterMover. Sie liest eine Queue oder deren
$DeadLetterQueue, entscheidet anhand der ErrorMessage per Muster (Resubmit, None, Skip)
und sendet Nachrichten ohne Error-Properties zurück in die Original-Queue, dedupliziert über
SourceKey. Connection-String, Queue-Name und der Schalter UseDeadletter stehen als
Konstanten im Code und werden vor dem Lauf angepasst. Der Connection-String darf nicht
eingecheckt werden.
Persistenz
| Datenart | Ort | Lebensdauer |
|---|
| Konfiguration | Table Storage connections, jobs, mappings | persistent, manuell gepflegt |
Watermarks (Watermark, LastCronRun) | Table Storage watermark | persistent je Job |
| Nachrichten | Service-Bus-Queues | transient, TTL P14D |
| Durable-State | Azure Storage DurableFunctionsHub | laufzeitgebunden |
| Telemetrie | Application Insights | 90 Tage |
| Fachdaten | nur Quell- und Zielsysteme | – |
Watermark-Logik
Die Wertlogik liegt in WatermarkValue (Models/Common), der Service persistiert nur.
GetWatermarkByJobName liefert bei fehlendem Eintrag eine nicht-persistierte Entity;
LastCronRun == null bedeutet „Job ist fällig“.GetAllWatermarks ist eine Bulk-Abfrage auf PartitionKey watermark und wird vom
Scheduler genutzt, damit nicht je Job eine Einzelabfrage anfällt.WatermarkValue.Initialize(type, stored) setzt Typ-Defaults (date → 0001-01-01,
datetime → 0001-01-01T00:00:00Z). Der Typ wird getrimmt und case-insensitiv geparst;
ein unbekannter Typ belässt den gespeicherten Wert und erzeugt eine Warnung.Advance(changeTracker) vergleicht typabhängig mit InvariantCulture und
RoundtripKind.UpdateTableEntry schreibt ohne ETag (Last-Writer-Wins). Der HTTP-Endpunkt
ExecuteJob kann parallel zum Timer laufen; ein veralteter Stand ist immer älter und
führt zu Wiederholungen, nicht zu Lücken.- Reset: Feld
Watermark in der Tabelle leeren → vollständiger Neulauf. Anwendersicht:
Watermarks.
6 - Abhängigkeiten
Projekt-Abhängigkeiten, Azure-Dienste, externe Systeme und architekturkritische Kopplungen.
net10.0 in allen Projekten, ImplicitUsings und Nullable aktiv.- Azure Functions v4, Isolated Worker,
OutputType=Exe. - Tests auf xUnit v3 mit Microsoft Testing Platform. Aufruf mit
dotnet test --project …; das Positionsargument funktioniert nicht mehr.
Projekte
flowchart LR
HOST["DataBridge (Host)"] --> CORE["DataBridge.Core"]
DLM["DeadLetterMover"] --> CORE
TEST["DataBridge.Test"] --> CORE
TEST --> HOSTNuGet-Pakete
Die Versionen werden zentral in src/Directory.Packages.props gepflegt (Central Package
Management); die .csproj-Dateien referenzieren Pakete nur noch ohne Version. Ein Update
passiert also an genau einer Stelle.
Sicherheitsprüfung beim Build: Directory.Packages.props aktiviert NuGet Audit für alle
Projekte, inklusive transitiver Pakete, und meldet Treffer jeder Schwere. Ab „moderate“
(NU1902–NU1904) gelten sie als Fehler, „low“ (NU1901) bleibt eine Warnung — solche Lücken
betreffen oft Funktionen, die gar nicht genutzt werden, und sollen kein Release blockieren.
Ein Paket mit einer Sicherheitslücke ab „moderate“ lässt damit Restore und Build lokal, im
Dockerfile und in der Pipeline scheitern. Abhilfe ist ein Update der betroffenen Version; liegt
die Lücke in einem transitiven Paket, dieses mit eigener Version in Directory.Packages.props
aufnehmen. Weil neue Advisories jederzeit erscheinen, kann ein bisher grüner Stand ohne
Code-Änderung rot werden. Ist der Advisory-Feed nicht erreichbar, bleibt es bei der Warnung
NU1900, offline bauen funktioniert also weiter.
Hier nur die Rolle der wichtigsten Pakete:
| Paket | Projekt | Rolle |
|---|
Microsoft.Azure.Functions.Worker.* | Host | Runtime, HTTP/ASP.NET-Core-, Service-Bus-, Timer-, Storage-, Durable-Extensions |
Microsoft.ApplicationInsights.WorkerService, Microsoft.ApplicationInsights | Host, Core | Telemetrie. Pin auf 2.23.0, weil 3.x ITelemetryProcessor entfernt hat |
ModelContextProtocol.AspNetCore | Host | MCP-Server |
ModelContextProtocol.Core | Core | nur McpException, damit Tool-Fehlerursachen den MCP-Client erreichen |
Azure.Data.Tables | Core | Table Storage |
Azure.Messaging.ServiceBus | Core | Queues, Queue-Laufzeitdaten |
Azure.Identity, Azure.Security.KeyVault.Secrets | Core | DefaultAzureCredential, optionaler Key-Vault-Bezug |
Microsoft.PowerPlatform.Dataverse.Client | Core | CE-Connector |
Microsoft.PowerPlatform.Dataverse.Client.AzAuth | Core | CE-Connector, Managed Identity |
Microsoft.OData.Edm, Microsoft.OData.Core | Core, Host | EDM-Metadaten für BC/NAV |
Microsoft.OpenApi, Microsoft.OpenApi.YamlReader | Core | OpenAPI-Connector |
Microsoft.Data.SqlClient, Npgsql, MySql.Data, Oracle.ManagedDataAccess.Core | Core | SQL-Connectoren |
NCrontab.Signed | Core | Cron-Auswertung mit Sekundenfeld |
Newtonsoft.Json | alle | Serialisierung von QueueMessage und Konfigurations-JSON |
System.Linq.Async | Core | IAsyncEnumerable beim seitenweisen Abruf |
Alle DB-Treiber werden immer mitgeladen, auch wenn nur ein Teil genutzt wird.
Azure-Dienste
| Dienst | Verwendung | Kritikalität |
|---|
| Azure Functions / App Service Plan | Ausführung | kritisch |
| Azure Service Bus (Basic) | Messaging zwischen Erkennung und Verarbeitung | kritisch |
| Azure Table Storage | Konfiguration und Watermarks | kritisch |
| Azure Blob/File/Queue Storage | Functions-Runtime, Content Share, Durable-State | kritisch |
| Application Insights | Telemetrie | hoch |
| Grafana-Dashboard (Azure Managed) | Betriebsdashboard | optional |
| Azure Key Vault | optionaler Secret-Bezug der Connectoren, nicht deployt | optional |
Externe Systeme
| System | Protokoll / SDK | Lesen | Schreiben |
|---|
| D365 Business Central | OData v4, OAuth2 / Basic / Managed Identity | ja | ja |
| D365 CE / Dataverse | ServiceClient (Connection-String oder Managed Identity über AzAuth), Change Tracking | ja | ja |
| D365 NAV | OData, OAuth2 / Basic | ja | ja |
| REST-API mit OpenAPI-Spezifikation | HTTP, API-Key / OAuth2 / Basic | ja | ja |
| MSSQL, PostgreSQL, MySQL, Oracle | ADO.NET | ja | nein |
Architekturkritische Kopplungen
| Abhängigkeit | Risiko |
|---|
| Service Bus Basic | Keine Topics, Sessions, Partitionierung, Duplicate Detection |
| Table Storage als Konfig-Store | Fehlkonfiguration wirkt global; Prüfung nur über CheckListService und JobService.Initialize |
| Dataverse-SDK-Version | Enge Kopplung des CE-Connectors |
NCrontab.Signed | Bestimmt die Scheduling-Semantik |
Eigener BufferingLoggerProvider | Diagnose hängt am Custom-Sampling; AI-Standardprovider ist abgeschaltet |
Newtonsoft.Json | Verbindliches Wire-Format der Nachrichten |
| String-Typisierung der Nachrichten | Jeder Resolver parst selbst inkl. Locale; Quelle von Kulturproblemen zwischen Systemen |
7 - Fähigkeiten-Katalog
Katalog aller Fähigkeiten der dsyr.DataBridge mit ihrer technischen Umsetzung.
Integration und Datenfluss
| Fähigkeit | Umsetzung |
|---|
| Change Detection (Pull) | GetChanges → ProcessJobsForRetrievalInOrderService; je Connector RetrieveChangesAsync (BC/NAV $filter, CE RetrieveEntityChangesRequest, SQL seitenweises SELECT, OpenAPI Query-String) |
| Orchestrierter Abruf | Durable: GetChangesOrchestrator, GetChangesActivity, Entity JobState |
| Asynchrone Entkopplung | Service Bus mit sieben Queues, ServiceBusService mit Batch bis 200 |
| Priorisierte Verarbeitung | JobPriorityType → Queue; drei Trigger |
| Reihenfolgensteuerung | Job.Order |
| Idempotentes Schreiben | Connector.Upsert/Delete; TargetKey/TargetKeys mit KeyRank, erster Treffer gewinnt |
| Aktions-Jobs | InvokeAction: Job-Action ohne CRUD-Bedeutung ruft einen benannten Endpunkt |
| Job-eigene Pfade | Source/Target am Job überschreiben SourceTable/TargetTable des Mappings |
| Eingebettete Kinder | EmbedChildren (nur CE als Quelle) |
| Response-Jobs | Upsert(..., responseMapping, job) → Response-Nachricht |
| Watermarking | WatermarkService + WatermarkValue; Typen date, datetime, string |
Konnektivität
| Fähigkeit | Umsetzung |
|---|
| D365 Business Central | BcConnector : HttpConnectorBase, OAuth2 / Basic / Managed Identity |
| D365 CE / Dataverse | CeConnector : ConnectorBase, ServiceClient (Connection-String oder url + Managed Identity) |
| D365 NAV | NavConnector : HttpConnectorBase |
| REST über OpenAPI | OpenApiConnector : HttpConnectorBase, Spec aus openApiSpecUrl |
| MSSQL, PostgreSQL, MySQL, Oracle | SqlConnectorBase<T>, nur lesend |
| Auth-Varianten HTTP | Basic, OAuth2 Client Credentials mit tokenUrl, API-Key, Managed Identity (explizit über authmethod) |
| Key-Vault-Bezug | keyVaultUrl in der Connection + DefaultAzureCredential |
| Schema-Metadaten | IConnector.GetTableNames, GetTableMetadata für alle Connector-Typen |
| Ad-hoc-HTTP | ExecuteHttpRequest (nur GET) |
| Fähigkeit | Umsetzung |
|---|
| Feld-Mapping | TableMapping + FieldMapping aus mappings |
| Typ-Coercion | AttributeResolver-Familien je Systemtyp |
| Metadaten-basierte Resolver-Wahl | TargetValueMapper je Connector, statische FrozenDictionary-Registry mit Coverage-Tests |
| Default-Werte | Target-Operationen DefaultValue, DefaultValueIfNullOrEmpty, DefaultValueMap |
| Option-/Picklist-Mapping | OptionMapping, CE-Resolver für Picklist, MultiSelect, State/Status |
| Lookup-Auflösung | Source-Operation LookupValue mit Cache; CE mit Batch-Pre-Caching; DoNotCreate |
| CE-Beziehungen | ActivityParty, LinkToRecord |
| ReadFromMessage | Wert aus der Nachricht übernehmen |
| Virtuelle Felder | _-Präfix im SourceField, nie gegen Metadaten validiert |
| Erweiterbarkeit | Source-Operation-Handler per Reflection |
Konfiguration und Steuerung
| Fähigkeit | Umsetzung |
|---|
| Konfigurationsgetriebener Betrieb | Table Storage, StorageService, SettingsService |
| Cron je Job | Job.Schedule, NCrontab.Signed, sechs Felder |
| Manueller Job-Start | ExecuteJob |
| Function-Toggles | AzureWebJobs.<Name>.Disabled, Bicep disabledFunctions |
| Konfig-Diagnose | CheckList, Validate, JobApi, MappingApi |
| KI-Anbindung | MCP-Server mit read-only Tools |
| Self-Repair der Initialisierung | ResetInitialization bei Fehlern |
| Quell-Filter | Job.Filter mit {{…}}-Templating |
| Veraltete Nachrichten überspringen | SkipOutdatedMessages → sbqignored |
Resilienz
| Fähigkeit | Umsetzung |
|---|
| Backoff-Retry | DeliveryCount² · 1000 ms |
| Lock-Renewal | Hintergrund-Task alle 30 s |
| Konflikt-Isolation | TransitiveException → sbqtransitive* |
| Dead-Lettering | maxDeliveryCount, Fehler-Properties an der Nachricht |
| Requeue | DeadLetterMover |
| Kommunikationsfehler | GetChanges fängt EndpointNotFoundException, CommunicationException |
Observability
| Fähigkeit | Umsetzung |
|---|
| Gepuffertes Logging | BufferingLoggerProvider, Flush pro Invocation |
| Dependency-Sampling | DependencySamplingProcessor |
| Custom Metrics | MetricsService (ChangesDetected, MessagesProcessed) |
| Korrelation | InvocationContext, operation_id |
| Steuerung | TELEMETRY_CONTROL, drei Raten-Settings |
| Alerts | DLQ-Stufen, Queue-Länge, Action Group |
| Dashboard | Grafana-Dashboard aus Bicep |
| Version | GetInfo |
Infrastruktur und Auslieferung
| Fähigkeit | Umsetzung |
|---|
| Infrastructure as Code | Bicep mit Modulen und .bicepparam |
| Marketplace | createUiDefinition.json, generierte mainTemplate.json |
| Identitätsbasierter Zugriff | enableManagedIdentity + Rollen |
| Serverless-Skalierung | Y1 / EP1–EP3, functionScaleLimit |
| Run-from-Package | WEBSITE_RUN_FROM_PACKAGE=1 |
| Container | Dockerfile, compose.yml |
| Versionierung | Major.Minor.yyyy.MMdd, manueller Bump |
| CI/CD | Azure Pipelines unter cicd/ |
| Lokale Entwicklung | Podman-Testumgebung mit DBs und HTTP-Mocks |
Qualitätssicherung
| Fähigkeit | Umsetzung |
|---|
| Unit-Tests | DataBridge.Test, xUnit v3, Moq |
| Coverage | Microsoft Testing Platform Code Coverage |
| Resolver-Vollständigkeit | *TargetValueMapperCoverageTests |
| Dokumentation | Hugo/Docsy unter docs/, veröffentlicht per Pipeline |