Subscriptions
Publish & Subscribe in ONE Record¶
ONE Record utilizes a publish & subscribe pattern to enable exchanging data updates in a distributed network of ONE Record nodes. More precisely, In ONE Record, so-called Notification data objects are exchanged between applications to inform other ONE Record nodes about data updates.
This chapter describes the publish & subscribe concept and how it MUST be implemented in ONE Record.
Note
Receiving data updates (via notifications) from other ONE Record nodes requires implementation of the Notifications API endpoint (see Notifications)
Guidelines for Subscriptions in ONE Record:
- A Subscription MUST be immutable (unchangeable object), i.e. a Subscription cannot be changed after it is created and published.
- Updating a Subscription requires to delete/revoke the active Subscription and create a new Subscription that MUST be communicated to other ONE Record nodes.
- The publisher of a Notification MUST ensure the guaranteed delivery.
- It is RECOMMENDED to implement for each subscriber and each topic a message queue that is maintained by the publisher. While in transit, data is kept in message queues that ensure integrity and availability of the system. If a subscribing application is unavailable, messages are safely retained until the subscribing application returns to be available.
- The publisher MUST ensure the guaranteed delivery. That means keeping data until the subscriber confirms it has received a particular Notification.
Subscription Data Model
The Subscription and SubscriptionRequest are data classes of the ONE Record API ontology. The properties and relationships to other data classes are visualized in the following class diagram.
classDiagram
direction LR
class Organization{
}
class SubscriptionRequest{
+ hasSubscription: Subscription
}
SubscriptionRequest "1" --> "1" Subscription
class Subscription{
+ hasContentType[]: xsd:string [*]
+ hasDescription: xsd:string [0..1]
+ expiresAt: xsd:dateTime [0..1]
+ hasSubscriber: Organization
+ hasTopicType: TopicType
+ notifyRequestStatusChange: xsd:boolean = FALSE
+ sendLogisticsObjectBody: xsd:boolean = FALSE
+ includeSubscriptionEventType[]: SubscriptionEventType [1..*]
+ hasTopic: xsd:anyURI
}
Subscription "1" --> "1" Organization: hasSubscriber
Subscription --> TopicType
Subscription "1" --> "1..*" SubscriptionEventType
class TopicType{
<<Enumeration>>
LOGISTICS_OBJECT_TYPE
LOGISTICS_OBJECT_IDENTIFIER
}
class SubscriptionEventType{
<<Enumeration>>
LOGISTICS_OBJECT_CREATED
LOGISTICS_OBJECT_UPDATED
LOGISTICS_EVENT_RECEIVED
}
Two scenarios were identified for initiating the publish/subscribe process:
1) Publisher initiates Subscription: After creating and publishing an Logistics Object, the publisher wants to subscribe another organization to receive Notifications about the newly created Logistics Object.
2) Subscriber initiates Subscription: An organization wants to receive Notifications about a specific Logistics Object or a type of Logistics Objects.
Both scenarios are described with examples below. For simplicity reasons, the security part was not detailed in the following diagrams.
Get Subscription information as Publisher¶
If the publisher of a Logistics Object - also called Holder of the Logistics Object
- wants to subscribe a user of a Logistics Object - also called Subscriber
in this scenario - to send Notifications about data updates and/or Logistics Events, the publisher can request the potential subscriber to provide subscription information.
There are two different forms of this scenario:
- The publisher wants to notify the subscriber about changes to Logistics Objects of a certain type (e.g. Waybill or Piece)
- The publisher wants to notify the subscriber about changes to a specific Logistics Object, e.g. https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c
Workflow
As result, for both scenarios the interaction between two ONE Record participants follows the following workflow:
sequenceDiagram
participant PUB as Publisher
participant SUB as Subscriber
PUB->>+SUB: (1) Request subscription information for specific Logistics Object or Logistics Object type<br> GET /subscriptions HTTP/1.1
SUB-->>-PUB: (2) Returning Subscription objects<br> HTTP/1.1 OK<br>Subscription
note over PUB, SUB: After Subscription initiation
PUB->>SUB: (3) Send Notifications about data updates and events<br> POST /notifications HTTP/1.1
(Optional) Step 0 - Create and Publish a Logistics Object
This step is optional, because a subscription MUST also be possible for already existing Logistics Objects.
Step 1 - Retrieve Subscription information
The publisher MAY propose a Subscription to a subscriber by requesting the Subscription information from the potential subscriber.
The publisher sends a GET request to the subscriptions endpoint of a Subscriber with the proposed Logistics Object type or a specific Logistics Object URI using the query parameters topicType
and topic
. If the subscription proposal targets a specific Logistics Object, the subscribers MUST set the topicType=LOGISTICS_OBJECT_IDENTIFIER and provide an accessible Logistics Object URI as topic
parameter. Example:
GET /subscriptions?topicType=https://onerecord.iata.org/ns/api%23LOGISTICS_OBJECT_IDENTIFIER&topic=https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c HTTP/1.1
Host: 1r.example.com
Accept: application/ld+json; version=2.0.0-dev
Step 2 - Acknowledge Subscription request In order to acknowledge a proposal the subscriber response to the request with the Subscription information. Example:
HTTP/1.1 200 OK
Content-Type: application/ld+json; version=2.0.0-dev
Content-Language: en-US
{
"@context": {
"cargo": "https://onerecord.iata.org/ns/cargo#",
"api": "https://onerecord.iata.org/ns/api#"
},
"@id": "https://1r.example.com/subscriptions/5f1a4869-e324-45b1-9ab0-60271ba54185",
"@type": "api:Subscription",
"api:hasContentType": "application/ld+json",
"api:hasSubscriber": {
"@id": "https://1r.example.com/logistics-objects/957e2622-9d31-493b-8b8f-3c805064dbda"
},
"api:hasTopicType": {
"@id": "api:LOGISTICS_OBJECT_IDENTIFIER"
},
"api:includeSubscriptionEventType": [
{
"@id": "api:LOGISTICS_OBJECT_UPDATED"
},
{
"@id": "api:LOGISTICS_OBJECT_CREATED"
},
{
"@id": "api:LOGISTICS_EVENT_RECEIVED"
}
],
"api:hasTopic": {
"@type": "http://www.w3.org/2001/XMLSchema#anyURI",
"@value": "https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c"
}
}
It is the responsibility of the publisher to check the response, i.e. to compare the requested topic/topicType with the topic/topicType in the returned Subscription data object.
The publisher uses the response to store it in a SubscriptionRequest, which can later be referenced in Notifications (cf. isTriggeredBy property in Notification) and used to revoke the subscription. For example, the URI of the newly created SubscriptionRequest could be https://1r.example.com/action-requests/599fea49-7287-42af-b441-1fa618d2aaee
The publisher MUST ensure that the subscriber gets sufficient access to the resulted SubscriptionRequest to request the status of the SubscriptionRequest and can revoke AccessDelegationRequest (see also section about revoking Action Requests
Here an example of a SubscriptionRequest:
{
"@context": {
"api": "https://onerecord.iata.org/ns/api#"
},
"@type": "api:SubscriptionRequest",
"@id": "https://1r.example.com/action-requests/599fea49-7287-42af-b441-1fa618d2aaee",
"api:hasSubscription": {
"@id": "https://1r.example.com/subscriptions/5f1a4869-e324-45b1-9ab0-60271ba54185",
"@type": "api:Subscription",
"api:hasContentType": "application/ld+json",
"api:hasSubscriber": {
"@id": "https://1r.example.com/logistics-objects/957e2622-9d31-493b-8b8f-3c805064dbda"
},
"api:hasTopicType": {
"@id": "api:LOGISTICS_OBJECT_IDENTIFIER"
},
"api:includeSubscriptionEventType": [
{
"@id": "api:LOGISTICS_OBJECT_UPDATED"
},
{
"@id": "api:LOGISTICS_OBJECT_CREATED"
},
{
"@id": "api:LOGISTICS_EVENT_RECEIVED"
}
],
"api:hasTopic": {
"@type": "http://www.w3.org/2001/XMLSchema#anyURI",
"@value": "https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c"
}
},
"api:isRequestedBy": {
"@id": "https://1r.example.com/logistics-objects/957e2622-9d31-493b-8b8f-3c805064dbda"
},
"api:hasRequestStatus": {
"@id": "api:REQUEST_PENDING"
},
"api:isRequestedAt": {
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
"@value": "2023-04-20T10:38:01.000Z"
}
}
(SubscriptionRequest_example.json)
Note
The @id found in the subscription details provided by the Subscriber is not mandatory, but it can serve as a means for the Subscriber to manage various subscriptions. The Subscriber has the freedom to determine the format of this identifier. To access the SubscriptionRequest, both the Subscriber and the Publisher must utilize the id assigned by the Publisher for the SubscriptionRequest (https://1r.example.com/action-requests/599fea49-7287-42af-b441-1fa618d2aaee in the example), rather than the id of the Subscription.
Step 3 - Send Notification to Subscribers
Once the subscription information is received the publisher sends notifications to the subscriber using the details provided. If a subscriber was not available at the time, then the publisher MUST need to queue and retry to notify the Subscriber. Example:
POST /notifications HTTP/1.1
Host: 1r.example.com
Content-Type: application/ld+json; version=2.0.0-dev
Accept: application/ld+json; version=2.0.0-dev
{
"@context": {
"api": "https://onerecord.iata.org/ns/api#"
},
"@type": "api:Notification",
"api:hasEventType": {
"@id": "api:LOGISTICS_OBJECT_CREATED"
},
"api:hasLogisticsObject": {
"@id": "https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c"
},
"api:hasLogisticsObjectType": {
"@type": "http://www.w3.org/2001/XMLSchema#anyURI",
"@value": "https://onerecord.iata.org/ns/cargo#Piece"
},
"api:isTriggeredBy": {
"@id": "https://1r.example.com/action-requests/599fea49-7287-42af-b441-1fa618d2aaed"
}
}
Note
Notifications will be triggered for the creation of a new Logistics Event on a Logistics Object solely when the subscription property 'includeSubscriptionEventType' contains the value 'LOGISTICS_EVENT_RECEIVED'. On the contrary, this notification will be omitted.
Endpoint¶
Request¶
The following HTTP query parameters MUST be supported:
Query parameter | Description | Valid values / Examples |
---|---|---|
topicType | Used by the publisher to specify if Subscription information for a specific Logistics Object or a data class should be in the response body. When passed in a URL, the topicType must be URL encoded (i.e # becomes %23 or can be replaced with / ) |
|
topic | Used by the publisher to specify the data class or Logistics Object URI the Subscription information should be related to. topic MUST be a valid URI |
|
The following HTTP header parameters MUST be present in the request:
Header | Description | Examples |
---|---|---|
Accept | The content type that a ONE Record client wants the HTTP response to be formatted in. This SHOULD include the version of the ONE Record API, otherwise the latest supported ONE Record API MAY be applied. |
|
Response¶
A successful request MUST return a HTTP/1.1 200 OK
status code.
The body of the response includes the list of subscriptions in the RDF serialization format that has been requested in the Accept
header of the request.
This list of subscriptions MAY also be empty.
The following HTTP headers parameters MUST be present in the response:
Header | Description | Example |
---|---|---|
Content-Type | The content type that is contained with the HTTP body. | application/ld+json |
Content-Language | Describes the language(s) for which the requested resource is intended. | en-US |
The following HTTP status codes MUST be supported:
Code | Description | Response body |
---|---|---|
200 | The request to retrieve the Subscription Information has been successful | Subscriptions |
400 | The request is invalid | Error |
401 | Not authenticated | Error |
403 | Not authorized to retrieve the Subscription Information | Error |
500 | Internal Server Error | Error |
Security¶
To engage with the "Get Subscription" endpoint, a client needs proper authentication. If requests lack proper authentication, the ONE Record server should respond with a 401 "Not Authenticated"
status.
Example A1¶
Request subscription information for specific Logistics Object URI.
Request:
GET /subscriptions?topicType=https://onerecord.iata.org/ns/api%23LOGISTICS_OBJECT_IDENTIFIER&topic=https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c HTTP/1.1
Host: 1r.example.com
Accept: application/ld+json; version=2.0.0-dev
Response:
HTTP/1.1 200 OK
Content-Type: application/ld+json; version=2.0.0-dev
Content-Language: en-US
{
"@context": {
"cargo": "https://onerecord.iata.org/ns/cargo#",
"api": "https://onerecord.iata.org/ns/api#"
},
"@id": "https://1r.example.com/subscriptions/5f1a4869-e324-45b1-9ab0-60271ba54185",
"@type": "api:Subscription",
"api:hasContentType": "application/ld+json",
"api:hasSubscriber": {
"@id": "https://1r.example.com/logistics-objects/957e2622-9d31-493b-8b8f-3c805064dbda"
},
"api:hasTopicType": {
"@id": "api:LOGISTICS_OBJECT_IDENTIFIER"
},
"api:includeSubscriptionEventType": [
{
"@id": "api:LOGISTICS_OBJECT_UPDATED"
},
{
"@id": "api:LOGISTICS_OBJECT_CREATED"
},
{
"@id": "api:LOGISTICS_EVENT_RECEIVED"
}
],
"api:hasTopic": {
"@type": "http://www.w3.org/2001/XMLSchema#anyURI",
"@value": "https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c"
}
}
Example A2¶
Request subscription information for a Logistics Object type
Request:
GET /subscriptions?topicType=https://onerecord.iata.org/ns/api%23LOGISTICS_OBJECT_TYPE&topic=https://onerecord.iata.org/ns/cargo#Shipment
Host: 1r.example.com
Content-Type: application/ld+json; version=2.0.0-dev
Response:
HTTP/1.1 200 OK
Content-Type: application/ld+json; version=2.0.0-dev
Content-Language: en-US
{
"@context": {
"cargo": "https://onerecord.iata.org/ns/cargo#",
"api": "https://onerecord.iata.org/ns/api#",
"publisher": "https://1r.example.com/",
"subscriber": "https://1r.example.com/"
},
"@id": "publisher:subscriptions/5f1a4869-e324-45b1-9ab0-60271ba54185",
"@type": "Subscription",
"api:contentType": "application/ld+json",
"api:hasSubscriber": {
"@id": "subscriber:logistics-objects/957e2622-9d31-493b-8b8f-3c805064dbda"
},
"api:includeSubscriptionEventType": [
{
"@id": "api:LOGISTICS_OBJECT_UPDATED"
},
{
"@id": "api:LOGISTICS_OBJECT_CREATED"
},
{
"@id": "api:LOGISTICS_EVENT_RECEIVED"
}
],
"api:hasTopicType": {
"@id": "api:LOGISTICS_OBJECT_TYPE"
},
"api:hasTopic": {
"@type": "http://www.w3.org/2001/XMLSchema#anyURI",
"@value": "https://onerecord.iata.org/ns/cargo#Shipment"
}
}
Example A3¶
Request subscription information for a not supported Logistics Object type
Request:
GET /subscriptions?topicType=https://onerecord.iata.org/ns/api%23LOGISTICS_OBJECT_TYPE&topic=https://onerecord.iata.org/ns/cargo#ForkLift
Host: 1r.example.com
Accept: application/ld+json
Response:
HTTP/1.1 400 Bad Request
Content-Type: application/ld+json; version=2.0.0-dev
Content-Language: en-US
{
"@context": {
"api": "https://onerecord.iata.org/ns/api#",
"@language": "en-US"
},
"@type": "api:Error",
"@id": "_:b0",
"api:hasTitle": "Logistics Object Type not supported",
"api:hasErrorDetail": [{
"@type": "api:ErrorDetail",
"@id": "_:b1",
"api:hasCode": "400",
"api:hasMessage": "Provided Logistics Object Type is not supported"
}]
}
Example A4¶
Required parameter topic=
is missing in the HTTP query parameters.
Request:
GET /subscriptions?topicType=https://onerecord.iata.org/ns/api%23LOGISTICS_OBJECT_TYPE
Host: 1r.example.com
Accept: application/ld+json
Response:
HTTP/1.1 400 Bad Request
Content-Type: application/ld+json; version=2.0.0-dev
Content-Language: en-US
{
"@context": {
"api": "https://onerecord.iata.org/ns/api#",
"@language": "en-US"
},
"@type": "api:Error",
"@id": "_:b0",
"api:hasTitle": "Missing query parameter",
"api:hasErrorDetail": [{
"@type": "api:ErrorDetail",
"@id": "_:b1",
"api:hasCode": "400",
"api:hasMessage": "The required query parameter `topic` is missing."
}]
}
Subscribe to Logistics Objects¶
In the second scenario, the subscriber initiates the subscription process by actively sending subscription information to the publisher. The subscription information can either specify a type of Logistics Object or a specific Logistics Object. The publisher creates a SubscriptionRequest from the submitted Subscription.
sequenceDiagram
participant SUB as Subscriber
participant PUB as Publisher
SUB->>+PUB: Subscribe to either a concrete Logistics Objector a Logistics Object type <br> POST /subscriptions HTTP/1.1
PUB-->>-SUB: Acknowledge Subscription request and return location of SubscriptionRequest<br>HTTP/1.1 204 No Content <br> Location: <URI of SubscriptionRequest>
note over PUB, SUB: After subscription proposal process
PUB->>SUB: Send Notifications about data updates and events<br> POST /notifications HTTP/1.1
The ONE Record server must validate that :
- the topicType
is one of the types described in the ONE Record API ontology
- the topic
is a valid Logistics Object (i.e. https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c)or a supported Logistics Objects type (i.e https://onerecord.iata.org/ns/cargo#Shipment)
The Callback URL can be derived from the Organization IRI provided in the hasSubscriber
property of the body request.
Endpoint¶
Request¶
The following HTTP header parameters MUST be present in the request:
Header | Description | Examples |
---|---|---|
Accept | The content type that a ONE Record client wants the HTTP response to be formatted in. This SHOULD include the version of the ONE Record API, otherwise the latest supported ONE Record API MAY be applied. |
|
Content-Type | The content type that is contained with the HTTP body. Valid content types. | application/ld+json |
The HTTP body must contain a valid Subscription object in the format as specified by the Content-Type in the header.
Response¶
A successful request MUST return a HTTP/1.1 201 Created
status code and the following HTTP headers parameters MUST be present in the response:
Header | Description | Examples |
---|---|---|
Location | The URI of the newly created Logistics Object | https://1r.example.com/action-requests/599fea49-7287-42af-b441-1fa618d2aaed |
Type | The type of the newly created Logistics Object as a URI | https://onerecord.iata.org/ns/api#SubscriptionRequest |
The following HTTP status codes MUST be supported:
Code | Description | Response body |
---|---|---|
201 | Subscription Request has been created | No response body |
400 | Bad Request | Error |
401 | Not authenticated | Error |
403 | Not authorized | Error |
415 | Unsupported Content Type | Error |
500 | Internal Server Error | Error |
Security¶
To engage with the "Get Subscription" endpoint, a client needs proper authentication. If requests lack proper authentication, the ONE Record server should respond with a 401 "Not Authenticated"
status.
Example B1¶
Request subscription for a specific Logistics Object URI.
Request:
POST /subscriptions HTTP/1.1
Host: 1r.example.com
Accept: application/ld+json; version=2.0.0-dev
Content-Type: application/ld+json; version=2.0.0-dev
{
"@context": {
"cargo": "https://onerecord.iata.org/ns/cargo#",
"api": "https://onerecord.iata.org/ns/api#"
},
"@type": "api:Subscription",
"api:hasContentType": "application/ld+json",
"api:hasSubscriber": {
"@id": "https://1r.example.com/logistics-objects/957e2622-9d31-493b-8b8f-3c805064dbda"
},
"api:hasTopicType": {
"@id": "api:LOGISTICS_OBJECT_IDENTIFIER"
},
"api:includeSubscriptionEventType": [
{
"@id": "api:LOGISTICS_OBJECT_UPDATED"
},
{
"@id": "api:LOGISTICS_OBJECT_CREATED"
},
{
"@id": "api:LOGISTICS_EVENT_RECEIVED"
}
],
"api:hasTopic": {
"@type": "http://www.w3.org/2001/XMLSchema#anyURI",
"@value": "https://1r.example.com/logistics-objects/1a8ded38-1804-467c-a369-81a411416b7c"
}
}
Response:
HTTP/1.1 201 Created
Location: https://1r.example.com/action-requests/599fea49-7287-42af-b441-1fa618d2aaed
Content-Type: application/ld+json; version=2.0.0-dev
Type: https://onerecord.iata.org/ns/api#SubscriptionRequest
Subscriptions with 3rd parties¶
It is feasible to initiate a subscription on behalf of a third party. In such instances, the individual making the request will differ from the individual intended to receive the notifications.
The entity responsible for implementing the ONE Record Server is obligated to establish a mechanism to verify the legitimacy of the intended recipient for receiving these notifications. There are scenarios where the implementor might choose to disallow this capability and verify that the authenticated user matches the intended receiver.
Note
Notifications are sent to the subscriber's Notification endpoint. It MUST be assumed that the subscriber performs the internal forwarding of the received notifications to the backend systems or third parties for further processing.
Unsubscribe¶
Due to changing information needs, a subscribed user of a logistics object may no longer wish to receive notifications of logistics object updates. To unscribe, an active Subscriber MUST revoke a SubscriptionRequest. This is explained in the Revoke Action Request section.
The ActionRequest URI required for the Revocation is
provided to the User with the isTriggeredBy
property in the Notification) data object.
Or it is given to the user in the HTTP response header Location
to the subscription request.