Salesforce Marketing Cloud – Mai 2020 Release Highlights
12. Juni 2020Digitalmarketing und Leadgenerierung im B2B
17. Juni 2020Mit SSJS komplexe Projekte in Salesforce Marketing Cloud realisieren
Von Design Pattern zum Test-Driven-Development
Modernes Web-Development benötigt eine gute Code-Struktur und sollte mit einer testgetriebenen Entwicklungsweise einhergehen. Die zu schreibenden Tests erreichen dabei idealerweise eine Testabdeckung von 100 Prozent – nur so ist sichergestellt, dass die Software weiterhin funktioniert, wenn der Code in den Funktionen (Anpassung der Anforderungen, Refactoring, …) angepasst wird. Gängige Programmiersprachen in Verbindung mit entsprechenden Frameworks erleichtern Entwicklern dabei die Arbeit erheblich und liefern entsprechende Test-Frameworks gleich mit. Bei Publicare nutzen wir solche Test-Frameworks in den Bereichen Laravel (PHP, PHPUnit), vue.js, APEX (Salesforce) und golang.
Wer beispielsweise in Salesforce Sales Cloud Deployment-Prozesse unter Einbindung von APEX-Code anstoßen möchte, der kommt nicht daran vorbei, Tests zu schreiben. Wird eine minimale, von Salesforce vorgegebene Testabdeckung nicht eingehalten oder schlagen die Tests fehl, wird das Deployment unterbunden.
Anders verhält es sich in Salesforce Marketing Cloud. Die dortigen Programmiersprachen AMPScript und SSJS sowie die zur Verfügung stehenden Plattform-Bibliotheken kennen keine Unterstützung einer testgetriebenen Entwicklung. Auch gibt es keinerlei Anforderungen, Tests schreiben zu müssen, weder innerhalb von CloudPages noch von Automation-Scripts. Mag sein, dass der zu schreibende Code innerhalb der Plattform theoretisch als wenig komplex angesehen wird. Praktisch betrachtet hat aber eine Entwicklung in Salesforce Marketing Cloud einem professionellen Mindestanspruch an Herangehensweise und Qualität zu folgen. Das hat mehrere Gründe:
- Fehler in Prozessen führen zu potentiellen Datenverlusten, beispielsweise von Newsletter-Registrierungen, Listen-Abmeldungen oder Übertragungen an Drittsysteme.
- Schlecht strukturierter Code kann den Änderungen der Kundenanforderungen nur schlecht bis gar nicht mehr folgen und muss komplett neu entwickelt werden.
- Flaschenhälse in der Performance sind in einem unsauberen „Spaghetti-Code“ nur schwer aufzuspüren.
- Die Wartung solcher Code-Ungetüme ist aufwändig und kostenintensiv.
Um das alles zu vermeiden beziehungsweise das Risiko möglichst gering zu halten, setzen wir in Salesforce Marketing Cloud ebenfalls auf eine testgetriebene Entwicklung. Was davon aber lässt sich innerhalb der Plattform umsetzen und vor allem: Wie kann man dem eigenen Anspruch an Professionalität mit SSJS gerecht werden – einer Sprache, die auf der im Dezember 1999 veröffentlichten ECMA3-Spezifikation basiert, also einem Relikt aus dem letzten Jahrtausend? Diese Frage hat uns als Digitalmarketingagentur seit unserem ersten Projekt auf Salesforce Marketing Cloud immer wieder beschäftigt. Nach und nach haben wir in diesem Bereich einige Design Patterns adaptiert und plattformspezifisch umgesetzt. Die folgenden Kernelemente haben uns letztlich eine testgetriebene Entwicklung ermöglicht.
DRY – Don’t Repeat Yourself
Code strukturieren wir in der Marketing Cloud objektorientiert, allerdings ohne weiterführende Vererbung. Objekte sind in dem Fall als Cluster von Methoden zu verstehen und sollen die Paradigmen bzw. Ansätze beim Lesen des Codes besser verständlich machen. Ein Beispiel: Wir verwenden häufig ein sogenanntes Controller-Objekt (von uns angelegte Objekte beginnen im Code mit einem „_“, um Namenskonfikte im späteren Verlauf zu vermeiden). Der Controller hat Funktionen inne, um Requests zu verarbeiten (Parameter auslesen, Parameter gegen eine Data Extension mappen, etc.). Da wir dieses Objekt in den CloudPages, welche wir in Projekten aufsetzen, öfter verwenden, verzichten wir darauf, das Objekt in jeder CloudPage neu zu implementieren. Bei einem Bug oder einer anders motivierten Anpassung müssten wir andernfalls jede CloudPage manuell bearbeiten, was Zeit und Nerven kostet.
Stattdessen implementieren wir den wiederverwendbaren Code in Code Snippets und sorgen dafür, dass er in die CloudPages importiert wird. Anpassungen an dem Code müssen wir so nur noch an einer Stelle vornehmen. Die Importe sind vergleichbar mit dem Pendant in golang:
import (
„fmt“
„github.com/badoux/checkmail“
„github.com/devfacet/gocmd“
)
oder dem Pendant in PHP
use App\Abstracts\DataExtensionDescriptionJob;
use App\BusinessUnit;
use App\Data Extension;
use App\MarketingCloud\Read\DataExtensionReader;
Dies ist auch der erste Schritt zur Testbarkeit von Code. Würden wir ausschließlich mit ‚Copy & Paste‘ arbeiten, um gleichen Code zu verteilen, könnten wir dabei Fehler nicht ausschließen und wüssten nicht, wie ein professionelles codebasiertes Testszenario aussehen müsste.
Make It Configurable
Angenommen man müsse eine Methode schreiben, um Kontaktdaten aus einer Data Extension zu filtern. Im SOAP-Kontext ist es dafür notwendig, die abzufragenden Spalten explizit anzugeben. Das ist weiter kein Problem, denn die Methode ist nicht schwer zu implementieren. Die Spalten ID, EmailAddress, FirstName und LastName schreibt man als Array in die Abfrage hinein, den CustomerKey der Data Extension ebenfalls. Im nächsten Schritt aber transferiert man den geschriebenen Code in eine weitere Business Unit des Kunden, weil dieser dort ebenfalls auf die Funktionalitäten zugreifen möchte. In dieser zweiten Business Unit sind die abzufragenden Spalten allerdings ID, EmailAddress und City und natürlich ist der CustomerKey der Data Extension ein anderer. Ab diesem Zeitpunkt hätte man eine Methode mit zwei verschiedenen Rümpfen über die verschiedenen Business Units verteilt, obwohl die Absicht der Methode immer die gleiche ist – das sind codeseitig betrachtet zwei Gleise. Eventuell kommen noch n weitere Business Units hinzu.
Aus Wartungssicht ist diese Herangehensweise eher unklug. Besser ist es, mit Variablen zu arbeiten. Sowohl der CustomerKey der Data Extension als auch die abzufragenden Spalten werden zu Konfigurationsvariablen im Code Snippet, der Rumpf der Methode bleibt gleich. Ein Deployment auf andere BUs bedeutet, dass pro BU nur noch Konfigurationsvariablen angepasst werden müssen, während der Rest übernommen werden kann.
Genau diese Konfigurationsvariablen sind ein entscheidender Baustein, um in der testgetriebenen Entwicklung innerhalb von Salesforce Marketing Cloud auch „Datenbankoperationen“ testen zu können.
Test-Driven-Development
Die Umsetzung der ersten zwei Patterns „DRY – Don’t Repeat Yourself“ und „Make It Configurable“ ermöglicht es, auch das sogenannte „Test-Driven-Development“ bestmöglich zu adaptieren. Da uns kein Terminal zur Verfügung steht, weichen wir dazu auf die Implementierung innerhalb von CloudPages aus und geben unsere Test Cases visuell aus – alles grün, alles gut.
Mit dem ersten Pattern „DRY – Don’t Repeat Yourself“ sind wir in der Lage, unsere schon von den anderen CloudPages genutzte Codebase auch in die Test Suite-CloudPage einzubinden. Ändert sich etwas an der Codebase, müssen die Tests weiterhin erfolgreich sein – oder die Anpassungen sind eben noch fehlerhaft. Übrigens: Um eben solche Anpassungen durchführen zu können, ohne den Livebetrieb zu stören, werden die Codebase-Elemente von uns versioniert abgespeichert. Eine neue Version muss erst durch die Test Suite bestätigt werden, dann kann sie von den Controllern eingebunden werden.
Da wir die URL der Test Suite-CloudPage weitergeben und die Tests zu jeder Zeit von jedem Projektbeteiligten angestoßen werden können sollen, wäre es undenkbar, data-extension-basierte Tests gegen die „Live-Datenbank“ zu fahren. Leider aber kennt Salesforce Marketing Cloud das Konzept von „Live-Datenbank“ vs. „Test-Datenbank“ nicht. Es gibt derartige Unterschiede schlichtweg nicht. Hier kommt uns das Pattern „Make It Configurable“ zur Hilfe. Bevor wir Methoden in der Test Suite für Tests aufrufen, sorgen wir dafür, dass die Konfigurationsvariablen der genutzten Data Extensions überschrieben werden – statt der potentiell produktiven Data Extensions nutzen wir schon während der Entwicklung ein Abbild der Data Extensions zu Testzwecken. Diese Herangehensweise ist zudem ein sehr nützliches Werkzeug, um Prozesse zu debuggen, ohne den Livebetrieb zu stören.
Der Rest ist dann die „übliche“ Entwicklungsarbeit: Methoden schreiben, Tests dafür entwickeln und durchführen. Aus unserer Sicht ist gerade diese Art der Entwicklung eine äußerst beruhigende. Je besser wir Tests schreiben, desto sicherer können wir sein, dass unsere Software ihren Zweck erfüllt. Testen wir allerdings komplexe Prozesse manuell (was lange Zeit nötig war, bevor wir die Werkzeuge in Salesforce Marketing Cloud geschaffen hatten), so ist dies zum einen nervenaufreibend (da es oft eine Menge Klickarbeit erfordert) und zum anderen vor allem zeitaufwändig, denn bei jeder Anpassung muss der Test von vorne beginnen, um mögliche Querbeeinflussungen zu betrachten.
Wie aber mit Sonderlogiken in einzelnen Business Units verfahren?
Eine potentielle Schwachstelle, wenn man business-unit-übergreifend Methoden modelliert, sind die Ausnahmen von der Regel. Damit wir trotz Sonderlogiken business-unit-übergreifend Methoden modellieren können, haben wir uns für die Implementierung von Event-Objekten entschieden. Ein Event – oder auch Ereignis – kann in einer Methode stattfinden. Es besteht aus einem Event-Namen und einem Event-Datenobjekt, beides wird aus der Methode heraus an die fire-Methode des Events mitgegeben. Aus Methodensicht ist nichts weiter notwendig. Ein Beispiel:
_Event.fire(„Contact.subscribed“, {Contact: Contact})
Wenn der Prozess an diesem Punkt in Business Unit A abgeschlossen ist, während in Business Unit B die Anforderung besteht, eine Willkommens-E-Mail an den Kontakt zu schicken, ist die gemeinsame Nutzung des Methoden-Codes trotzdem möglich. In Business Unit B kommt nun nur weiterer Code hinzu, ein sogenannter Listener, der in einem zusätzlichen Code Snippet steht. Ein Listener ist eine Funktion, die auf ein Ereignis mit einem bestimmten Namen reagieren kann. Jedes Ereignis kann beliebig viele Listener haben. Im Code Snippet muss der Listener mit seiner Callback-Funktion am entsprechenden Event registriert werden:
var listenerFunction = function(eventData) {
var Contact = eventData.Contact,
//… logic here
}
_Event.addListener(„Contact.subscribed“, listenerFunction)
Und genau in diesem Listener wird eine Willkommens-E-Mail an die Adresse aus dem übergebenen Kontakt ausgelöst.
Das Prinzip mag simpel erscheinen, schafft aber viel Spielraum, um Code business-unit-übergreifend zu modellieren, indem man die Gemeinsamkeiten herausarbeitet und Besonderheiten event-basiert behandeln kann.
Ausblick
Neben den hier beschriebenen Patterns haben wir auch eine Reihe von weiteren Arbeitsweisen erarbeitet. Diese beeinflussen die Herangehensweise, mit der wir individuelle Erweiterungen in Salesforce Marketing Cloud umsetzen. Dabei haben wir stets den Anspruch, eine professionelle, zeitgemäße und vor allem stabile Implementierung durchzuführen.
Ein gut strukturierter Code ist die Basis für jeden effektiven Prozess. Auch Projekte in der Salesforce Marketing Cloud sind wiederkehrenden Änderungsanforderungen von Kundenanforderungen gewachsen, wenn sie übersichtlich codiert sind – dies erleichtert zudem die Wartung und Optimierung solcher Prozesse enorm.
Wenn Sie mehr zum Thema erfahren wollen oder professionelle Services dazu suchen, stehen wir Ihnen gern mit Rat und Tat zur Seite.