9.2 Update Payloads
The SData update protocol does not need the consumer to send the full payload when updating a resource. Instead it can send a partial payload with modified properties. In the 9.1 Update example, if the consumer had only modified the shipDate property, the POSTed payload could be reduced to:
<salesOrder xmlns="http://schemas.sage.com/myContract"> <shipDate>2008-04-05</shipDate> </salesOrder>
This feature reduces bandwidth usage and simplifies the logic on the consumer side. If a consumer only understands a subset of the properties exposed by the provider, it doesn’t need to memorize values for properties that it doesn’t understand. It can send a partial update limited to the properties that it does understand. In a typical “Edit Form” scenario, the consumer initializes the form with values that it received from a GET operation, ignoring payload elements that are not displayed in the form. When the form is submitted the consumer prepares a partial payload containing only the form values, or even better, only the modified form values.
When a provider implements an update operation, it MUST always treat the absence of a payload property element as meaning the property has not been modified. It MUST NOT interpret it as meaning the property has been reset to some default value. For example, null. If the consumer wants to set a property value to null, it MUST do so by including an element with an xsi:nil attribute in the payload. For example, if the consumer wants to reset the shipDate of the sales order, it must send a payload such as:
<salesOrder xmlns="http://schemas.sage.com/myContract"> <shipDate xsi:nil="true" /> </salesOrder>
This feature has an implication on SData schemas. The minOccurs attribute SHOULD be set to 0 so that partial payloads can be successfully validated against the schema.
This minOccurs guideline is only a recommendation for providers that want to support the partial update feature. A provider can still impose full payloads for a contract. In this case, the provider should do the opposite and flag all properties with minOccurs="1" to indicate it needs full payloads for create and update operations.
SData provides an sme:isMandatory attribute to flag properties that are mandatory at resource creation time. So, contracts that support the partial update feature should set minOccurs to 0 on all properties and should use the sme:isMandatory attribute rather than minOccurs to flag properties that are mandatory at creation time. Properties that are flagged with sme:isMandatory=”true” MUST be present in the payload of POST operations but they may be omitted in PUT operations, unless their value has been modified.
A PUT operation MAY update child resources (properties marked with sme:relationship=”child” in the schema). For example, a sales order payload may contain payloads for the billing address, shipping address and order lines of the sales order.
If a child resource has not been modified the corresponding property MAY be omitted from the parent payload. For example, if the billing address of a sales order has not been modified, the <billingAddress> element can be omitted from the sales order payload.
If a child resource is optional (property marked with nillable=”true” in the schema) and the property has been reset (it referenced a child before and it has been set to null), the property element MUST be included in the payload and it MUST carry an xsi:nil=”true” attribute.
So, the absence of a child property in the payload MUST always be interpreted as an absence of modification on the child, never as a an optional child being reset to null.
SData provides two ways to update lists of child resources (properties marked with sme:relationship=”child” and sme:isCollection=”true” in the schema). The consumer can choose to send:
a delta that only contains the children that have been inserted, updated or deleted. In this case deleted elements are included in the list and MUST be flagged with sdata:isDeleted=”true”.
the full list of children. In this case the list only contains the elements that remain in the list (and it MUST contain all of them). The list itself is flagged with sdata:deleteMissing=”true” to indicate that the provider should compare its current list of children with the new list and delete the children that are absent from the new list.
An SData consumer MAY choose to send updates on lists in either form. If it chooses to send the full list, it MUST set the sdata:deleteMissing flag on the list element.
An SData provider MUST accept both forms. It MUST test the sdata:deleteMissing flag to decide if missing elements should be deleted or simply ignored (left in the list).
Let’s see how this works with a sales order and its order lines.
First, if the update payload for a sales order doesn’t contain any <orderLines> element, it means the order lines have not been modified.
If the list of order lines has changed and the consumer chooses to send the changes as a delta, it includes an <orderLines> element in the sales order payload with the list of modified <salesOrderLine> elements underneath it. For example:
<salesOrder xmlns="http://schemas.sage.com/myContract"> <shipDate>2008-05-27</shipDate> <orderLines> <salesOrderLine sdata:uuid="CEFE3F52-5529-46b9-A166-79EDFD2D0595"> <orderQty>4</orderQty> </salesOrderLine> <salesOrderLine sdata:uuid="CD1BA6F5-C6D5-4a9b-9D59-68D43B8C58B5" sdata:isDeleted="true"/> </orderLines> </salesOrder>
This partial payload is interpreted as a request to :
- Update the shipDate of the sales order
Update the orderQty of the line with UUID CEFE3F52…
- Delete the line with UUID CD1BA6F5…
The consumer should include either the internal identifier (sdata:key) or global identifier (sdata:uuid) attribute of each updated line that it sends so that the provider can match the lines it receives with the lines that already exist in its datastore.
If the provider finds a match on a line, it should update it with the line payload it receives, interpreting the line payload as a partial payload. In the example, the provider updates the orderQty of line CEFE3F52…
Lines that are flagged with sdata:isDeleted=”true”, like line CD1BA6F5… above should be deleted by the provider. The provider should abort the operation and send an error response, if the line cannot be deleted for some reason.
If the provider finds a payload line that doesn’t match an existing line, it creates a new line resource. This feature works well when lines are identified by a universally unique identifier (UUID) because the UUIDs for the new lines are allocated by the consumer and sent to the provider. The consumer can use these UUIDs to match the lines that it receives in the provider’s response with the lines that it sent.
This feature can cause issues when lines are identified by an internal ID that the provider allocates at creation time. In this case, the consumer should not associate any identification with the created lines. The provider allocates an internal ID but the consumer uses heuristics to match what it receives from the provider to what it sent. This is rather fragile. For example, a match on line number or a match on product id and quantity.
In this mode the consumer does not need to include all the child resources in the payload. It only needs to include the child resources that have been modified (created, updated or deleted).
If instead the consumer chooses to send the changes as a full list, it includes an <orderLines> element in the sales order payload with the complete list of <salesOrderLine> elements:
<salesOrder xmlns="http://schemas.sage.com/myContract"> <shipDate>2008-05-27</shipDate> <orderLines sdata:deleteMissing="true" > <salesOrderLine sdata:uuid="36B2ECF4-4309-4e62-9878-28DF60B78CFD"/> <salesOrderLine sdata:uuid="CEFE3F52-5529-46b9-A166-79EDFD2D0595"> <orderQty>4</orderQty> </salesOrderLine> </orderLines> </salesOrder>
The <orderLines> element is flagged with sdata:deleteMissing=”true”. This flag informs the provider that the consumer is using the full mode and that the provider should thus delete any child resource that is missing from the payload.
This partial payload is interpreted as a request to:
- Update the shipDate of the sales order
Update the orderQty of the line with UUID CEFE3F52…
- Delete any line that is not included in the <orderLines> list. So, the provider had 3 lines with UUIDs 36B2ECF4…, CEFE3F52… and CD1BA6F5… before the request, it would keep only the first two and delete line CD1BA6F5… .
The last rule explains why we included the 36B2ECF4… line in the <orderLines> collection. This resource doesn’t need any update but it must be present in the collection so that the provider does not attempt to delete it.
In this mode, it is very important that the contents of the <orderLines> collection always reflects the full set of lines that the consumer wants the sales order to have. If it contains a line that the provider doesn’t have, the provider creates a new line. If it does not contain a line that the provider has, the provider deletes its non matching line(s). On the other hand, the consumer doesn’t have to include a full payload for every line. It just needs to include the elements that have been modified in existing lines and the elements that are necessary at creation time in new lines.
In this mode, an empty <orderLines> element is interpreted differently to a missing <orderLines> element. If the provider receives an empty <orderLines> element (<orderLines sdata:deleteMissing=”true”/>), it deletes all the sales order lines. If it receives a payload in which the <orderLines> element is missing then it does not modify any lines.
The synchronization protocol imposes the full mode. So all children lists MUST be flagged with sdata:deleteMissing=”true” in synchronization payloads.
A PUT operation MAY update a reference to a resource (a property flagged with sme:relationship=”reference” or sme:relationship=”parent” in the schema) but the update payload SHOULD NOT include any payload details for the referenced resource.
To update a reference, a consumer MUST send a property element carrying either an sdata:key or sdata:uuid attribute that identifies the referenced resource, unless the consumer wants to reset the reference, in which case it MUST send a property element carrying an xsi:nil=”true” attribute.
When a provider receives an update for a reference, it MUST set the reference to the resource identified by sdata:key or sdata:uuid, or reset the reference if the payload element is flagged with xsi:nil=”true”. The provider MUST NOT update the referenced resource, even if the consumer included payload details for the referenced resource.
For example, a consumer can change the contact reference on a sales order by sending the following payload:
<salesOrder xmlns="http://schemas.sage.com/myContract"> <contact sdata:uuid="A8F337CB-8816-490C-13E9-31002CB081F6"/> </salesOrder>
But the consumer cannot update the contact itself in the same request and for example change the contact’s last name. It needs to send as separate PUT request on the contact resource.
An PUT operation MAY update an association (a property flagged with sme:relationship=”association” in the schema) but it MAY only modify the association list. The update payload SHOULD NOT include any payload details for the associated resources.
To update an association, a consumer MUST send a property element containing a list of references to the associated resources. The list of references MAY be sent either in delta mode or in full mode, as described above (list of children). Each entry of the assocation list MUST carry either an sdata:key or sdata:uuid attribute that identifies the associated resource but it SHOULD NOT contain any payload details for the associated resource.
When a provider receives an update for an association, it MUST update the association list. The provider MUST NOT update the associated resource, even if the consumer included payload details for them.
If the association payload contains an entry marked with sdata:isDeleted="true" the provider MUST delete the entry in the association table but it MUST NOT delete the associated resource. For example, in the case of an association between accounts and tax codes, an update on an account resource may delete one or more associations from the account to tax codes but it will never delete the tax code resources themselves.
For example, a consumer can change the list of tax codes for a customer by sending the following payload:
<customer xmlns="http://schemas.sage.com/myContract"> <taxCodes sdata:deleteMissing="true"> <taxCode sdata:uuid="92FE3F52-5529-46b9-A166-79EDFD2D0595"/> <taxCode sdata:uuid="081BA6F5-C6D5-4a9b-9D59-68D43B8C58B5"/> </taxCodes> </customer>
The sdata:deleteMissing flag must be specified if the consumer wants to replace the association list with a new one. Without this flag new elements would be added but old elements that are left out of the new list would remain in the association.
Associations are sometimes bidirectional. In this case, there is often a direction in which the association may be updated and another direction in which it is only available in read-only mode. This MUST be reflected in the schema by setting sme:isReadOnly=”true” on association properties that are read-only. Update payloads SHOULD NOT contain payload for read-only properties (whether they represent an association or not) and SData providers MUST ignore any payload element which is flagged with sme:isReadOnly in the schema.
The consumer can optimize the data transfer by adding the returnDelta=true query parameter to the PUT URL. This parameter tells the SData provider to return only the properties that differ from the request payload, i.e. properties for which the provider overruled the value passed by the consumer or properties that were modified by a side effect. See details in the Query Parameters section.
SData providers MUST support partial updates on all properties that are flagged with minOccurs=0 in the schema. SData providers MUST comply with the rules described above: missing properties MUST be interpreted as "non modified" values. Null values MUST be passed as empty elements that carry an xsi:nil="true" marker. SData providers MUST support both the delta mode and full mode for lists (children and associations). SData consumers MUST set the sdata:deleteMissing flag to true when they send list updates in full mode. SData providers MAY support the partial return feature.