Den Code branchen

Den Code branchen

Es gibt viele Möglichkeiten, den Workflow von Code-Änderungen in einem Projekt zu organisieren – Das direkte Arbeiten am Git Master-Branch und der direkte Einsatz im Produktivbetrieb ohne Tests ist aber nicht der beste Weg.

Beim Testen geht es nicht nur um Unit- oder Funktionale Tests, sondern auch um die Überprüfung des Anwendungsverhaltens mit echten Daten. Wenn Du oder Deine Stakeholder die Anwendung genau so benutzen können, wie sie für Endbenutzer*innen bereitgestellt wird, entsteht ein großer Vorteil, der Dir ermöglicht die Anwendung mit Vertrauen deployen zu können. Es ist besonders effizient, wenn nicht-technische Personen neue Funktionen validieren können.

Wir werden der Einfachheit halber (und zur Vermeidung von Wiederholungen) die nächsten Schritte auf dem Git Master-Branch fortführen, aber mal sehen, wie das besser funktionieren könnte.

Einen Git-Workflow einführen

Ein möglicher Workflow ist die Erstellung eines Branches pro neuem Feature oder Bugfix. Das ist einfach und effizient.

Branches erstellen

Der Workflow beginnt mit der Erstellung eines Git-Branches:

1
$ git branch -D sessions-in-db || true
1
$ git checkout -b sessions-in-db

Dieser Befehl erstellt einen sessions-in-db-Branch, basierend auf dem master-Branch. Es "spaltet" den Code und die Infrastrukturkonfiguration vom master-Branch ab.

Sessions in der Datenbank speichern

Wie Du vielleicht schon am Namen des Branches erraten hast, wollen wir die Speicherung der Sessions vom Dateisystem auf (unsere PostgreSQL) Datenbank umstellen.

Die notwendigen Schritte, um dies zu verwirklichen, sind typisch:

  1. Erstelle einen Git-Branch;
  2. Aktualisiere bei Bedarf die Symfony-Konfiguration;
  3. Schreibe und/oder aktualisiere bei Bedarf etwas Code;
  4. Update the PHP configuration if needed (like adding the PostgreSQL PHP extension);
  5. Aktualisiere die Infrastruktur auf Docker und Platform.sh falls nötig (füge den PostgreSQL-Dienst hinzu);
  6. Teste lokal;
  7. Teste remote;
  8. Führe den Branch mit dem Master-Branch zusammen;
  9. Deploye in die Produktivumgebung;
  10. Lösche den Branch.

Um Sessions in der Datenbank zu speichern, ändere die session.handler_id-Konfiguration so, dass sie auf den Datenbank-DSN weist:

1
2
3
4
5
6
7
8
9
10
11
--- a/config/packages/framework.yaml
+++ b/config/packages/framework.yaml
@@ -7,7 +7,7 @@ framework:
     # Enables session support. Note that the session will ONLY be started if you read or write from it.
     # Remove or comment this section to explicitly disable session support.
     session:
-        handler_id: null
+        handler_id: '%env(DATABASE_URL)%'
         cookie_secure: auto
         cookie_samesite: lax
         storage_factory_id: session.storage.factory.native

Um Sessions in der Datenbank zu speichern, müüsen wir die sessions-Tabelle anlegen. Mach das mit Doctrine Migrations:

1
$ symfony console make:migration

Bearbeite die Datei um die Tabellenerstellung in der up()-Methode hinzuzufügen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--- a/migrations/Version00000000000000.php
+++ b/migrations/Version00000000000000.php
@@ -21,6 +21,15 @@ final class Version00000000000000 extends AbstractMigration
     {
         // this up() migration is auto-generated, please modify it to your needs

+        $this->addSql('
+            CREATE TABLE sessions (
+                sess_id VARCHAR(128) NOT NULL PRIMARY KEY,
+                sess_data BYTEA NOT NULL,
+                sess_lifetime INTEGER NOT NULL,
+                sess_time INTEGER NOT NULL
+            )
+        ');
+        $this->addSql('CREATE INDEX EXPIRY ON sessions (sess_lifetime)');
     }

     public function down(Schema $schema): void

Führe die Datenbankmigration aus:

1
$ symfony console doctrine:migrations:migrate

Teste lokal, indem Du Dir die Website anschaust. Da es keine visuellen Veränderungen gibt und wir noch keine Sessions verwenden, sollte alles wie bisher funktionieren.

Note

Wir brauchen Schritt 3 bis 5 hier nicht, weil wir die Datenbank erneut für die Session-Speicherung gebrauchen, aber das Kapitel wie man Redis gebraucht zeigt uns unkompliziert wie man einen neuen Dienst für Docker und Platform.sh hinzufügt, testet und bereitstellt.

Da die neue Tabelle nicht durch Doctrine "verwaltet" wird, müssen wir Doctrine so konfigurieren, dass die Tabelle nicht bei der nächsten Datenbankmigration entfernt wird:

1
2
3
4
5
6
7
8
9
10
11
--- a/config/packages/doctrine.yaml
+++ b/config/packages/doctrine.yaml
@@ -5,6 +5,8 @@ doctrine:
         # IMPORTANT: You MUST configure your server version,
         # either here or in the DATABASE_URL env var (see .env file)
         #server_version: '13'
+
+        schema_filter: ~^(?!session)~
     orm:
         auto_generate_proxy_classes: true
         naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware

Commit Deine Änderungen zu dem neuen Branch:

1
2
$ git add .
$ git commit -m'Configure database sessions'

Einen Branch deployen

Bevor wir zum Produktivsystem deployen, sollten wir den Branch auf der gleichen Infrastruktur wie die Production-Environment testen. Wir sollten auch sicherstellen, dass für die Symfony prod-Environment alles gut funktioniert (die lokale Website hat die Symfony dev-Environment verwendet).

Lasst uns nun eine Platform.sh-Environment erstellen, die auf dem Git-Branch basiert:

1
$ symfony cloud:env:delete sessions-in-db
1
$ symfony cloud:deploy

Dieser Befehl erstellt eine neue Environment:

  • Der Branch erbt den Code und die Infrastruktur vom aktuellen Git-Branch (sessions-in-db);
  • Die Daten stammen von der Master-Environment (auch bekannt als Production oder Produktivumgebung), und zwar durch eine Momentaufnahme aller Servicedaten, einschließlich Dateien (z. B. von Benutzer*innen hochgeladene Dateien) und Datenbanken;
  • Ein neuer dedizierter Cluster wird erstellt, um den Code, die Daten und die Infrastruktur zu deployen.

Da das Deployment den gleichen Schritten folgt wie das Deployment in die Produktivumgebung, werden auch Datenbankmigrationen durchgeführt. Dies ist gleichzeitig eine gute Möglichkeit um sicherzugehen, dass die Migrationen mit echten Daten funktionieren.

Die Nicht-master-Environments sind dermaster-Environment sehr ähnlich, bis auf einige kleine Unterschiede: So werden beispielsweise E-Mails standardmäßig nicht gesendet.

Wenn das Deployment abgeschlossen ist, öffne den neuen Branch in einem Browser:

1
$ symfony cloud:url -1

Beachte, dass alle Platform.sh-Befehle mit dem aktuellen Git-Branch arbeiten. Somit wird der Befehl die URL für den sessions-in-db-Branch aufrufen. Die URL sieht dann so https://sessions-in-db-xxx.eu-5.platformsh.site/ aus.

Teste die Website auf dieser neuen Environment. Du solltest jetzt alle Daten sehen, die Du in der Master-Environment angelegt hast.

Wenn Du weitere Konferenzen in die master-Environment hinzufügst, werden diese nicht in der sessions-in-db-Environment angezeigt und umgekehrt. Die Environments sind unabhängig und isoliert.

Wenn sich der Code auf Master weiterentwickelt, kannst Du diese Änderungen jederzeit mittels rebase in den aktuellen Branch integrieren und die aktualisierte Version deployen, wodurch die Konflikte sowohl für den Code als auch für die Infrastruktur gelöst werden.

Du kannst sogar die Daten von Master zurück in die sessions-in-db-Environment synchronisieren:

1
$ symfony cloud:env:sync

Fehler von Deployments in die Produktivumgebung vermeiden

Standardmäßig verwenden alle Platform.sh-Environments die Einstellungen der master/prod-Environment (auch bekannt als die prod-Symfony-Environment). Auf diese Weise kannst Du die Anwendung unter realen Bedingungen testen. Dies gibt Dir das Gefühl, direkt auf Produktivsystemen zu entwickeln und zu testen, aber ohne den damit verbundenen Risiken. Das erinnert mich an die guten alten Zeiten, als wir Deployments noch über FTP gemacht haben.

Wenn ein Problem auftritt, möchtest Du vielleicht auf die dev-Symfony-Environment wechseln:

1
$ symfony cloud:env:debug

Wenn Du fertig bist, gehe zurück zu den Produktiveinstellungen:

1
$ symfony cloud:env:debug --off

Warning

Aktiviere niemals die dev-Environment oder den Symfony Profiler im master-Branch; dies würde Deine Anwendung wirklich langsam machen und viele ernsthafte Sicherheitsschwachstellen öffnen.

Produktivinstallationen vor dem Deployment testen

Der Zugriff auf die zukünftige Version der Website mit echten Daten eröffnet viele Möglichkeiten: vom visuellen Regressionstest bis zum Performance-Test. Blackfire ist das perfekte Werkzeug für diese Aufgabe.

Lies den Schritt über Performance, um mehr darüber zu erfahren, wie Du Blackfire verwenden kannst, um Deinen Code vor dem Deployment zu testen.

In die Produktivumgebung mergen

Wenn Du mit den Änderungen im Branch zufrieden bist, führe den Code und die Infrastruktur wieder in den Git Master-Branch zurück:

1
2
$ git checkout master
$ git merge sessions-in-db

Und deploye:

1
$ symfony cloud:deploy

Beim Deployment werden nur die Code- und Infrastrukturänderungen zu Platform.sh übertragen; die Daten werden in keiner Weise beeinträchtigt.

Aufräumen

Entferne zum Abschluss den Git-Branch und die Platform.sh-Environment:

1
2
$ git branch -d sessions-in-db
$ symfony cloud:env:delete -e sessions-in-db
This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.