Extension Data Constraints
Overview
Extension Data Constraints are a tool to help ensure that extension data fields that are related are persisted in valid combinations. For example, suppose you had a table of vehicle data like this:
Make |
Model |
Year |
---|---|---|
Ford |
Mustang |
2020 |
Ford |
Mustang |
2021 |
Ford |
F150 |
2021 |
Toyota |
Camry |
2020 |
Toyota |
Camry |
2021 |
Toyota |
Camry |
2022 |
In this case, if the user first chooses Ford as the Make, then you would want to present only Mustang and F150 as options for Model, and not Camry. Likewise, if the user then selects F150 for the Model, they should be constrained to select only 2021 as the Year, and not 2020.
Extension Data Constraints support this sort of constraint building for user interfaces, and can also be used as a validation step to ensure that data created outside of the normal UI also conforms to the constraints desired (though this validation can be bypassed if needed.)
Structure
Data Constraints may be simple (such as constraining the value of a field based on the value of another field on that same element), or complex, where the interdependent fields are on completely different elements within the policy structure. The components used are:
Configuration, where you declare dependencies, using a path structure
Dependency Maps, which are generated for segments based on the actual data in a segment
Filter Evaluation API, which will compute allowed values for fields based on existing or prospective updated data
Constraint Tables, which are a special kind of table used for defining valid combinations of data
Extended Validation, which can ensure that data on the segment meets constraint requirements, regardless of whether it originated in a UI
Configuration
Each property in a data extension has a constraint field, which defines:
The name of the
table
that contains the valid combinationsThe
column
of the table that has valid values for that particular fieldThe
where
clause, which contains constraints for other columns in the table, or a static list of allowed values
For example, a configuration might include the following field declarations within a vehicle
element declaration:
{
"vehicleMake": {
"type": "string",
"constraint": {
"table": "vehicle_data",
"column": "make_name"
}
},
"vehicleModel": {
"type": "string",
"constraint": {
"table": "vehicle_data",
"column": "model_name",
"where": {
"make_name": {
"key": "vehicleMake"
}
}
}
},
"vehicleYear": {
"table": "vehicle_data",
"column": "year",
"where": {
"make_name": {
"key": "vehicleMake"
},
"model_name": {
"key": "vehicleModel"
}
}
}
}
In this example, we declare that:
The vehicle make must be one of the values in the
vehicle_data
table’smake_name
column.The model must be one of the values in the
model_name
column of that same table, but only including those rows that havemake_name
matching the make selected by the user.The year must be one of the values in the
year
column of the table, but only including those rows that havemake_name
matching the selected make andmodel_name
matching the selected model.
Dependency Maps
After a quote or policy transaction is created, the UI can call the Fetch Dependency Map for Quote or Fetch Dependency Map for Policy Transaction endpoint. The result will be the Dependency Map, which describes the dependencies and constrains that apply for that quote or policy transaction.
A DependencyMap is a nested map that references ConstraintDependency objects, each of which has the same structure as the constraints in configuration. The difference is that each of these is referenced by the actual locator of data for the quote or policy transaction. For example, a dependency map using the above configuration might look like this:
{
"01HTKMYZ0W7QT7VMSMMCFQNT1Q": {
"vehicleMake": {
"table": "vehicle_data",
"column": "make_name"
},
"vehicleModel": {
"table": "vehicle_data",
"column": "model_name",
"where": {
"make_name": {
"fieldName": "vehicleMake",
"staticLocator": "01HTKMYZ0W7QT7VMSMMCFQNT1Q"
}
},
"vehicleYear": {
"table": "vehicle_data",
"column": "year",
"where": {
"make_name": {
"fieldName": "vehicleMake",
"staticLocator": "01HTKMYZ0W7QT7VMSMMCFQNT1Q"
},
"model_name": {
"fieldName": "vehicleModel",
"staticLocator": "01HTKMYZ0W7QT7VMSMMCFQNT1Q"
}
}
}
}
}
This map indicates that:
The only element that has constrainted data is the vehicle, which has a
staticLocator
of01HTKMYZ0W7QT7VMSMMCFQNT1Q
.The vehicle make is still dependant on the table data only, as declared in the configuration
The vehicle model has the same table dependency but also only rows with a
make_name
that equal thevehicleMake
property for that vehicle will be considered.The vehicle year is similar but will only match rows with a matching make name (like the model), but also where
model_name
matches thevehicleModel
on the element.
Now, with the dependency map, the UI has enough information to ask Socotra what the actual allowed values should be for the make and model fields.
Note
When adding elements, the client should fetch a new dependency map if any of those elements could affect constraint processing in the UI.
Constraint Evaluation API
The UI can use the dependency map, together with the values selected so far by the user, in order to get updates of what values are valid for remaining inputs. This is done by calling the Evaluate Constraints for Quote or Evaluate Constraints for Policy Transaction endpoints.
For example, with the dependency map above, the UI could send this request:
{
/* as received earlier */
"01HTKMYZ0W7QT7VMSMMCFQNT1Q": {
"vehicleMake": {},
"vehicleModel": {},
"vehicleYear": {}
},
"01HTKN7Y5PSYF0D95RWTTZ88QT": { }
}
In this case, the user hasn’t selected anything yet, and so the response is basic:
{
"01HTKMYZ0W7QT7VMSMMCFQNT1Q": {
"vehicleMake": ["Toyota", "Ford"]
}
The system has determined that the vehicleMake
options are fully determined since they only depend on table data, and there are no where
restrictions, so all values found in the table are valid from the beginning. In contrast, the vehicleModel
selection does depend on the vehicleMake
, and since there are no rows with an empty vehicleMake
value, there are no matches for the inputs, and so there are no valid values returned.
Note
When there are no values returned for a particular field, the UI could be programmed to disable or hide those fields depending on the user experience they require.
Important
If a value is not included in the evaluation request, the system will defer to the currently persisted value of the element to fill those values. This behavior may change to treating those cases as having “no value” as described above.
After the user chooses a vehicle make, such as Ford, the UI can update the evaluation request as follows:
{
/* this hasn't changed from the earlier request */
"01HTKMYZ0W7QT7VMSMMCFQNT1Q": {
"vehicleModel": {
"make_name": "Ford"
}
}
}
And the response is:
{
"vehicleMake": ["Toyota", "Ford"],
"vehicleModel": ["Mustang", "F150"]
}
Or set the data.vehicleMake
in the quote/transaction data extension, save and now the value is used:
/* quote or transaction data extension field */
{
"data": {
"vehicleMake": "Toyota"
}
}
{
/* quote/transaction: data.vehicleMake field value is automatically used */
"01HTKMYZ0W7QT7VMSMMCFQNT1Q": {
"vehicleMake": {},
"vehicleModel": {}
}
}
And the response is:
{
"vehicleMake": ["Toyota", "Ford"],
"vehicleModel": ["Camry"]
}
The system determined that vehicleModel
has a valid match with the input value from quote or transaction Toyota
.
Referencing Other Elements
When specifying the reference field that constrains another field, that reference field does not have to be in the same element. If only its name is included in the key
property, it refers to that field on the same element. If prefixed with ..
, such as "../driverPoints"
, the constraint comes from the driverPoints
field on the parent element. These can be chained; for example, ../../driverPoints
refers to the driverPoints
field on the parent’s parent element.
The root (product) element can be refered to by prefixing a slash, such as /driverPoints
.
Validation
The discussion above refers to the up-front process used while the quote or policy transaction is still in draft
state. When the user has made their selections, the transaction can proceed to validated
state. By default, the system will apply the constraints implied by the configuration and constraint tables to ensure that the actual data meets the constraints, even if the origin of the data was not the UI.
Important
The ability to bypass this validation is not yet present, but will be added in an upcoming release.