====== 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]].