Routing strategy
Why a dynamic strategy is needed
The standard RoutingConfiguration accommodates a wide range of applications but has limitations in specific use cases. For example, different countries may require different configurations for legal or economic reasons. In some regions, demand might call for fast deliveries, while in others, there is no economic incentive for features such as order splitting. Another use case is to treat certain ratings, like the Geodistance rating, differently for orders that require pallets and freight forwarding.
Basic concepts
The routing configuration is a tree-like structure called a RoutingStrategy. Every RoutingStrategy has a mandatory RootNode that serves as an entry point and contains the baseline routing configuration. This baseline can be extended and altered by nested nodes.
A node can be active or inactive and can be followed by a condition. The condition defines a rule that, if met, leads to the evaluation of a specified node. The condition may also define a subsequent condition to check if the initial criteria are not met.
Setup
This section describes the entities involved in building a RoutingStrategy.
Entities
RoutingStrategy
This is the top-level entity that encompasses all configurations. Multiple RoutingStrategy entities can be defined, but only one may be active at a time, denoted by the inUse flag. It requires a localized name to distinguish between different strategies. The revision number indicates the latest version of the strategy. When a new RoutingStrategy is created, the system automatically assigns it the highest revision number.
{
"nameLocalized": {
"en_US": "Routing strategy",
"de_DE": "Routingstrategie"
},
"rootNode": {
// RoutingStrategyNode
},
"globalConfiguration": {
// RoutingStrategyGlobalConfiguration
},
"version": 1,
"revision": 3
}RoutingStrategyGlobalConfiguration
This entity contains global settings that apply to the entire routing process.
{
"stopRoutingAttemptsAfterTime": "PT1M",
"defaultPrice": 10,
"timeTriggered": {
"shipFromStoreDeliveryReroute": {
// ...
},
"shipFromStoreSameDayReroute": {
// ...
},
"clickAndCollectReroute": {
// ...
}
}
}RoutingStrategyNode
These entities contain the actual routing configurations and specific rules. At least one RootNode must be defined for any strategy. A node may define a condition that references another node.
For a node to be applied, two criteria must be met: the active field must be true, and at least one of its ActivationTimeFrame definitions must be valid. An inactive node (where active is false) is never applied, regardless of its activationTimeFrames.
{
"nameLocalized": {
"en_US": "Routing strategy node",
"de_DE": "Routingstrategieknoten"
},
"active": true,
"config": {
// RoutingStrategyNodeConfig
},
"nextCondition": {
// optional, RoutingStrategyCondition
},
"activationTimeFrames": []
}ActivationTimeFrame
This entity defines a valid timeframe for a node to be active, such as for a seasonal sale. The recurrence can be NONRECURRING or YEARLY. When recurrence is set to YEARLY, the timeframe repeats annually based on the defined start and end dates.
{
"activeFrom": "2024-12-24",
"activeUntil": "2024-12-31",
"recurrence": "YEARLY"
}RoutingStrategyCondition
A condition is a fundamental part of the dynamic routing rules. It contains a rule and links to a RoutingStrategyNode that applies if the rule is matched. It may also define a subsequent condition to be evaluated if the rule does not apply. Like a node, a condition can also define one or more ActivationTimeFrames.
{
"nameLocalized": {
"en_US": "Orders to Germany",
"de_DE": "Bestellungen nach Deutschland"
},
"rule": {
"predicateConnector": "OR",
"predicates": [{
"propertyPath": "$.order.consumer.addresses[?(@.type === 'POSTAL_ADDRESS')].country",
"expectedValue": "Germany",
"entityOperator": "ANY_VALUE_EQUALS"
}]
},
"nextNode": {
// RoutingStrategyNode
},
"nextCondition": {
// optional, could be a condition checking for another address country
},
"activationTimeFrames": {
// ActivationTimeFrame
},
}RoutingStrategyNodeConfig
A routing configuration is defined by each RoutingStrategyNode. Any part of this configuration can be overwritten or extended by more deeply nested RoutingStrategyNodeConfigs. These entities contain the core DOMS routing functionality, such as Fences, Ratings, and order-split and reroute configurations. Both standard and toolkit fences and ratings are configured in the same location within this entity.
{
"fences": [
// RoutingStrategyStandardFence and RoutingStrategyToolkitFence
],
"ratings": [
// RoutingStrategyStandardRating and RoutingStrategyToolkitRating
],
"orderSplit": {
// RoutingStrategyOrderSplitConfig
},
"reroute": {
// RoutingStrategyRerouteConfig
}
}RoutingStrategyStandardFence
A fence that uses a predefined implementation. It is nearly identical to the previous Fence model but includes a type field to denote it as a StandardFence.
{
"type": "StandardFence",
"referenceId": "unique-id",
"rule": {
// ToolkitRule
},
"comparisonRule": {
// ToolkitComparisonRule, mutually exclusive with ToolkitRule
},
}RoutingStrategyToolkitRating
A rating created using toolkit functionality. A referenceId is required to uniquely identify a toolkit rating, as it may appear on multiple layers of the RoutingStrategy, and changes to it can affect more than one entity. Similar to its standard counterpart, a required type field is added to denote it as a ToolkitRating.
{
"type": "ToolkitRating",
"referenceId": "unique-id",
"rule": {
// ToolkitRule
},
"comparisonRule": {
// ToolkitComparisonRule, mutually exclusive with ToolkitRule
},
}Example: Routing strategy with special configurations for pallet articles
Step 1: Set up the routing strategy using the API
Before you get started, request a new auth token and add the headers as described in the quickstart documentation. The baseURL variable will be https://{YOUR-TENANT-NAME}.api.fulfillmenttools.com when using the REST API. When using GraphQL, replace the api subdomain with graphql.
The first step is to create a RoutingStrategyForCreation. The absolute minimum required is a localized name to distinguish it from other strategies. This example uses the name “Initial RoutingStrategy” for the English locale and leaves other fields empty.
The following mutation creates the initial routing strategy. This mutation can be used in any GraphQL client (such as Altair) with valid authorization headers.
mutation createRoutingStrategy {
createRoutingStrategy(input: { nameLocalized: { en_US: "Initial RoutingStrategy" }}) {
routingStrategy {
id
version
nameLocalized
rootNodeRef
nodes {
id
active
nameLocalized
name
config {
fences {
__typename
... on RoutingStrategyStandardFence {
implementation
name
active
}
}
ratings {
__typename
... on RoutingStrategyStandardRating {
implementation
name
active
}
}
}
}
}
}
}If everything has worked out you should see that we have a basic routing strategy with all standard fences with active set to false and no additional configurations. However it won’t be active yet so let’s do that now.
With a REST client such as Postman, send a POST request to {baseURL}/api/routing/strategies with the following JSON body. The request requires the standard bearer token authorization header.
{
"nameLocalized": {
"en_US": "Initial RoutingStrategy"
}
}A successful request returns the created routing strategy. It contains a global routing configuration and a root node with a basic configuration where all standard ratings and fences are present but inactive. The routing strategy itself is also inactive until it is explicitly activated.
Step 2: Add a condition and node to the routing strategy
This example creates a new condition that applies to orders having line items with pallet articles. It assumes the OrderLineItem has a tag that looks as follows: { id: ‘load-unit’, value: ‘pallet’ }. This is used to build a toolkit rule that evaluates to true if a line item with this tag exists.
Create the JSONPath
The entities to be evaluated have the following schema:
{
order: OrderForCreation,
routingPlan?: RoutingPlan
}Therefore, the JSONPath pointing to the tag is:
$.order.orderLineItems[?(@.tags.find(tag => tag.id === 'load-unit' && tag.value === 'pallet'))]
Create the ToolkitPredicate with the JSONPath
This JSONPath selects all OrderLineItems that have the defined tag with the appropriate value. Because the result is an array, the COUNT transformer can be used to check if at least one OrderLineItem satisfies the properties. To confirm that there is one or more such line items, the predicate is defined as follows:
{
"propertyPath": "$.order.orderLineItems[?(@.tags.find(tag => tag.id === 'load-unit' && tag.value === 'pallet'))]",
"transformation": "COUNT",
"entityOperator": "GREATER_EQUALS",
"expectedValue": 1
}Create a RoutingStrategyCondition with the ToolkitPredicate
The assembled ToolkitRulePart contains an array with this single predicate and a ToolkitPredicateConnector, which becomes important as more predicates are added. In this case, it is set to AND. The condition containing the rule looks as follows:
{
"nameLocalized": {
"en_US": "Order requires pallets"
},
"active": true,
"rule": {
"predicates": [
{
"propertyPath": "$.order.orderLineItems[?(@.tags.find(tag => tag.id === 'load-unit' && tag.value === 'pallet'))]",
"transformation": "COUNT",
"entityOperator": "GREATER_EQUALS",
"expectedValue": 1
}
],
"predicateConnector": "AND"
},
"nextNode": {}
}The node created should have a configuration that applies only to orders fulfilling that condition, so it should have a config that extends the existing default routing configuration.
Create a RoutingStrategyNode
With the ToolkitRulePart created, a RoutingStrategyCondition can be defined that uses this rule. This example names the node "Pallet routing configuration" and adds the appropriate routing configuration to it. The RoutingStrategyNode could look as follows:
{
"active": true,
"nameLocalized": {
"en_US": "Pallet routing configuration"
},
"config": {
"ratings": [
{
"implementation": "GEO-DISTANCE",
"type": "StandardRating",
"maxPenalty": 1000,
"active": true
}
]
}
}The configuration of this node contains only one rating. In practice, this means every other configuration (fences, other ratings, reroute configuration) is inherited from the root configuration, and only this particular rating is overwritten.
Update the RoutingStrategy with the new node and condition
The RoutingStrategy can be updated via the GraphQL or REST API.
The following mutation adds the RoutingStrategyNode and RoutingStrategyCondition to the root node. The following variables must be provided:
strategyRef: The ID of the createdRoutingStrategy.routingStrategyVersion: The current version of theRoutingStrategyfrom the last response.node: TheRoutingStrategyNodecreated in the previous step.condition: TheRoutingStrategyConditioncreated in the previous step.
mutation appendRoutingStrategyConditionAndNodeToNode ($node: RoutingStrategyNodeCreationInput!, $condition: RoutingStrategyConditionCreationInput!, $strategyRef: String!, $rootNodeRef: String!, $routingStrategyVersion: Int!) {
appendRoutingStrategyConditionAndNodeToNode(
input: {
strategyRef: $strategyRef
nodeRef: $rootNodeRef
condition: $condition
node: $node
routingStrategyVersion: $routingStrategyVersion
}
) {
routingStrategy {
id
created
lastModified
version
revision
inUse
nameLocalized
name
rootNodeRef
nodes {
active
nameLocalized
config {
ratings {
__typename
... on RoutingStrategyStandardRating {
active
maxPenalty
implementation
}
}
}
}
conditions {
active
nameLocalized
rule {
predicateConnector
predicates {
propertyPath
entityOperator
expectedValue
transformation
}
}
}
}
routingStrategyVersion
}
}Using REST requires sending the entire RoutingStrategy in the request body, so the entity must be assembled manually.
Retrieve the current
RoutingStrategy. Use theGET /api/routing/strategies/:strategyIdendpoint if needed.Locate the
rootNodefield in theRoutingStrategy.Add a field named
nextConditionto therootNode.Set the
nextConditionfield to theRoutingStrategyConditioncreated in the previous step.Add the
nextNodefield to theRoutingStrategyCondition.Within the
RoutingStrategyCondition, set thenextNodefield to theRoutingStrategyNodecreated in the previous step.
Step 3: Create an order to test the configuration
The purpose of a dynamic routing configuration is to behave differently depending on the context. To test this, two different orders are created.
Create a regular order
First, test that a regular order (one that does not satisfy the defined condition) uses the default configuration, meaning all standard fences and ratings are inactive. The order could look like this:
{
"consumer": {
"addresses": [
{
"city": "Cologne",
"country": "Germany",
"street": "Schanzenstrasse",
"postalCode": "51063"
}
]
},
"orderDate": "2025-01-30T08:15.000Z",
"orderLineItems": [
{
"article": {
"title": "Gaffel Wiess"
},
"quantity": 4711
}
]
}Create a special order
Next, define an order that does meet the condition. The order is mostly the same except for the discriminator, which is the tags array on the OrderLineItem.
{
"consumer": {
"addresses": [
{
"city": "Cologne",
"country": "Germany",
"street": "Schanzenstrasse",
"postalCode": "51063"
}
]
},
"orderDate": "2025-01-30T08:15.000Z",
"orderLineItems": [
{
"article": {
"title": "Gaffel Wiess vom Fass"
},
"quantity": 100,
"tags": [
{
"id": "load-unit",
"value": "pallet"
}
]
}
]
}Step 4: Test the routing strategy evaluation
This step uses the REST API to test the evaluation.
Prepare a
POSTrequest toapi/routing/strategies/:strategyId/actions.Set the
strategyIdpath parameter to the ID of theRoutingStrategybeing tested.Set the request body to the
OrderForCreationJSON of the order to test.The response contains
evaluatedPathandevaluatedConfig.The
evaluatedConfigcontains the actualRoutingStrategyNodeConfigthat would be used for routing this particular order.The
evaluatedPathcontains the evaluation path for transparency and debugging.Examine the
evaluatedConfigand inspect theGeodistanceRating.When the first order (without tags) is sent, the rating should be inactive with a
maxPenaltyof0.When the second order (with the pallet tag) is sent, the rating should be active with a
maxPenaltyof1000.Repeat the steps for the other order to confirm both paths work as expected.
It is recommended to perform this validation for every change. Do not activate the RoutingStrategy until this step is successfully completed and all configurations are verified.
Step 5: Activate the routing strategy
Feature Activation: Assuming the dynamic routing feature is enabled on the tenant, activating a RoutingStrategy makes it the single source of truth for all routing decisions. Do not proceed until all configurations are verified.
Use the activateRoutingStrategy mutation, which requires an activateRoutingStrategyInput containing the routingStrategyId and version.
mutation activateRoutingStrategy {
activateRoutingStrategy(
activateRoutingStrategyInput: { routingStrategyId: "string", version: 1 }
) {
routingStrategy {
inUse
}
routingStrategyVersion
}
}A successful response shows the RoutingStrategy now has its inUse property set to true.
Send a POST request to the {baseURL}/api/routing/strategies/:strategyId/actions endpoint with the following body to activate the RoutingStrategy.
{
"name": "ACTIVATE"
}Last updated