GoodFunds Help Center
Microsoft Dynamics 365 (Entwickler)

Architektur- & Entwickler-Referenz

Interner Aufbau des Microsoft-Dynamics-365-CRM-Adapters — Klassen, Dataverse-Web-API-Endpunkte, OData-Besonderheiten, OAuth und Fehlerbehandlung.

Der Microsoft-Dynamics-Adapter folgt dem hexagonalen Aufbau des Projekts und verwendet das bestehende CRM-Port-System wieder. Neu ist nur der provider-spezifische Adapter.

Paketstruktur

integration/outbound/adapter/crm/microsoftdynamics/
├── domain/
│   └── MicrosoftDynamicsCredentials.java        entschlüsselte Credential-Struktur
├── inbound/vaadin/
│   └── MicrosoftDynamicsCredentialForm.java      Credential-Formular im Assistenten
└── outbound/
    ├── MicrosoftDynamicsClient.java              HTTP-/OData-Gateway
    └── MicrosoftDynamicsProvider.java            CrmIntegrationPort-Implementierung

Der ProviderName-Enum erhält den Wert MicrosoftDynamics. Spring-Bean-IDs:

  • Provider: MicrosoftDynamicsProvider
  • Formular: MicrosoftDynamicsForm (d. h. ProviderName.MicrosoftDynamics + "Form", damit IntegrationProviderFactory#getCredentialInput es automatisch auflöst).

Verantwortlichkeiten

KlasseVerantwortung
MicrosoftDynamicsCredentialsEntschlüsselte In-Memory-Zugangsdaten (Tenant/Client/Secret/Environment-URL, Attribut-Map, Update-Strategie). authMethod = OAUTH2_CLIENT_CREDENTIALS. Persistiert als verschlüsseltes configJson.
MicrosoftDynamicsClientLow-Level-Gateway: OkHttp-Nutzung, URL-Bau, OData-Header, Response-Lesen, HTTP-Fehler-Mapping. Liefert rohes JSON als String.
MicrosoftDynamicsProviderGeschäftslogik: parst JSON, mappt GoodFunds ↔ Dynamics, wendet die Update-Strategie an, implementiert CrmIntegrationPort.
MicrosoftDynamicsCredentialFormVaadin-Credential-Formular (CredentialInput): sammelt Eingaben, steuert den Verbindungstest, baut das Feld-Mapping.

Dataverse-Web-API

Basis-URL: {environmentUrl}/api/data/v9.2

OperationHTTPPfad
VerbindungsprüfungGET/WhoAmI
Kontakt per E-Mail suchenGET/contacts?$filter=emailaddress1 eq '{email}'
Kontakt erstellenPOST/contacts
Kontakt aktualisierenPATCH/contacts({contactId})
Feld-ErkennungGET/EntityDefinitions(LogicalName='contact')/Attributes

URLs werden immer mit dem HttpUrl-Builder von OkHttp gebaut — keine String-Konkatenation. Die OData-Entity-Set-/Key-/Navigation-Segmente (z. B. contacts(<id>), EntityDefinitions(LogicalName='contact')) sind gültige Pfadsegmente und werden unverändert durchgereicht.

OData-Besonderheiten

  • Request-Header bei jedem Aufruf: OData-Version: 4.0, Accept: application/json.
  • Bei POST/PATCH zusätzlich: Content-Type: application/json und Prefer: return=representation (damit der Response-Body die geschriebene Entität enthält).
  • Collection-Antworten sind in eine OData-Hülle verpackt: { "value": [ ... ] }.
  • Der Kontakt-Primärschlüssel ist contactid (eine GUID), bereitgestellt als GoodFunds-External-ID.

Authentifizierung (OAuth 2.0 Client Credentials)

Die Authentifizierung ist vollständig an den gemeinsamen OAuthTokenService (GVL-127) delegiert. Der Client baut aus den Zugangsdaten eine OAuthClientCredentialsConfig und ruft getValidToken auf, das das Token pro Credential-ID cacht und nach Ablauf transparent neu holt.

  • Token-Endpunkt: https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
  • Scope: {environmentUrl}/.default
  • Jeder Request trägt Authorization: Bearer {accessToken}.

Die Environment-URL wird normalisiert (Leerzeichen entfernt, abschließende Schrägstriche entfernt), bevor sie in Scope und Basis-URL verwendet wird — ein versehentliches nachgestelltes Leerzeichen erzeugt sonst AADSTS70011 invalid_scope.

Upsert-Ablauf

createOrUpdateContact ist ein Upsert mit der E-Mail als Schlüssel:

  1. findContact(email) → GET /contacts?$filter=emailaddress1 eq '{email}'.
  2. Treffer → Felder mit ContactAttributeMerger (gemäß konfigurierter Strategie) zusammenführen und /contacts({contactId}) per PATCH aktualisieren. Ergibt das Merge keine Änderungen, wird das Update übersprungen und als erfolgreiches No-op gemeldet.
  3. Kein Treffer → POST /contacts, liefert die neue contactid.

Die Feld-Erkennung (getAvailableContactFields) liest die contact-Attribut-Metadaten und mappt Dynamics-Attributtypen auf CRM-Feldtypen:

Dynamics AttributeTypeCRM FieldType
String, MemoSTRING
Integer, BigInt, Decimal, Double, MoneyNUMBER
DateTimeDATE
BooleanBOOLEAN
Picklist, State, StatusENUM
alles andere (Lookup, Owner, Uniqueidentifier, Virtual, …)(übersprungen)

Fehlerbehandlung

Der Client mappt HTTP-/Netzwerkfehler auf die Integrations-Exception-Typen, damit der Queue-Task-Handler über einen Retry entscheiden kann:

BedingungException
HTTP 429 oder 5xxRetryableIntegrationException
Sonstiges Non-2xxNonRetryableIntegrationException
Netzwerk-/IO-FehlerRetryableIntegrationException
Ungültige Environment-URL / SerialisierungsfehlerNonRetryableIntegrationException

getProviderStatus wirft nie: bei jedem Fehler liefert es einen DOWN-Status mit der Fehlermeldung, der das Ergebnis von „Verbindung testen“ im Formular steuert.

Logging

Operationen loggen mit strukturiertem Kontext — credentialId, der Account-ID (dem Host der Dataverse-Umgebung), dem Operationsnamen und bei Schreibvorgängen der resultierenden contactid sowie den geänderten Feld-Keys. Der Client loggt zusätzlich Statuscode und Request-Dauer auf Debug-Level.

Tests

Drei Testklassen decken den Adapter ab:

  • MicrosoftDynamicsClientTest — HTTP-Aufrufe gegen einen MockWebServer (OkHttp).
  • MicrosoftDynamicsProviderTest — alle Port-Methoden gegen einen gemockten Client.
  • MicrosoftDynamicsCredentialFormTest — Formular-Validierung.

Backend-Tests ausführen mit:

cd vaadin-spring && mvn test -Pskip-frontend -Dtest=MicrosoftDynamics*

Auf dieser Seite