5.15. 数据模型版本化示例
- 实体属性被重命名
-
我们假设
sales$Order
实体的oldNumber
属性被重命名为newNumber
并且date
被重命名为deliveryDate
。在这种情况下,转换配置将如下所示:<?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>
如果客户端应用程序需要使用旧版的
sales$Order
实体,那么它必须在 URL 参数中传递modelVersion
值:http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.0
将返回以下结果:
{ "_entityName": "sales$Order", "_instanceName": "00001", "id": "46322d73-2374-1d65-a5f2-160461da22bf", "date": "2016-10-31", "description": "Vacation order", "oldNumber": "00001" }
尽管 CUBA 应用程序中的实体具有
newNumber
和deliveryDate
属性,但是响应 JSON 却包含oldNumber
和date
属性, - 实体名称更改
-
接下来,试想一下,在应用程序的某个版本中,
sales$Order
实体的名称也发生了变化。新名称是sales$NewOrder
。版本
1.1
的转换配置将如下所示:<?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>
除了上一个示例中的配置之外,还在此处添加了
oldEntityName
属性。它指定对模型版本1.1
有效的实体名称。currentEntityName
属性指定当前实体名称。虽然名称为
sales$Order
的实体不再存在,但以下请求也可以正常运行:http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1
REST API controller 会明白它必须在
sales$NewOrder
实体里进行搜索,并且在找到具有给定 id 的实体之后,在结果 JSON 中替换实体的名称和newNumber
属性的名称:{ "_entityName": "sales$Order", "_instanceName": "00001", "id": "46322d73-2374-1d65-a5f2-160461da22bf", "date": "2016-10-31", "description": "Vacation order", "oldNumber": "00001" }
客户端应用程序也可以使用旧版本的数据模型进行实体更新和创建。
此 POST 请求使用旧实体名称,并且在请求体使用旧的 JSON,这个 POST 请求可以正常工作:
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" }
- 必须从 JSON 中删除的实体属性
-
如果某个属性已添加到实体,但使用旧版数据模型的客户端不希望有此新属性,则可以从结果 JSON 中删除新属性。
此示例的转换配置如下所示:
<?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>
此配置文件中的转换包含带有嵌套
removeAttribute
命令的toVersion
标记。这意味着当执行从当前状态到特定版本的转换时(即当请求实体列表时),必须从结果 JSON 中删除discount
属性。在这种情况下,如果在没有
modelVersion
属性的情况下执行请求,discount 属性将会被返回: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 }
如果指定
modelVersion
,则将删除discount
属性http://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" }
- 使用自定义转换器
-
还可以创建和注册自定义 JSON 转换器。举个例子,我们来看看下面的情况:有一个实体
sales$OldOrder
被重命名为sales$NewOrder
。该实体具有orderDate
字段。在之前的版本中,此日期字段包含时间部分,但在最新版本的实体中,时间部分将被删除。请求旧模型版本1.0
的实体的 REST API 客户端期望日期字段有时间部分,因此转换器必须修改 JSON 中的值。首先,转换器配置必须如下所示:
<?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>
有一个
custom
元素和嵌套的toVersion
和fromVersion
元素。这些元素引用了转换器 bean。这意味着自定义转换器必须注册为 Spring bean。这里有一件重要的事情:自定义转换器可以使用RestTransformations
平台 bean(如果需要,这个 bean 可以访问其它实体转换器)。但是RestTransformations
bean 是在 REST API servlet 的 Spring 上下文中注册,而不是在 Web 应用程序的主上下文中注册。这意味着自定义转换器 bean 也必须在 REST API Spring 上下文中注册。我们怎样做到这一点?
首先,在 web 或 portal 模块中创建一个
rest-dispatcher-spring.xml
(例如,在com.company.test
包中)。接下来,在 Web 或 portal 模块的
app.properties
中注册此文件:cuba.restSpringContextConfig = +com/company/test/rest-dispatcher-spring.xml
rest-dispatcher-spring.xml
必须包含自定义转换器 bean 定义:<?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>
sales_OrderJsonTransformerToVersion
转换器的内容如下: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"); } } }
此转换器在 JSON 对象中找到
orderDate
节点,并通过将时间部分添加到该值来修改该值。当请求数据模型版本
1.0
的sales$OldOrder
实体时,结果 JSON 将包括含有时间部分的orderDate
字段的实体,尽管它不再存储在数据库中。再啰嗦几句关于定制转换器:它们必须实现
EntityJsonTransformer
接口。还可以继承AbstractEntityJsonTransformer
类并覆盖其doCustomTransformations
方法。AbstractEntityJsonTransformer
类包含标准转换器的所有功能。