08. April 2020, Benjamin Rosenberger

mein-handel.at – ein Magento2 Multishop in unter 7 Tagen

wie ein Aprilscherz Wirklichkeit wurde

mein-handel.at – ein Magento2 Multishop in unter 7 Tagen

Magento2 ist nun bereits in das Erwachsenen-Alter eingetreten mit der Version 2.3. Ich bzw. wir haben etliche sehr große Shops bereits umgesetzt und doch konnte ich noch nie die wahre Power des Magento-Untergrunds bis zur Gänze ausreizen.

Am 1. April war es soweit, Bernhard kam mit der Idee zur Umsetzung eines Multiwebsite-Shops für potenziell alle Händlerinnen und Händler OÖ. Zunächst dachten andere und ich an einen Aprilscherz durch die knappe Deadline (nicht einmal eine ganze Woche!). Nach kurzer Überlegung bin ich zum Schluss gekommen, dass Magento 2 Community Edition nun seine schier unendlichen Möglichkeiten ausspielen kann und wir im Team es schaffen werden.

Die aktuelle Home-Office Herausforderung kam für mich dann noch zusätzlich zum Tragen – mit vier Kindern, Hunden und Pferd musste ich mir einen Ort der „Ruhe“ gestalten. Bekanntlich entstehen die besten Ideen und Projekte ja in Garagen. Hier ein Eindruck von meinem „Mein-Handel-Home-Office-Fokus-Arbeitsplatz“:

Die Anforderungen

Unsere Anforderungen klingen sehr einfach, haben jedoch jeweils Tücken im Detail:

  • Infrastruktuelle Anforderungen wie
    • Ausfallsicherheit
    • Lastverteilung
    • Schnelligkeit
    • ein lokales Entwickeln der gesamten Umgebung muss möglich sein

  • Es soll einen eigenen Shop geben, indem man einen Shop bestellen kann:
    • dieser soll ein gesondertes Menü haben, welches durchgemischt CMS- und Produktseiten beinhaltet
    • eine Übersicht über alle vorhandenen Shops inkl. Details (Name, Kategoriebranche, Logo,…) sollen angezeigt und durchsuchbar sein
    • eine Produktsuche über alle vorhandenen Händlershops soll implementiert werden

  • Es soll viele Händlershops geben, welche
    • einfachst aufgesetzt werden können
    • mit minimalen Einstellungen eine größtmögliche Variation für die Händlerin und den Händler schaffen (Kategorien, Produkte,…)
    • eine einfache Wartungsoberfläche für Produkte und andere Dinge haben
    • als Unterpfad zur Hauptdomain gehostet werden
    • Daten weitgehend begrenzt auf eine Händlerin oder einen Händler haben sollte (Kategoriebaum, Produkte,…)

Die Infrastruktur

Um die Infrastruktur abbilden zu können, welche Lasten der einzelnen Shops auf mehrere Server verteilen kann, haben wir diese nach folgendem Schema aufgebaut:

Ein Proxy verteilt entsprechend - der Anfrage - URLs an die entsprechenden Magento Server. Um Bottlenecks zu vermeiden, haben wir uns entschlossen, keine zentralen Instanzen von der Datenbank, Redis und Varnish zu erstellen, sondern diese auf jeden Server für sich laufen zu lassen. Dies entspricht dem vorgeschlagenen Standard-Server-Setup (siehe dazu auch die DevDocs unter https://devdocs.magento.com/guides/v2.3/config-guide/bk-config-guide.html).

Als Konsequenz dessen ist für jede Magento2 Instanz eine andere Konfiguration vonnöten. Dies kann bereits durch die Konfigurationsmöglichkeiten von Magento2 zum Teil abgebildet werden (siehe dazu die entsprechenden DevDocs: https://devdocs.magento.com/guides/v2.3/config-guide/config/config-php.html).

Configuration as deployable code

Nur leider, wenn man sich unser Szenario weiterdenkt, sind diese 2 Dateien nicht ausreichend, um schnell und zuverlässig Konfiguration auf den Servern zu verteilen, ohne sich per SSH am Server Zugriff zu verschaffen.

Ich habe dazu zwei weitere Konfigurationsdateien eingeführt, welche sich mit den 2 bestehenden folgendermaßen definiert sind:

  • conf.php – Initialisierung der Magento Module, siehe https://devdocs.magento.com/guides/v2.3/extension-dev-guide/build/enable-module.html, Definition der Themes und eine gewisse quasi unverrückbare Grundeinstellung
  • conf.shared.php – Systemeinstellungen, welche für alle Server gleich sind, inklusive Websites, Stores und Store Views (diese sind hier immer als disabled gekennzeichnet)
  • conf.local.php – Aktivierung einzelner Store Views
  • env.php – Servereinstellung wie Datenbankverbindungen, Cache-Definitionen,….

Wir nutzen schon immer Gitlab für die Source-Verwaltung dessen CI/CD Möglichkeiten für den Deploy. Die Build-Pipelines sind entsprechend dem möglichen Magento2 Zero-Downtime-Deployment (siehe dazu die DevDocs unter https://devdocs.magento.com/cloud/deploy/reduce-downtime.html) eingerichtet und ermöglichen einen quasi nahtlosen Übergang einer Version zur nächsten (sofern keine Datenbankänderungen vorhanden sind).

Wer Magento2 kennt, der weiß, dass sich dieser Prozess am Server jedoch abhängig von der Anzahl der Themes, Sprachen, Module,… über einen relativ langen Zeitraum ziehen kann (wenige Minuten bis zu fast einer halben Stunde).

Somit sind nun die Konfigurationen auf 3 Stellen aufgeteilt:

  • config.php im Magento2 Repository
  • config.shared.php und config.local.php als eigenes Repository
  • env.php hinterlegt auf den einzelnen Servern

Dadurch ist eine Trennung von Code-Updates (10+ Minuten) und Konfigurations-Updates (< 1 Minute) möglich.

Composer them all

Zusätzlich musste beachtet werden, dass man nach wie vor lokal mit allen möglichen Stores weiterentwickeln können muss. Dies sollte automatisch passieren, um viele Fehler des manuellen Kopierens, Änderns etc. zu vermeiden.

Um dieses Problem zu lösen, setzen wir auf Möglichkeit über Composer einfache Befehle ausführen zu können (siehe dazu auch die Composer-Dokumentation unter https://getcomposer.org/doc/articles/
scripts.md#what-is-a-script-
)

Folgende Dinge können nun automatisiert gesteuert werden:

  • Unterscheidung zwischen Entwicklerumgebungen (composer update) und Serverumgebungen (composer install)
  • Installieren notwendiger Sicherheitspatches (siehe dazu https://magento.com/security/patches) sobald diese erscheinen oder ein Upgrade auf die nächste Minor-Version noch nicht möglich ist
  • Einspielen manueller Patches des Cores, welche noch nicht in das aktuelle Magento2 Release aufgenommen worden sind (etwa offene Pull Request, oder bereits für neuere Magento2 Versionen vorgesehene Pull Requests)
  • Kopieren der nötigen Konfigurationen aus den installierten Modulen

Testen aller Schritte

Gitlab’s CI/CD Funktionalitäten bieten die Möglichkeit mehrere Umgebungen und deren Abhängigkeiten zu definieren (nähere Informationen unter https://docs.gitlab.com/ee/ci/introduction/). So können alle Änderungen (sowohl Code, als auch Konfiguration) zuerst am Testsystem deployed und getestet und in weiterer Folge am Live-System ausgerollt werden.

Der Einstiegsshop

Ein Shop um einen Shop bestellen – wie sieht sowas aus? Diese Frage stellte sich unser Frontend-Team, während die Infrastruktur im entstehen war. Ebenso stellten sich viele Fragen technischer Natur:

  • Wie kann man ein Menü definieren, welches nicht auf Kategorien basierend ist? 
  • Wie kann ich mit möglichst Magento2 Funktionen ein Formular erzeugen, welches als Bestellung abgelegt und bezahlt wird? 
  • Wie findet man alle erstellten Shops, wenn diese doch auf verschiedenen Server liegen?

Das Ergebnis lässt sich sehen und ist einfach zu bedienen.

Magento2 und das liebe Topmenu

Die Anforderung scheint so leicht:

  • Ich will direkt auf Seiten im Menü verlinken
  • Ich will direkt auf ein Produkt im Menü verlinken
  • Ich will pro Store View konfigurieren, was als Quelle für das Menü genommen werden soll

Menüs in Magento2 werden wie auch in Magento 1 Versionen von der Topmenu-Klasse (aktuelle Version ist hier zu sehen: https://github.com/magento/magento2/blob/
9544fb243d5848a497d4ea7b88e08609376ac39e/
app/code/Magento/Catalog/Plugin/Block/Topmenu.php
) erstellt. Diese ist sehr starr (generiertes HTML in PHP und nicht der entsprechenden Template-Datei) und basierend auf dem Kategoriebaum. Dinge, die zu tun waren:

  • Herauslösen der HTML Struktur in Template-Dateien
  • Herauslösen der Menübaumgenerierung und Bereitstellung mehrerer Quellen (Kategorien, etwaige andere Menüstruktur)

Magento2 und die Customizable Options

Ziel ist es mittels eines Bestellvorgangs alle notwendigen Daten zu erheben, um einen neuen Shop bereitstellen zu können. Hier bieten sich die Customizable Options an, welche eine Vielzahl von Möglichkeiten bieten:

  • ein einziges Produkt im Shop ist ausreichend, auf welches auch direkt vom Menü verlinkt werden kann
  • verschiedene Typen von Eingabefeldern wie Text, Selects, Checkboxen, File-Uploads,… 
  • speichern der Optionen und nachträgliches ändern über den Warenkorb
  • Dokumentation der benötigten Informationen zur weiteren Shop-Erstellung und Ausrollung

Einzig und allein was fehlt: Wie kann man den Kundinnen und Kunden Hilfestellungen geben, damit man korrekte Daten erhält? Und so wurde eine Tooltip-Option eingebaut, um Erklärungen zu den einzelnen Feldern bereit zu stellen.

Magento2 und die Systemkonfiguration

Was bringt mir ein Portal, wenn ich Kundinnen und Kunden nicht zu den einzelnen Shops führen kann? Somit musste eine Möglichkeit geschaffen werden, alle Rumpfdaten inklusive so mancher Mediendaten im Einstiegsshop und ebenso in den einzelnen Händlershops zugreifbar zu machen.

Der erste Punkt der Daten ist aufgrund des bereits oben erwähnten „Configuration as deployable code“ leicht zu lösen. Man kennt auf allen Servern alle Rumpfdaten eines Shops um diesen darstellen und suchen zu können.

Nun fehlen nur noch die Mediendaten (wie zB Logos). Das Prinzip hier ist jedoch das gleiche wie bei Konfigurationen, welche über die Datei config.shared.php auf allen Server verteilt werden. Somit musste nur das Deploy-Skript um diese neuen Dateien erweitert werden und schon hat jeder Server alle nötigen Daten.

Die Händlershops

Nun, da neue Shops bestellt werden können, müssen diese auch entsprechende Grundfunktionen beinhalte, welche in folgenden Anforderungen definiert sind:

  • jeder Shop kann unterschiedliche Kategorien haben (jede Änderung einer Kategorie darf sich nur auf diesen einen Shop auswirken)
  • jeder Shop kann unterschiedliche Versand- und Bezahlmethoden haben
  • alle Shops sollen als Pfade hinter der Hauptdomain erreichbar sein
  • es sollen standardisierte Seiten und Blöcke genutzt werden, um die Setup-Zeit zu verringern – jegliche Anpassungen können in weiteren Schritten vorgenommen werden
  • die Händlerin oder der Händler muss möglichst einfach seine Produkte einpflegen und warten können
  • wir als Magento2 Betreiber müssen zusätzlich zur Konfiguration Einstellungen für die Kundin oder den Kunden im Original-Backend machen können

Magento2 und die seine Root-Kategorien

Dadurch, dass alle Shops unterschiedliche Kategoriebäume haben können und sich gegenseitig nicht beeinflussen dürfen, wird man in Magento2 bereits deutlich eingeschränkt. Somit ist uns klar geworden, wir brauchen pro Shop eine Root-Kategorie, welche dann auf Store Ebene gesetzt werden kann.

Mit der zusätzlichen Anforderung unterschiedlicher Versand- und Bezahlmethoden blieb nur noch die Möglichkeit, jeden Shop als eigene Website in Magento2 abzubilden.

Magento2 und mehrere Websites als Subpfad

Warum die Seiten als Subpfad verwaltet werden sollen, hat vorrangig zwei Gründe:

  • ohne Änderung der Routinginformationen Proxy kann ein neuer Shop erstellt werden (ginge auch mit Subdomains)
  • man kann die Subpfade so gestalten, dass diese auch SEO-technisch vorteilhaft sind.

Um den SEO-Teil zu erledigen, wurde die Struktur der Pfade mit dem Bezirkskürzel und dem Unternehmensnamen aufgebaut.

Soweit so gut, grundsätzlich einstellbar über die jeweilige Base-URL der Website, doch dann fingen die Probleme an:

  • Subpfade werden an die entsprechende index.php als Seite weitergegeben und resultieren in 404-Seiten des Default-Stores
  • erstellt man die Subpfade, muss man auch die entsprechenden Dateien und Ordner verlinken, sodass Magento2 als solches wieder aufgerufen wird
  • setzen der entsprechenden Website-Codes, damit Magento2 die korrekte Seite ausliefert.

Zusätzlich musste bei einem Code- bzw. Konfigurations-Deploy diese Ordnerstruktur erstellt und getestet werden.

Für die Konfiguration habe ich im Konfigurations-Repository eine neue Datei hinterlegt, welche die Information von Subpfad zu Website-Code beinhält. Ein einfaches zusätzliches Script liest diese Information aus und erstellt alle benötigten Ordner und Links im entsprechenden pub/-Verzeichnis.

Magento2 und WYSIWYG-Variablen

Magento2 hat bereits viele vorgefertigte Variablen, die in CMS-Pages und CMS-Blöcke eingefügt werden können. Diese mit allen nötigen Informationen zu erweitern, ist ein leichtes, wenn man das richtige Core Modul kennt.

Hier ist es besonders vorteilhaft, dass die gesamte Shop-Information als Konfiguration gespeichert ist.

Diese können leicht nutzbar gemacht werden, wie es in folgenden DevDoc-Artikel beschrieben ist: https://devdocs.magento.com/guides/v2.3/extension-dev-guide/variable-pool/

Magento2 und die Produktwartung

Man darf ruhig ehrlich sein: das Magento2 Backend ist für Einsteigerinnen und Einsteiger kompliziert und wenig intuitiv. Die Anforderung hier ist nun folgende:

  • Alle nicht benötigten Funktionen müssen entfernt werden
  • Jede Benutzerin und jeder Benutzer darf nur die ihr oder ihm zugewiesene Website bearbeiten können.
  • Die Händlerin oder der Händler darf sich bei der Produktpflege nicht von den vielen Möglichkeiten ablenken lassen, die wir noch gar keine Beachtung im Frontend geschenkt haben.

Dies sind nun 3 große Problemfelder, die alle in der kurzen Zeit gelöst werden wollten.

Magento2 und die Rechteverwaltung

Das war der leichteste Punkt. Magento2 bietet mit seinem Rollenmanagement, eine einfache und sehr mächtige Möglichkeit Benutzerinnen und Benutzer auf die Funktionsbereiche einzuschränken, die sie benötigen. Problem 1 ist gelöst. Diese sind aktuell sehr eingeschränkt auf:

  • Produkte erstellen, ändern und löschen
  • Kategorien ansehen, ändern und löschen

Magento2 und Mehrmandatenfähigkeit

Aktuell ist dies ein Feature der Enterprise-Edition und trotzdem wollte ich es nicht nicht glauben, dass dies in der Community Version nicht mit geringen Aufwand nachzubauen wäre und so habe ich mich entsprechend aufgemacht, um diese Funktion auch mittels Modul bereitzustellen.

Folgende Erweiterungen sind davon betroffen:

  • Hinzufügen einer Website-Zuordnung bei einer Backend-Benutzerin oder einem Backend-Benutzer
  • Einschränkung aller Website-Aufrufe, um nur die zugewiesene Website zu erhalten
  • Filterung aller Produktanfragen (Listen, Detailansicht,…)
  • Filterung aller Kategorieanfragen (Listen, Detailansicht, Bäume,…)

Magento2 und die unwissenden Editoren

Um die Produktwartung so einfach wie nur möglich zu gestalten, sahen wir uns nicht in der Lage das aktuelle Admin-Backend zu verschlanken, damit jeder alles findet. Zusätzlich müssten wir alle nicht erlaubten Felder entfernen oder irgendwie über Rechte verbieten. Dies schien uns zu aufwendig, um in der kurzen Zeit eine sinnvolle Lösung zu erreichen. Die Idee ein eigenes Backend-Light zu entwerfen war geboren:

  • Produktwartung rein über API Aufrufe
  • Beschränkung auf die aktuell unterstützten Elemente
  • modernes Frontend, welches übersichtlich und leicht zu bedienen ist

Auch hier waren etliche Herrausforderungen zu meistern:

  • Bereitstellung einer modernen Web-Applikations-Infrastructure mittels NPM und Vue.js
  • Ausnutzung der extremen Vielfalt von Magento2 REST-Endpunkten (siehe auch https://devdocs.magento.com/redoc/2.3/)
  • Filterung der Endpunkte bzgl. der Website-Einschränkung
  • Deployment einer Vue.js Anwendung als Composer-Modul und ausspielen über Magento2 in unterschiedlichen Subpfaden

Magento2 adminhtml mal X

Vieles kann über Konfigurationen abgedeckt werden, doch spätestens zum Erstellen neuer Seiten für die Händlerin oder den Händler wird es schwierig. Hier musste eine Möglichkeit geschaffen werden, damit sich eine jede Berechtigte und ein jeder Berechtigter auf allen Magento2-Server einloggen kann.

Eine SSO (Single-Sign-On) Lösung musste her, welche automatisch berechtigte Benutzerinnen und Benutzer über einen weiteren Server authentifiziert und authorisiert, in Magento2 eine Benutzerin oder einen Benutzer mit der entsprechenden Rollenberechtigung anlegt und schlussendlich alle Magento2 Authentifizierungen setzt.

Noch gibt es viel zu tun

Innerhalb kürzester Zeit ist es uns gelungen durch ausgezeichnete Teamarbeit und perfekte Aufteilung der anstehenden Aufgaben diesen Aprilscherz Wirklichkeit werden zu lassen und somit dem regionalen Handel eine Möglichkeit zu bieten, in dieser Krise und danach für ihre Kundinnen und Kunden Waren im Zeitalter des Internets über einen Onlineshop anbieten zu können.

Alles Gute hat einmal ein Ende

mein-handel.at war ein Projekt der WKOÖ für den Zeitraum der Corona-Pandemie und nun ist mit heute (5. Mai 2021) offiziell zu Ende gegangen. Alle bestehenden Shops sind natürlich weiter erreichbar, bis diese nicht mehr benötigt werden.