Implement complex projects in Salesforce Marketing Cloud with SSJS

From design pattern to test-driven development
Modern web development requires a good code structure and should go hand in hand with a test-driven development method. The tests to be written ideally achieve test coverage of 100 percent — this is the only way to ensure that the software continues to work when the functions of the code (adjustment of requirements, refactoring,...) are adjusted. Common programming languages combined with appropriate frameworks make work much easier for developers and provide appropriate test frameworks at the same time. At Publicare, we use such testing frameworks in the areas of Laravel (PHP, PHPUnit), vue.js, APEX (Salesforce) and golang.
For example, anyone who wants to initiate deployment processes using APEX code in Salesforce Sales Cloud cannot avoid writing tests. If a minimal test coverage specified by Salesforce is not met or if the tests fail, deployment is prevented.
It's different in Salesforce Marketing Cloud. The AMPscript and SSJS programming languages there and the available platform libraries do not support test-driven development. There are also no requirements to write tests, neither within CloudPages nor within automation scripts. It may be that the code to be written within the platform is considered to be less complex in theory. From a practical point of view, however, development in Salesforce Marketing Cloud must follow a minimum professional standard of approach and quality. There are several reasons for this:
- Errors in processes lead to potential data losses, for example from newsletter registrations, list cancellations or transfers to third-party systems.
- Poorly structured code is difficult or even unable to follow changes in customer requirements and must be developed from scratch.
- Performance bottlenecks are difficult to detect in an unclean “spaghetti code.”
- Maintaining such code monsters is complex and costly.
In order to avoid all of this and to keep the risk as low as possible, we also rely on test-driven development in Salesforce Marketing Cloud. But what of this can be implemented within the platform and above all: How can you meet your own standards of professionalism with SSJS — a language that is used in ECMA3 specification published in December 1999 is based, i.e. a relic from the last millennium? As a digital marketing agency, this question has been on our mind again and again since our first project on Salesforce Marketing Cloud. We have gradually adapted several design patterns in this area and implemented them in a platform-specific way. The following core elements have ultimately enabled us to carry out test-driven development.
DRY — Don't Repeat Yourself
In the Marketing Cloud, we structure code in an object-oriented way, but without further inheritance. In this case, objects are to be understood as clusters of methods and should make the paradigms or approaches easier to understand when reading the code. An example: We often use a so-called controller object (objects created by us start with a “_” in code to avoid name confictions later on). The controller has functions to process requests (read parameters, map parameters against a data extension, etc.). Since we use this object more often in the CloudPages, which we set up in projects, we refrain from reimplementing the object in every CloudPage. If there was a bug or another motivated adjustment, we would otherwise have to manually edit every CloudPage, which costs time and nerves.
Instead, we implement the reusable code in code snippets and ensure that it is imported into CloudPages. This means that we only have to make adjustments to the code in one place. The imports are comparable to the counterpart in Golang:
import (
“fmt”
“github.com/badoux/checkmail”
“github.com/devfacet/gocmd”
)
or its counterpart in PHP
use App\ Abstracts\ DataExtensionDescriptionJob;
use App\ BusinessUnit;
use App\ Data Extension;
use App\ MarketingCloud\ Read\ DataExtensionReader;
This is also the first step towards code testability. If we worked exclusively with “Copy & Paste” to distribute the same code, we would not be able to rule out errors and would not know what a professional code-based test scenario should look like.
Make It Configurable
Suppose you have to write a method to filter contact data from a data extension. In the SOAP context, it is necessary to explicitly specify the columns to be queried. That is no problem anymore, because the method is not difficult to implement. The columns ID, EmailAddress, FirstName and LastName are written as an array into the query, as is the CustomerKey of the data extension. The next step, however, is to transfer the written code to another business unit of the customer, because the customer also wants to access the functionalities there. In this second business unit, however, the columns to be queried are ID, EmailAddress and City and, of course, the CustomerKey of the data extension is different. From this point on, a method with two different hulls would have been distributed across the various business units, although the intent of the method is always the same — looking at the code side, that's two tracks. There may be another n business units added.
From a maintenance perspective, this approach is rather unwise. It is better to work with variables. Both the CustomerKey of the data extension and the columns to be queried become configuration variables in the code snippet, the body of the method remains the same. Deploying to other BUs means that only configuration variables need to be adjusted per BU, while the rest can be taken over.
It is precisely these configuration variables that are a decisive component for being able to test “database operations” in test-driven development within Salesforce Marketing Cloud.
Test-driven development
The implementation of the first two patterns “DRY — Don't Repeat Yourself” and “Make It Configurable” makes it possible to adapt so-called “test-driven development” in the best possible way. Since we don't have a terminal available, we switch to implementation within CloudPages and visually output our test cases — everything is green, everything is good.
With the first pattern “DRY — Don't Repeat Yourself”, we are able to integrate our code base already used by the other CloudPages into the Test Suite CloudPage. If something changes in the code base, the tests must continue to be successful — or the adjustments are just incorrect. By the way: In order to be able to carry out such adjustments without disrupting live operation, we save the codebase elements in versioned form. A new version must first be confirmed by the test suite, then it can be integrated by the controllers.
Since we share the URL of the Test Suite CloudPage and the tests should be able to be initiated at any time by anyone involved in the project, it would be unthinkable to run data-extension-based tests against the “live database”. Unfortunately, Salesforce Marketing Cloud doesn't know the concept of “live database” vs. “test database.” There are simply no such differences. This is where the “Make It Configurable” pattern comes to our aid. Before we call up methods in the test suite for tests, we ensure that the configuration variables of the data extensions used are overwritten — instead of the potentially productive data extensions, we use an image of the data extensions for testing purposes during development. This approach is also a very useful tool for debugging processes without disrupting live operations.
The rest is the “usual” development work: writing methods, developing and carrying out tests for them. From our point of view, this type of development is extremely reassuring. The better we write tests, the more certain we can be that our software is doing its job. However, if we manually test complex processes (which was necessary a long time before we created the tools in Salesforce Marketing Cloud), this is on the one hand nerve-wracking (as it often requires a lot of click work) and on the other hand time-consuming, because with every adjustment, the test must start from the beginning to look at possible cross-influences.
But how to proceed with special logics in individual business units?
One potential vulnerability when modeling cross-business unit methods is the exceptions to the rule. So that we can model methods across business units despite special logic, we have decided to implement event objects. An event — or even an event — can take place in a method. It consists of an event name and an event data object, both of which are passed from the method to the fire method of the event. From a methodological point of view, nothing else is necessary. An example:
_event.fire (“contact.subscribed”, {Contact: Contact})
If the process is completed at this point in Business Unit A, while Business Unit B requires you to send a welcome email to the contact, you can still share the method code. Business Unit B now only adds more code, a so-called listener, which is in an additional code snippet. A listener is a function that can respond to an event with a specific name. Each event can have any number of listeners. In the code snippet, the listener must be registered with its callback function at the corresponding event:
var listenerFunction = function (eventData) {
var Contact = EventData.Contact,
//... logic here
}
_event.addListener (“contact.subscribed”, listenerFunction)
And it is precisely in this listener that a welcome email is triggered to the address from the submitted contact.
The principle may seem simple, but it creates plenty of scope to model code across business units by working out the similarities and being able to handle special features on an event-based basis.
outlook
In addition to the patterns described here, we have also developed a number of other working methods. These influence the approach we use to implement individual extensions in Salesforce Marketing Cloud. In doing so, we always aim to carry out a professional, up-to-date and, above all, stable implementation.
Well-structured code is the basis for every effective process. Projects in the Salesforce Marketing Cloud can also cope with recurring change requests from customer requirements if they are clearly coded — this also makes it much easier to maintain and optimize such processes.
If you would like to find out more about this topic or are looking for professional services, we will be happy to provide you with advice and assistance.