skip to content

In OData's multipart $batch format, how do you create an order and its lines so that they succeed or fail together?

level: middleimportance: must knowfreq 20%

answer

  1. a multipart inside the multipart
  2. change set means all or nothing
  3. Content-ID on the MIME part
  4. $1 as the first URL segment

basics

~20 s

Put both inserts in one change set, a nested multipart/mixed part whose operations each carry a Content-ID. POST the order as Content-ID 1, then POST the lines to $1/Items; the service applies all of them or none.

solid answer

~50 s

In the multipart `$batch` format, a **change set** is a body part that is itself a `multipart/mixed` document; each operation in it is an `application/http` part that MUST carry a `Content-ID` unique within the batch. I would POST the order with `Content-ID: 1`, then POST each line to `$1/Items`, where `$1` stands for the URL of the order just created. The service MUST apply every request in the change set or none of them, and it MAY execute them and return their responses in any order, so the client correlates by `Content-ID`, not position. On success the change set's response is a nested multipart of `201 Created` parts; if any operation fails, the whole change set is answered by one `application/http` part holding a single OData error. In OData 4.0, `$1` works only inside the same change set.

code

http · 26 lines
http
POST /sales/$batch HTTP/1.1
Host: api.example.com
OData-Version: 4.0
Content-Type: multipart/mixed; boundary=batch_1

--batch_1
Content-Type: multipart/mixed; boundary=changeset_1

--changeset_1
Content-Type: application/http
Content-ID: 1

POST Orders HTTP/1.1
Content-Type: application/json

{"CustomerId": "C042", "Currency": "EUR"}
--changeset_1
Content-Type: application/http
Content-ID: 2

POST $1/Items HTTP/1.1
Content-Type: application/json

{"Sku": "TEA-250", "Quantity": 3}
--changeset_1--
--batch_1--

go deeper

for a junior

Recall that a change set is a nested multipart part inside the batch whose operations are applied all together or not at all.

for a middle

Explain Content-ID on each operation, $1 references to a new entity, any-order execution inside the set, and the single error part a failed set returns.

for a senior

Show you know atomicity stops at the change set boundary, so reads and other sets need their own guarantees, and that cross-set references depend on version and advertised support.

for a principal

Judge when grouped writes belong in one change set and when the unit of consistency should instead be a single deep insert or a server-side action.

## The pieces of a multipart batch In the multipart batch format, the outer request is a `POST` to `$batch` with `Content-Type: multipart/mixed` and a `boundary` parameter. Its body is a sequence of **top-level parts**, each preceded by a boundary delimiter line, and each part is one of two things: - **An individual request**: a body part with `Content-Type: application/http` whose content is a complete HTTP request (request line, headers, blank line, body). - **A change set**: a body part that is *itself* a `multipart/mixed` document with its own boundary, holding one `application/http` part per operation. Change sets group data modification requests and action invocations; OData 4.0 states that a change set MUST NOT contain `GET` requests or other change sets. - **A Content-ID on every change-set operation**: each part representing an operation in a change set MUST carry a `Content-ID` header whose value is unique within the batch. OData 4.0 notes that it is a header of the MIME part itself, not of the HTTP request inside it. - **Three URL forms**: an inner request URL may be an absolute URI, an absolute path with a separate `Host` header, or a path relative to the batch request URL; services MUST support all three. ## Building the order-and-lines change set For a mobile client that must submit an order header and its lines as one unit: 1. Open one change set inside the batch. 2. Add the order insert, `POST Orders`, as an operation part with `Content-ID: 1`. 3. Add each line insert as its own operation part, posting to `$1/Items`. `$1` is the request identifier prefixed with `$`; the service replaces it with the URL of the entity that request created, so each line lands under the new order through its `Items` navigation property. 4. Give every operation a distinct `Content-ID` (2, 3, ...) so its response can be matched later. 5. Put credentials and the `OData-Version` header on the outer `POST`, not in the parts. ## What all-or-nothing means, and what it does not All requests in a change set are a **single change unit**: the service MUST successfully process and apply every one of them or apply none. How the service undoes an operation that had already run when a later one failed is up to the service implementation; the observable outcome is what the protocol fixes. - **Order inside a change set is not significant.** The service MAY execute its operations in any order and MAY return their responses in any order, and it MUST echo each request's `Content-ID` in the matching response. Express dependencies with `$` references, never with position. - **Order outside change sets is significant.** The service MUST process the top-level requests and change sets in the order received. - **Atomicity stops at the change set.** A `GET` before it or another change set after it is a separate unit; the batch as a whole is not one transaction. For reads that are consistent as of one point in time across the batch, OData offers the `Isolation: snapshot` header (named `OData-Isolation` in 4.0). ## The response The outer response is still `200 OK`; the change set's outcome sits in its own part. | Outcome | What the change set's part contains | |---|---| | Every operation succeeded | a nested `multipart/mixed` part with one `application/http` response per operation, each carrying its `Content-ID`, for example `201 Created` with a `Location` for each insert | | Any operation failed | a single `application/http` response, a valid OData error, that applies to the whole change set | The second row is one of the protocol's three exceptions to the rule that the response mirrors the request part for part. URLs in responses MUST NOT contain `$`-prefixed identifiers: the client gets the new order's real URL. ## Referencing rules across versions - **OData 4.0**: an entity created by an insert in a change set can be referenced by later requests in the *same* change set, in the request URL and, where the service supports it, in request bodies. - **OData 4.01**: request identifiers may be set on any request, services MAY support references across change sets (advertised with `ReferencesAcrossChangeSetsSupported` in the Capabilities `BatchSupport` annotation), and a later request may send `If-Match: $1` to reuse the ETag an earlier request returned. - **Name collisions**: if a `$`-prefixed identifier matches a top-level system resource such as `$metadata`, `$batch`, `$all`, `$entity`, `$root`, `$id` or `$crossjoin`, the system resource wins. Numeric identifiers avoid it.

  • Can a request in a later top-level part of an OData batch use $1 from an earlier change set?
    Not in OData 4.0: a new entity's `Content-ID` reference is valid only inside the same change set. OData 4.01 lets a service support references across change sets, and a service that does SHOULD advertise `ReferencesAcrossChangeSetsSupported` in its Capabilities `BatchSupport` annotation, so a client checks that before relying on it.
  • Does an OData change set also make a GET elsewhere in the same batch see consistent data?
    No. Atomicity covers only the writes inside the change set. For reads consistent as of one point in time across the batch, the client sends `Isolation: snapshot` (`OData-Isolation` in 4.0) on the batch request, and a service that does not support it MUST refuse the request with `412 Precondition Failed`.
  • What does $metadata refer to if an OData batch request was given the Content-ID "metadata"?
    The service's metadata document, not the new entity. When a `$`-prefixed identifier matches a top-level system resource such as `$metadata`, `$batch` or `$all`, the protocol resolves it to the system resource. Numeric request identifiers avoid the collision.

saying these in an interview costs you the question

  • Operations inside a change set always run in the order they are written.
  • In OData 4.0 a change set may also hold the GET that reads the order back.
  • The Content-ID header belongs among the embedded HTTP request's own headers.
  • A failed change set returns one error part per operation inside it.
  • The whole $batch is a single transaction across all of its parts.
  • In OData 4.0, $1 can be referenced from any later part of the batch.