====== Connection Manager ====== Der Connection Manager (''IWvdSConnectionManager'') ist ein provider-agnostischer Connection Broker auf ''IHost''. Er verwaltet benannte Datenverbindungen zentral für alle Add-ins. Ziel ist eine einheitliche Schnittstelle, über die jedes Add-in auf konfigurierte Datenquellen zugreift, ohne selbst Verbindungsparameter, Treibererkennung oder Instanz-Lebenszyklus verwalten zu müssen. Im Unterschied zu einer direkten Provider-Erzeugung im Add-in-Code liegt die gesamte Konfiguration in der ''user.config.json'' — Änderungen an Verbindungsparametern erfordern keinen Code-Eingriff. Der Vorteil besteht darin, dass alle Add-ins dieselbe Provider-Instanz teilen und der Host den Lebenszyklus kontrolliert. Erreichbar über ''FHost.Connections''. Zurück zur [[services|Service-Referenz]] oder zur [[start|SDK-Übersicht]]. ===== Architektur ===== Der Connection Manager arbeitet in drei Phasen: - **Konfiguration lesen:** Beim Start liest die Shell das ''connections''-Array aus der ''user.config.json''. Jede Verbindung hat einen Namen, einen Provider-Typ und ein Array mit provider-spezifischen Feldwerten. - **Factory-Registry:** Für jeden Provider-Typ (''mssql'', ''rest'', ''sqlite'', etc.) existiert eine ''IWvdSProviderFactory''. Der Host registriert eine eingebaute Factory für ''mssql'' (ODBC Driver 17/18). Add-ins können eigene Factories für weitere Provider-Typen registrieren. - **Lazy Singleton:** Provider-Instanzen werden erst beim ersten ''GetConnection''-Aufruf erzeugt. Danach liefert jeder weitere Aufruf mit demselben Namen dieselbe Instanz zurück. Add-ins erhalten Borrowed References — sie dürfen die Instanz **nicht** freigeben. user.config.json IWvdSConnectionManager ┌──────────────────┐ ┌─────────────────────────┐ │ "connections": [ │──liest──▶ │ RegisterFactory() │ │ { name, type, │ │ GetConnection() │──▶ Singleton-Provider │ values } │ │ GetConnectionNames() │ │ ] │ │ CheckInfrastructure() │ └──────────────────┘ │ TestConnection() │ └─────────────────────────┘ ===== Warum zentral? ===== **1. Eine Verbindung, nicht N.** Wenn fünf Add-ins jeweils einen eigenen Provider erzeugen, entstehen fünf ODBC-Verbindungen zum selben Server. Die Shell hält pro benannter Verbindung exakt eine Singleton-Instanz. Das reduziert Ressourcenverbrauch und vermeidet Lizenzprobleme bei verbindungslimitierten Datenbanken. **2. Credential-Hoheit.** Passwörter und API-Keys liegen in ''ISecretStorage'' (DPAPI-geschützt). Die Shell löst sie beim Verbindungsaufbau auf. Kein Add-in erhält jemals Klartext-Credentials — weder zur Laufzeit noch über die Konfigurationsdatei. **3. Einheitliches Verhalten.** TrustCertificate, Timeout, Retry-Strategie, Connection-Monitoring: alles einmal in der Shell implementiert, nicht in jedem Add-in dupliziert. Änderungen am Verbindungsverhalten wirken sofort für alle Consumer. **4. Infrastruktur-Check.** Die Shell prüft beim Start, ob die benötigten Treiber und Laufzeitumgebungen installiert sind (ODBC Driver 17/18, libsqlite3, TLS-Zertifikate). Das Ergebnis wird in der Settings-Oberfläche angezeigt — der Anwender sieht sofort, ob die Voraussetzungen erfüllt sind. ===== API ===== ==== Hilfs-Records ==== type { Beschreibt ein Konfigurationsfeld eines Providers } TWvdSProviderField = record Name: string; // Feldname, z.B. 'Server', 'Database' TypeName: string; // Typbezeichnung, z.B. 'string', 'boolean', 'integer' Description: string; // Beschreibung für UI/Dokumentation DefaultValue: string; // Standardwert als String end; { Ergebnis einer Infrastrukturprüfung } TWvdSInfraCheckResult = record Available: Boolean; // True wenn der benötigte Treiber vorhanden ist DriverVersion: string; // Erkannte Treiberversion, z.B. '17.10.6.1' Message: string; // Meldung für den Benutzer end; { Ergebnis eines Verbindungstests } TWvdSConnectionTestResult = record Success: Boolean; // True wenn die Verbindung hergestellt werden konnte LatencyMs: Integer; // Antwortzeit in Millisekunden Message: string; // Fehlermeldung oder Statustext end; ==== IWvdSProviderFactory ==== Eine Provider-Factory erzeugt Provider-Instanzen für einen bestimmten Typ. Der Host nutzt die Factory, um beim ersten ''GetConnection''-Aufruf den passenden Provider zu erzeugen. type IWvdSProviderFactory = interface { Gibt den Typ-Bezeichner zurück, z.B. 'mssql', 'rest', 'sqlite' } function GetProviderType: string; { Liefert die Felddefinitionen, die dieser Provider-Typ benötigt. Die Reihenfolge entspricht dem values-Array in der Konfiguration. } function GetConfigFields: TArray; { Prüft ob die benötigte Infrastruktur vorhanden ist (z.B. ODBC-Treiber für mssql, libsqlite3 für sqlite). } function CheckInfrastructure: TWvdSInfraCheckResult; { Erzeugt eine Provider-Instanz mit den gegebenen Feldwerten. Die Reihenfolge der Werte entspricht GetConfigFields. } function CreateProvider(const AValues: TStringArray): TObject; end; ^ Methode ^ Beschreibung ^ | ''GetProviderType'' | Gibt den Typ-Bezeichner zurück, z. B. ''"mssql"'', ''"rest"'' oder ''"sqlite"''. Dieser Bezeichner wird in der ''user.config.json'' als ''provider''-Feld verwendet. | | ''GetConfigFields'' | Liefert die Felddefinitionen, die dieser Provider-Typ benötigt. Für MSSQL sind das ''Server'', ''Database'', ''TrustedConnection'', ''Encrypt'', ''Timeout''. Diese Felddefinitionen werden in der Settings-Oberfläche als Eingabefelder dargestellt. | | ''CheckInfrastructure'' | Prüft, ob die Laufzeitumgebung für diesen Provider-Typ vorhanden ist. Bei MSSQL wird geprüft, ob ein ODBC-Treiber (17 oder 18) installiert ist. Das Ergebnis wird beim Shell-Start abgefragt und in der Settings-Oberfläche angezeigt. | | ''CreateProvider'' | Erzeugt eine konkrete Provider-Instanz aus den Konfigurationswerten. Das zurückgegebene ''TObject'' wird vom Consumer auf den konkreten Typ gecastet (z. B. ''TWvdSMSSqlProvider''). | ==== IWvdSConnectionManager ==== Das zentrale Interface, erreichbar über ''FHost.Connections''. type IWvdSConnectionManager = interface { Registriert eine Factory für einen Provider-Typ. Gibt ein IDisposable zurück — an IExtensionContext übergeben. } function RegisterFactory( AFactory: IWvdSProviderFactory): IDisposable; { Liefert den Provider für eine benannte Verbindung. Erzeugt die Instanz beim ersten Aufruf (Lazy Singleton). Borrowed Reference — NICHT freigeben! } function GetConnection(const AName: string): TObject; { Füllt die Liste mit allen konfigurierten Verbindungsnamen. } procedure GetConnectionNames(AList: TStringList); { Prüft die Infrastruktur für einen Provider-Typ (z.B. ob der ODBC-Treiber installiert ist). } function CheckInfrastructure( const AProviderType: string): TWvdSInfraCheckResult; { Testet eine benannte Verbindung (Aufbau + einfache Query). } function TestConnection( const AName: string): TWvdSConnectionTestResult; end; ^ Methode ^ Beschreibung ^ | ''RegisterFactory'' | Registriert eine Provider-Factory für einen bestimmten Typ. Gibt ein ''IDisposable'' zurück, das an ''IExtensionContext.Subscribe'' übergeben werden muss. Die Shell registriert die eingebaute ''mssql''-Factory beim Start. Add-ins können eigene Factories nachregistrieren. | | ''GetConnection'' | Gibt die Provider-Instanz für die benannte Verbindung zurück. Beim ersten Aufruf wird der Provider über die zuständige Factory erzeugt und gecacht. Weitere Aufrufe liefern dieselbe Instanz (Singleton pro Name). **Wichtig:** Die zurückgegebene Referenz ist geborgt (Borrowed Reference) — das Add-in darf sie nicht freigeben. Gibt ''nil'' zurück, wenn der Name nicht konfiguriert ist. | | ''GetConnectionNames'' | Füllt die übergebene ''TStringList'' mit den Namen aller konfigurierten Verbindungen. Nützlich für dynamische UI-Elemente (z. B. Verbindungs-Auswahl in einem Dialog). | | ''CheckInfrastructure'' | Delegiert an die Factory des angegebenen Provider-Typs und gibt das Infrastruktur-Prüfergebnis zurück. | | ''TestConnection'' | Führt einen Verbindungstest für die benannte Verbindung durch. Erzeugt ggf. den Provider, baut die Verbindung auf und misst die Latenz. Das Ergebnis enthält Erfolg/Misserfolg, Latenz in Millisekunden und eine Diagnosemeldung. | ===== Nutzung im Add-in ===== Ein Add-in greift über ''FHost.Connections'' auf eine konfigurierte Verbindung zu. Der zurückgegebene ''TObject'' wird auf den konkreten Provider-Typ gecastet. var LProvider: TObject; begin LProvider := FHost.Connections.GetConnection('mydb'); if LProvider = nil then begin FHost.Log.Error('Verbindung "mydb" nicht konfiguriert'); Exit; end; // Cast auf den konkreten Provider-Typ TWvdSSQLDBProvider(LProvider).FillWithParams(FDataSet, SSqlExecTree, []); end; **Wichtig:** Der Provider ist eine Borrowed Reference. Das Add-in darf ''Free'' darauf **nicht** aufrufen. Der Connection Manager verwaltet den Lebenszyklus — beim Herunterfahren des Hosts werden alle Provider-Instanzen automatisch freigegeben. ===== Eigene Provider-Factory registrieren ===== Add-ins können eigene Provider-Typen bereitstellen, indem sie eine ''IWvdSProviderFactory'' implementieren und beim Connection Manager registrieren. type TMyRestFactory = class(TInterfacedObject, IWvdSProviderFactory) public function GetProviderType: string; function GetConfigFields: TArray; function CheckInfrastructure: TWvdSInfraCheckResult; function CreateProvider(const AValues: TStringArray): TObject; end; function TMyRestFactory.GetProviderType: string; begin Result := 'rest'; end; function TMyRestFactory.GetConfigFields: TArray; begin SetLength(Result, 3); Result[0].Name := 'BaseUrl'; Result[0].TypeName := 'string'; Result[0].Description := 'REST API Basis-URL'; Result[0].DefaultValue := ''; Result[1].Name := 'ApiKey'; Result[1].TypeName := 'string'; Result[1].Description := 'API-Schlüssel'; Result[1].DefaultValue := ''; Result[2].Name := 'Timeout'; Result[2].TypeName := 'integer'; Result[2].Description := 'Timeout in Sekunden'; Result[2].DefaultValue := '30'; end; function TMyRestFactory.CheckInfrastructure: TWvdSInfraCheckResult; begin Result.Available := True; Result.DriverVersion := ''; Result.Message := 'REST-Provider benötigt keine lokale Infrastruktur'; end; function TMyRestFactory.CreateProvider( const AValues: TStringArray): TObject; begin Result := TMyRestProvider.Create( AValues[0], AValues[1], StrToIntDef(AValues[2], 30)); end; Registrierung im ''Activate'': procedure TMyPlugin.Activate(const AContext: IExtensionContext); begin AContext.Subscribe( FHost.Connections.RegisterFactory(TMyRestFactory.Create) ); end; Danach können alle Add-ins Verbindungen vom Typ ''rest'' über ''GetConnection'' abrufen, sofern eine entsprechende Verbindung in der ''user.config.json'' konfiguriert ist. ===== Konfiguration ===== Verbindungen werden in der ''user.config.json'' (''~/.wvdsx/settings.json'') im ''connections''-Array konfiguriert. Jeder Eintrag hat drei Felder: ^ Feld ^ Typ ^ Beschreibung ^ | ''name'' | string | Eindeutiger Verbindungsname, über den Add-ins zugreifen | | ''provider'' | string | Provider-Typ (muss einer registrierten Factory entsprechen) | | Weitere Felder | je nach Provider | Benannte Feldwerte gemäß ''GetConfigFields'' der zuständigen Factory | ==== Beispiel: MSSQL-Verbindung ==== { "connections": [ { "name": "WIS", "provider": "mssql", "server": "localhost", "database": "WIS", "integratedSecurity": "true", "trustCertificate": "true", "timeout": "30" }, { "name": "archive", "provider": "mssql", "server": "dbserver.local\\INST2", "database": "WIS_ARCHIVE", "integratedSecurity": "false", "trustCertificate": "false", "timeout": "60", "username": "appuser", "password": "secret" } ] } Die Felder der eingebauten ''mssql''-Factory: ^ Feld ^ Typ ^ Beschreibung ^ Default ^ | ''server'' | string | Servername oder Instanz | ''localhost'' | | ''database'' | string | Datenbankname / Catalog | (leer) | | ''integratedSecurity'' | boolean | Windows-Authentifizierung | ''true'' | | ''trustCertificate'' | boolean | Serverzertifikat vertrauen | ''true'' | | ''timeout'' | integer | Verbindungs-Timeout in Sekunden | ''30'' | | ''username'' | string | SQL-Login (nur bei ''integratedSecurity=false'') | (leer) | | ''password'' | secret | SQL-Passwort | (leer) | ===== Infrastruktur-Prüfung ===== Bevor eine Verbindung hergestellt wird, kann ein Add-in prüfen, ob die benötigte Infrastruktur vorhanden ist. Für ''mssql'' prüft der Host, ob ODBC Driver 17 oder 18 installiert ist. procedure TWISPlugin.CheckDrivers; var LResult: TWvdSInfraCheckResult; begin LResult := FHost.Connections.CheckInfrastructure('mssql'); if LResult.Available then FHost.Log.Info('MSSQL-Treiber verfügbar: ' + LResult.DriverVersion) else begin FHost.Log.Error('MSSQL-Treiber fehlt: ' + LResult.Message); FHost.Notifications.ShowError( 'ODBC Driver 17/18 ist nicht installiert. ' + 'Bitte installieren Sie den Microsoft ODBC Driver für SQL Server.'); end; end; ==== Verbindungstest ==== Der Verbindungstest geht einen Schritt weiter: Er baut die Verbindung tatsächlich auf und misst die Antwortzeit. procedure TWISPlugin.TestWISConnection; var LResult: TWvdSConnectionTestResult; begin LResult := FHost.Connections.TestConnection('wis'); if LResult.Success then FHost.Log.Info(Format('Verbindung "wis" erfolgreich (%d ms)', [LResult.LatencyMs])) else FHost.Log.Error('Verbindung "wis" fehlgeschlagen: ' + LResult.Message); end; ===== Fehlerbehandlung ===== ^ Situation ^ Verhalten ^ | Verbindungsname nicht in ''user.config.json'' | ''GetConnection'' gibt ''nil'' zurück | | Provider-Typ ohne registrierte Factory | ''GetConnection'' gibt ''nil'' zurück, Log-Eintrag auf Error-Level | | Infrastruktur fehlt (z.B. ODBC-Treiber) | ''CheckInfrastructure'' gibt ''Available = False'' zurück | | Verbindungsaufbau schlägt fehl | ''TestConnection'' gibt ''Success = False'' mit Fehlermeldung zurück | | Factory-Registrierung für existierenden Typ | Überschreibt die bestehende Factory (letzte Registrierung gewinnt) | Weiter zur [[services|Service-Referenz]] oder zurück zur [[start|SDK-Übersicht]].