4.5.15. Data Model Versioning Example
- Entity attribute was renamed
-
Let’s suppose that the
oldNumberattribute of thesales$Orderentity was renamed tonewNumberanddatewas renamed todeliveryDate. In this case transformation config will be like this:<?xml version="1.0"?> <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd"> <transformation modelVersion="1.0" currentEntityName="sales$Order"> <renameAttribute oldName="oldNumber" currentName="newNumber"/> <renameAttribute oldName="date" currentName="deliveryDate"/> </transformation> ... </transformations>If the client app needs to work with the old version of the
sales$Orderentity then it must pass themodelVersionvalue in the URL parameter:http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.0The following result will be returned:
{ "_entityName": "sales$Order", "_instanceName": "00001", "id": "46322d73-2374-1d65-a5f2-160461da22bf", "date": "2016-10-31", "description": "Vacation order", "oldNumber": "00001" }The response JSON contains an
oldNumberanddateattributes although the entity in the CUBA application hasnewNumberanddeliveryDateattributes. - Entity name was changed
-
Next, let’s imagine, that in some next release of the application a name of the
sales$Orderentity was also changed. The new name issales$NewOrder.Transformation config for version
1.1will be like this:<?xml version="1.0"?> <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd"> <transformation modelVersion="1.1" oldEntityName="sales$Order" currentEntityName="sales$NewOrder"> <renameAttribute oldName="oldNumber" currentName="newNumber"/> </transformation> ... </transformations>In addition to the config from the previous example an
oldEntityNameattribute is added here. It specifies the entity name that was valid for model version1.1. ThecurrentEntityNameattribute specifies the current entity name.Although an entity with a name
sales$Orderdoesn’t exist anymore, the following request will work:http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1The REST API controller will understand that it must search among
sales$NewOrderentities and after the entity with given id is found names of the entity and of thenewNumberattribute will be replaced in the result JSON:{ "_entityName": "sales$Order", "_instanceName": "00001", "id": "46322d73-2374-1d65-a5f2-160461da22bf", "date": "2016-10-31", "description": "Vacation order", "oldNumber": "00001" }The client app can also use the old version of data model for entity update and creation.
This POST request that uses old entity name and has old JSON in the request body will work:
http://localhost:8080/app/rest/v2/entities/sales$Order{ "_entityName": "sales$Order", "_instanceName": "00001", "id": "46322d73-2374-1d65-a5f2-160461da22bf", "date": "2016-10-31", "description": "Vacation order", "oldNumber": "00001" } - Entity attribute must be removed from JSON
-
If some attribute was added to the entity, but the client that works with the old version of data model doesn’t expect this new attribute, then the new attribute can be removed from the result JSON.
Transformation configuration for this case will look like this:
<?xml version="1.0"?> <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd"> <transformation modelVersion="1.5" currentEntityName="sales$Order"> <toVersion> <removeAttribute name="discount"/> </toVersion> </transformation> ... </transformations>Transformation in this config file contains a
toVersiontag with a nestedremoveAttributecommand. This means that when the transformation from the current state to specific version is performed (i.e. when you request a list of entities) then adiscountattribute must be removed from the result JSON.In this case if you perform the request without the
modelVersionattribute, the discount attribute will be returned:http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93{ "_entityName": "sales$Order", "_instanceName": "00001", "id": "46322d73-2374-1d65-a5f2-160461da22bf", "deliveryDate": "2016-10-31", "description": "Vacation order", "number": "00001", "discount": 50 }If you specify the
modelVersionthendiscountattribute will be removedhttp://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1{ "_entityName": "sales$Order", "_instanceName": "00001", "id": "46322d73-2374-1d65-a5f2-160461da22bf", "deliveryDate": "2016-10-31", "description": "Vacation order", "oldNumber": "00001" } - Using custom transformer
-
You can also create and register a custom JSON transformer. As an example let’s examine the following situation: there was an entity
sales$OldOrderthat was renamed tosales$NewOrder. This entity has anorderDatefield. In the previous version, this date field contained a time part, but in the latest version of the entity, the time part is removed. REST API client that request the entity with an old model version1.0expects the date field to have the time part, so the transformer must modify the value in the JSON.First, that’s how the transformer configuration must look like:
<?xml version="1.0"?> <transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd"> <transformation modelVersion="1.0" oldEntityName="sales$OldOrder" currentEntityName="sales$NewOrder"> <custom> <fromVersion transformerBeanRef="sales_OrderJsonTransformerFromVersion"/> <toVersion transformerBeanRef="sales_OrderJsonTransformerToVersion"/> </custom> </transformation> ... </transformations>There are a
customelement and nestedtoVersionandfromVersionelements. These elements have a reference to the transformer bean. This means that custom transformer must be registered as a Spring bean. There is one important thing here: a custom transformer may use theRestTransformationsplatform bean (this bean gives an access to other entities transformers if it is required). But theRestTransformationsbean is registered in the Spring context of the REST API servlet, not in the main context of the web application. This means that custom transformer beans must be registered in the REST API Spring context as well.That’s how we can do that.
First, create a
rest-dispatcher-spring.xmlin the web or portal module (e.g. in packagecom.company.test).Next, register this file in the
app.propertiesof the web or portal module:cuba.restSpringContextConfig = +com/company/test/rest-dispatcher-spring.xmlThe
rest-dispatcher-spring.xmlmust contain custom transformer bean definitions:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"> <bean name="sales_OrderJsonTransformerFromVersion" class="com.company.test.transformer.OrderJsonTransformerFromVersion"/> <bean name="sales_OrderJsonTransformerToVersion" class="com.company.test.transformer.OrderJsonTransformerToVersion"/> </beans>The content of the
sales_OrderJsonTransformerToVersiontransformer is as follows:package com.company.test.transformer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Strings; import com.haulmont.restapi.transform.AbstractEntityJsonTransformer; import com.haulmont.restapi.transform.JsonTransformationDirection; public class OrderJsonTransformerToVersion extends AbstractEntityJsonTransformer { public OrderJsonTransformerToVersion() { super("sales$NewOrder", "sales$OldOrder", "1.0", JsonTransformationDirection.TO_VERSION); } @Override protected void doCustomTransformations(ObjectNode rootObjectNode, ObjectMapper objectMapper) { JsonNode orderDateNode = rootObjectNode.get("orderDate"); if (orderDateNode != null) { String orderDateNodeValue = orderDateNode.asText(); if (!Strings.isNullOrEmpty(orderDateNodeValue)) rootObjectNode.put("orderDate", orderDateNodeValue + " 00:00:00.000"); } } }This transformer finds the
orderDatenode in the JSON object and modifies its value by adding the time part to the value.When the
sales$OldOrderentity with a data model version1.0is requested, the result JSON will contain entities withorderDatefields that contain time part, although it is not stored in the database anymore.A couple more words about custom transformers. They must implement the
EntityJsonTransformerinterface. You can also extend theAbstractEntityJsonTransformerclass and override itsdoCustomTransformationsmethod. TheAbstractEntityJsonTransformercontains all functionality of the standard transformer.