For a recent pro­ject for a rather large cli­ent, we had a very inter­est­ing chal­lenge: As part of the effort to rebuild the exist­ing web shop infra­struc­ture into a mod­ern cloud-nat­ive sys­tem land­scape, we also com­pletely revamped the ful­fill­ment sys­tem. This includes the gen­er­a­tion of deliv­ery doc­u­ments and invoices.

Because the cli­ent is act­ive in a lot of dif­fer­ent coun­tries — some­times even with mul­tiple dif­fer­ent sub­si­di­ar­ies — , we decided to use a SaaS CMS to let the stake­hold­ers change the texts of these doc­u­ments them­selves (another aspect why we chose to do it this way is that change requests for 100+ dif­fer­ent vari­ations took a lot of afford on the side of the ful­fill­ment team as well as delays for the requests — which we wanted to avoid).
How­ever, the decision to use an external CMS brought some inter­est­ing chal­lenges. Namely, in some cases the texts depend on the spe­cif­ics of the order. To give an example: Some omni­chan­nel orders (spe­cific­ally Click & Col­lect and sim­ilar) have dif­fer­ent pro­cesses for returns, that must be reflec­ted on the documents.

The solu­tion we have come up with, is to design a min­imal DSL (Domain Spe­cific Lan­guage) that allows our stake­hold­ers to spe­cify con­di­tions that determ­ine which texts to print based on the actual order data. These con­di­tions are rep­res­en­ted as reusable objects in the CMS so that you don’t need to cre­ate mul­tiple objects for the same logical con­di­tion. They can only be used with spe­cific fields (for tech­nical reas­ons) — we call these con­di­tional fields. A con­di­tional field con­tains a default text as well as an arbit­rary num­ber (pos­sibly zero in case that spe­cific shop, or lan­guage, doesn’t need any spe­cial cases for that field) of spe­cial cases that each refer to exactly one con­di­tion object.
Ori­gin­ally, we only used these con­di­tional fields for things like the return inform­a­tion. How­ever, they turned out to work so well, that now we use them for any trans­la­tion that depends on the order data (like for example, pay­ment method or delivery/omnichannel type).

Illustration of the simplified object model in the CMS, showing how different elements are structured to manage and apply conditions for customizing documents in the fulfillment system.
Fig­ure 1: Sim­pli­fied object model in the CMS
Visual representation of a condition object in the CMS, used to define specific rules for customizing delivery documents and invoices based on order data
Fig­ure 2: A con­di­tion object in the CMS

The DSL itself is rather simple but designed to be extend­able (i.e. more oper­at­ors can be added later eas­ily — we actu­ally already made use of this prop­erty). Another design require­ment was to make it as easy as pos­sible to under­stand for non-tech­nical people. For the same reason, for more com­plex expres­sions (using “and” and “or”) we didn’t define an order of pre­ced­ence, but instead enforce the use of brack­ets as soon as the expres­sion might be ambigu­ous without them.
Atomic con­di­tions con­sist of three com­pon­ents: The field of the order object to com­pare (pos­sibly even nes­ted fields), the oper­ator to use (at the time of writ­ing there are 3 oper­at­ors: “is”, “is not” and “starts with”) and a string to com­pare against.

Example of code defining conditions for customizing delivery documents and invoices in a fulfillment system using a Domain Specific Language (DSL).
Fig­ure 3: The ori­ginal DSL grammar

Here are some examples of con­di­tional expressions:

deliveryType is "click&collect"

(paymentType is "paypal" or paymentType is "creditcard") and documentType is "creditNote"

The call­ing sys­tem, that fetches the con­tent from the CMS and for­wards it to the PDF gen­er­a­tion, con­tains the parser for the DSL and trans­forms the texts accord­ing to what con­di­tions match the incom­ing order object.
This parser is imple­men­ted dir­ectly in code (Java) since at the time it seemed overkill to use a parser gen­er­ator like ANTLR for such a simple DSL. We build a recurs­ive-decent parser with back­track­ing but without looka­head. The rationale was that it’s easy to build, and since both the gram­mar and the poten­tial inputs are quite small, the expo­nen­tial asymp­totic runtime basic­ally doesn’t mat­ter.
The most com­plic­ated part to imple­ment was the back­track­ing mech­an­ism. How­ever, since there are basic­ally just three dif­fer­ent cases (namely: token rule, altern­at­ive rule and sequence rule) to take care of, we were able to abstract the back­track­ing com­pletely out of the actual pars­ing logic.
Another inter­est­ing detail is that because we have keywords with spaces, we were unable to util­ize the stand­ard Java Scan­ner class for lex­ical ana­lysis. Instead, we had to build our own that only uses pat­terns to gen­er­ate the next token. This is because of how the gram­mar was designed. We would have been able to get rid of this cus­tom solu­tion if we had divided the oper­ator instances con­tain­ing whitespaces into sequences of tokens (e.g. “is” “not” instead of “is not”). At the time, hav­ing only one token per oper­ator seemed like the less com­plex solu­tion. How­ever, in ret­ro­spect, it would have prob­ably been bet­ter to move the com­plex­ity to the gram­mar and get rid of that cus­tom scan­ner component.

This sys­tem has been in ser­vice for the past few months, and thus far it has been work­ing without any major issues. As men­tioned earlier, we also already made use of the extens­ib­il­ity prop­erty of our gram­mar to add the new “starts with” oper­ator — which lit­er­ally only took copy­ing the respect­ive token class, adjust its pat­tern and adding the AST logic.

The recep­tion by the stake­hold­ers is gen­er­ally very good. Although, they are cur­rently only using preex­ist­ing con­di­tion objects, as there has not yet been a case, when the exist­ing ones are not com­pre­hens­ive enough to model a spe­cific use case.

image depicting a large screen with code, document texts, currency symbols (Euro and Dollar), and people interacting with the screen, illustrating the use of Domain Specific Language (DSL) in a fulfillment system for customizing delivery documents and invoices