Umgebungsabhängige Deployments in Fabric SQL Database (ohne SQLCMD-Variablen)

Wer schon mit SQL Server Database Projects in einem klassischen CI/CD-Setup gearbeitet hat, hat das Problem „In welche Umgebung deploye ich gerade?“ wahrscheinlich mit SQLCMD-Variablen gelöst. Man definiert eine Variable $(Environment), setzt sie in separaten Publish-Profilen auf DEV, TEST oder PROD und verzweigt im Post-Deployment-Skript danach. Einfach und zuverlässig.

Dann wechselt man zu SQL Database in Microsoft Fabric, und dieses Muster funktioniert nicht mehr.

Das Problem

Fabric SQL Database bringt eine integrierte Git-Anbindung und Deployment-Pipelines mit. Unter der Haube wird weiterhin ein SQL-Projekt (.sqlproj) verwendet, aber diese Datei gehört Fabric. Manuelle Änderungen an der .sqlproj im Repository werden beim nächsten Commit von Fabric in die Versionskontrolle zurückgesetzt. Ein Publish-Profil, das man bearbeiten könnte, gibt es ebenfalls nicht, denn man führt sqlpackage nicht selbst aus. Das Deployment übernimmt Fabric.

Das bedeutet: keine eigenen SQLCMD-Variablen und kein einfacher Weg, den Skripten mitzuteilen, in welcher Umgebung sie laufen.

Die gute Nachricht: Fabric unterstützt inzwischen Pre- und Post-Deployment-Skripte als Teil seiner integrierten CI/CD-Funktionen. Man legt eine Abfrage unter Shared Queries an, öffnet deren Menü und wählt Set as Post-deployment Script. Ab dann wird sie automatisch ausgeführt, sobald die Datenbank aus Git aktualisiert oder über eine Deployment-Pipeline bereitgestellt wird.

Wir haben also einen Hook, der bei jedem Deployment läuft. Was noch fehlt, ist eine Möglichkeit für das Skript, herauszufinden, wo es gerade ausgeführt wird.

Die Datenbank fragen, wo sie zu Hause ist

Jede Umgebung in Fabric liegt typischerweise in einem eigenen Workspace: einer für Dev, einer für Test, einer für Prod. Wenn die Datenbank uns sagen kann, zu welchem Workspace sie gehört, können wir danach verzweigen.

Und das kann sie tatsächlich. Schauen wir uns an, was SERVERPROPERTY('ServerName') in einer Fabric SQL Database zurückgibt:

sql

SELECT SERVERPROPERTY('ServerName') AS ServerName;

Das Ergebnis sieht etwa so aus:

3f7a1c92-5b4e-4d8a-9c21-7e6f0b8d4a15-a91d4e27-0c6b-4f3e-8b75-2d9c1e6f8a03.database.fabric.com

Der Servername besteht aus zwei GUIDs, die durch einen Bindestrich verbunden sind, gefolgt vom Suffix .database.fabric.com:

<tenant-id>-<workspace-id>.database.fabric.com
3f7a1c92-5b4e-4d8a-9c21-7e6f0b8d4a15 - a91d4e27-0c6b-4f3e-8b75-2d9c1e6f8a03 .database.fabric.com
└──────────── Tenant-ID ─────────────┘ └─────────── Workspace-ID ────────────┘

Die erste GUID ist die ID deines Microsoft-Entra-Tenants. Sie ist für alle Datenbanken in deiner Organisation gleich und hilft daher nicht dabei, Umgebungen zu unterscheiden. Die zweite GUID ist die ID des Fabric-Workspaces, in dem die Datenbank liegt, und genau das ist der Fingerabdruck der Umgebung, den wir suchen.

Tipp: Um herauszufinden, welche Workspace-ID zu welcher Umgebung gehört, öffne den jeweiligen Workspace im Fabric-Portal und sieh dir die URL an: app.fabric.microsoft.com/groups/<workspace-id>/.... Das ist dieselbe GUID, die im Servernamen auftaucht.

Die Workspace-ID extrahieren

Statt den kompletten Servernamen zu vergleichen, extrahiert man besser die Workspace-ID. Jede GUID ist 36 Zeichen lang. Die Tenant-ID belegt also die Positionen 1 bis 36, und die Workspace-ID beginnt an Position 38 (nach dem trennenden Bindestrich):

sql

DECLARE @server NVARCHAR(256) = CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(256));

SELECT
    LEFT(@server, 36)          AS TenantId,
    SUBSTRING(@server, 38, 36) AS WorkspaceId;

Verzweigen im Post-Deployment-Skript

Jetzt lässt sich ein Post-Deployment-Skript schreiben, das sich je nach Workspace unterschiedlich verhält:

sql

DECLARE @workspaceId CHAR(36) =
    SUBSTRING(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(256)), 38, 36);

IF @workspaceId = 'a91d4e27-0c6b-4f3e-8b75-2d9c1e6f8a03'
BEGIN
    PRINT 'Production workspace detected';
    -- Logik nur für Produktion
END
ELSE IF @workspaceId = '5c2e8b14-9f7a-4d61-a3c8-0e4b7d2f9a6c'
BEGIN
    PRINT 'Test workspace detected';
    -- Testdaten laden, Diagnose aktivieren usw.
END
ELSE
BEGIN
    PRINT 'Development or unknown workspace';
    -- Sicheres Standardverhalten
END

Achte auf den ELSE-Zweig. Er ist wichtiger, als er aussieht, wie wir gleich sehen werden.

Worauf man achten sollte

Workspace-IDs sind nicht dauerhaft. Wird ein Workspace gelöscht und neu angelegt, bekommt er eine neue ID, und dein Skript landet stillschweigend im ELSE-Zweig. Das sollte man bei jeder Umstrukturierung von Workspaces im Hinterkopf behalten.

Branch-out erzeugt neue Workspaces. Die Funktion „Branch out to a new workspace“ in Fabric gibt jedem Feature-Branch einen eigenen Workspace mit eigener ID. Diese kann man nicht im Voraus auflisten, daher muss sich der Fallback-Zweig sicher verhalten. Unbekannte Workspaces wie Entwicklungsumgebungen zu behandeln, ist meist die richtige Wahl. Ein unbekannter Workspace darf niemals im Produktionsverhalten landen.

Vollständige GUIDs vergleichen. Es ist verlockend, LIKE '%a91d4e27%' zu schreiben, aber Teilübereinstimmungen laden zu subtilen Fehlern ein. Extrahiere die exakte Workspace-ID und vergleiche sie auf Gleichheit.

Das Ganze hängt vom Format des Servernamens ab. Microsoft dokumentiert dieses Format nicht als verbindliche Schnittstelle, es könnte sich also künftig ändern. Halte die Parsing-Logik an einer einzigen Stelle, damit sie sich im Fall der Fälle leicht anpassen lässt.

Eine robustere Alternative: eine Konfigurationstabelle

Wem hartcodierte Workspace-IDs zu fragil erscheinen, der kann eine nützliche Eigenschaft des Fabric-Deployment-Modells ausnutzen: Das Schema wird aktualisiert, vorhandene Daten bleiben aber unangetastet.

Definiere im Projekt eine Konfigurationstabelle, sodass ihr Schema versioniert ist:

sql

CREATE TABLE dbo.EnvironmentConfig (
    ConfigKey   NVARCHAR(50)  NOT NULL PRIMARY KEY,
    ConfigValue NVARCHAR(200) NOT NULL
);

Füge dann manuell und einmalig pro Workspace den Umgebungswert ein:

sql

INSERT INTO dbo.EnvironmentConfig (ConfigKey, ConfigValue)
VALUES ('Environment', 'PROD');

Diese Zeile existiert nur in dieser Datenbank. Sie liegt nicht in Git, und Deployments rühren sie nicht an. Eine wichtige Regel: Dieses INSERT gehört nicht ins Post-Deployment-Skript. Das Skript läuft überall identisch und würde deine umgebungsspezifischen Werte überschreiben.

Beide Ansätze kombinieren

Man kann auch beides nutzen: zuerst aus der Konfigurationstabelle lesen und, falls sie leer ist, auf die Workspace-ID zurückfallen. So werden auch frisch abgezweigte Workspaces abgedeckt, die noch nicht konfiguriert sind:

sql

DECLARE @env NVARCHAR(200) =
    (SELECT ConfigValue FROM dbo.EnvironmentConfig WHERE ConfigKey = 'Environment');

IF @env IS NULL
BEGIN
    DECLARE @workspaceId CHAR(36) =
        SUBSTRING(CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(256)), 38, 36);

    SET @env = CASE @workspaceId
        WHEN 'a91d4e27-0c6b-4f3e-8b75-2d9c1e6f8a03' THEN 'PROD'
        WHEN '5c2e8b14-9f7a-4d61-a3c8-0e4b7d2f9a6c' THEN 'TEST'
        WHEN 'e8b3f607-1d4c-4a92-b5e7-6c0a9f3d2b81' THEN 'DEV'
        ELSE 'DEV'
    END;
END

PRINT CONCAT('Deploying to: ', @env);

Fazit

Die verwaltete Git-Integration von Fabric nimmt einem die SQLCMD-Variablen, aber nicht die Möglichkeit, umgebungsabhängige Deployments zu schreiben. Der Servername jeder Fabric SQL Database folgt dem Muster <tenant-id>-<workspace-id>.database.fabric.com und gibt deinen Skripten damit einen zuverlässigen Weg, zu erkennen, in welchem Workspace sie laufen. Zusammen mit den datenerhaltenden Deployments von Fabric, die eine Konfigurationstabelle praktikabel machen, hast du zwei solide Bausteine. Wähle den, der zu deinem Workflow passt, oder kombiniere beide, und sorge dafür, dass unbekannte Umgebungen immer auf ein sicheres Verhalten zurückfallen.