Content Negotiation
Introduction
Content Negotiation within BaRS leverages multiple variables within the CapabilityStatement and MessageDefinition to ensure that a Sender and a Receiver are Compatible. Though some of this is possible due to the Versioning Negotiation, Content Negotiation further builds on that concept with the capabilities published by the server and the identification of message definitions and use cases therein to ensure a workflow can be completed.
Information obtained from GET /metadata and GET /MessageDefinition will provide the information required to complete Content Negotiation. Along side this, the version variable in the ACCEPT header is also utilised for version negotiation.
Core vs Application
Within BaRS Core defines the version negotiation, leveraging the accept header completed using SemVer.
The Applications, also using semver, define the content negotiation and, though they employ a similar version negotiation paradigm, are not the same as Core.
These two things combined ensure a Sender and a Receiver are fully compatible within a given workflow.
Mechanism
Identifying Capabilities
A particular workflow may leverage an API capability that is not always implemented by a receiver. For example GET /ServiceRequest. The GET /metadata response provides the ability to define what is and is not available. For example, the following entry would be present under the rest.resource element, where the mode is defined as "server", confirming the server can handle the requests required.
{ "type": "ServiceRequest", "interaction": [ { "code": "read" }, { "code": "vread" }, { "code": "search-type" } ], "searchParam": [ { "name": "_id", "type": "token", "documentation": "Unique identifier for a ServiceRequest" }, { "name": "patient:identifier", "type": "token", "documentation": "NHS Number (system must be https://fhir.nhs.uk/Id/nhs-number)" } ], "versioning": "no-version" }
Another capability described within the CapabilityStatement is the definition of what MessageDefinitions are supported by the Receiver, prior to a GET /MessageDefinition request which will define the message definitions themselves. This will be an array under messaging.supportedMessage. This also defines if they are a Sender of the defined message type or a Receiver.
"supportedMessage": [ { "mode": "receiver", "definition": "MessageDefinition/bars-message-booking-request" }, { "mode": "receiver", "definition": "MessageDefinition/bars-message-servicerequest-request-referral" } ]
The receivers responsibility
The receiver at this level only needs to evaluate the Accept Header to ensure there is no major version discrepancy. A 406 HTTP response with the appropriate OprationOutcome should be given if this is the case.
Identifying MessageDefinitions
A Receiver's MessageDefinitions will contain several identifiers that will allow a Sender to ascertain whether their use-case workflow can be completed, beyond what the CapabilityStatement has already confirmed, supporting Content negotiation. The Message Definition will also contain a version number for version negotiation, along with the fundamental intent of the MessageDefinitions, defining the construct of the message (payload) a Sender must build. Example of identifiers are as follows:
- Name or URL in MessageDefinition.url / MessageDefinition.name : https://fhir.nhs.uk/MessageDefinition/bars-message-servicerequest-request
- Use case in MessageDefinition.useContext.code[]: https://fhir.nhs.uk/CodeSystem/usecases-categories-bars.
- Service in MessageDefinition.useContext.code[]: https://fhir.nhs.uk/CodeSystem/dos-id
- Version in MessageDefinition.version
The Name or URL define the type of message being sent. The Service id is confirmation of the correct Service. The Use Case Category code(s) define the care setting context required to ensure the message is actionable by the service. The Version allows for Version negotiation. All of these items need to be checked. There may be variations of the same message definition for a given service based on these variables.
useContext
use case
The Use-Case-Category is a codeSystem that defines the use case for the message, this is defined under the Application the use case is within. A Sender, wanting to build a message for a particular use case, will look for its corresponding code, from the identifier codeSystem, and include it in the subsequent message request they build. This code can also be included in the context query parameter for GET /MessageDefinition, to assist in filtering. Display names for these codes should never be surfaced to an end user, these are metadata, for background processing.
Service
The Service describes if the MessageDefinition is applicable for this service. This is something that should be implied by the query parameter for GET /MessageDefinition, which acts as a filter. This capability is already described here.
Version
The same SemVer rules are applied to the MessageDefinition versions. It could be the case that multiple MessageDefinitions are returned and in this scenario a Sender should select the closest version to their own from a Minor Revision and Patch perspective. If a Major version difference is detected then the Sender cannot continue unless another option is available in the response.
The receiver at this level only needs to evaluate the Accept Header, again, here to ensure there is no major version discrepancy, and also the service Id in the use context to ensure the correct MessageDefinitions are returned. This may already be taken care of in the logic handling the NHSD-Target-Identifier, however as they are contextually different fields, it is emphasised in this section of the guide. A 404 HTTP response should be given, with an appropriate OperationOutcome, if the context is incorrect.
Workflow
The following sequences give a basic overview of the roles of both the Sender and the Receiver for Content Negotiation:
CapabilityStatement
The response to the GET /metadata is evaluated by the sender. The normal Version header checks occur. If a CapabilityStatement is returned and all the required items are present for the desired workflow (Version, Functions, MessageDefinitions), the workflow can continue. Else, the Sender should stop.
MessageDefinition
The response to the GET /MessageDefinition is evaluated by the sender. The normal Version header checks occur. If the Message Definition version is acceptable and the use case category and service id match what is expected for the relevant message definition, continue including the use case category in the body of the next request. Else, the Sender should stop.
$process-message
The sending system sends a ServiceRequest/Booking request and the Receiver checks the use case category code to ensure that service can accept it as per the MessageDefinition associated with the service. The normal Version header checks occur.
Sender CapabilityStatement
Upon Receiving a CapabilityStatement in response to GET /metadata from the receiver, the Sender needs to confirm the Receiver is capable of completing the desired workflow. This means it must contain all the required functions in the mode of server, at a compatible version, supporting the MessageDefinitions defined by the applications.
The below pseudo code gives an example of how to accomplish this.
bool Content_Negotiation_Client_CapabilityStatement() { List RequiredCapabilities = WorkflowCapabilityRequirements; var CoreVersion = "1.1.0"; var Service = GetDoSid(); var DesiredMessagetype = "https://fhir.nhs.uk/MessageDefinition/bars-message-servicerequest-request"; // GET /metadata ResponseCapabilityStatement = GetMetadata( NHSD-Target-Identifier = Service, AcceptHeader += CoreVersion; ); bool compatible = false; //Versioning if (ResponseCapabilityStatement.version == CoreVersion) { compatible = true; } else if(ResponseCapabilityStatement.version.MajorVersion > CoreVersion.MajorVersion) { compatible = false; return compatible; //Version is incompatible. } else { compatible = true; } foreach (RequiredCapability in RequiredCapabilities) { if(!ResponseCapabilityStatement.Capabilities.Contains(RequiredCapability)) { compatible = false; return compatible; //not all capabilities supported } else { compatible = true; } } if(!ResponseCapabilityStatement.messaging.supportedMessage.Contains(DesiredMessagetype as receiver)) { compatible = false; //required message definition not supported. return compatible; } else { compatible = true; } return compatible; }
Receiver $process-message
Upon Receiving a message, along with the existing validation and checks present within the rest of BaRS Core and the relevant application; the Receiver should check that the use case category is correct in serviceRequest.category.
The below pseudo code gives an example of how this should be carried out.
Receive_Request { string requestUseCase; //Versioning if (ACCEPTHeader.version == CoreVersion) { compatible = true; } else if(ACCEPTHeader.version.MajorVersion > CoreVersion.MajorVersion) { compatible = false; OperationOutcome.issue.code = "not-supported"; throw exception with "REC_NOT_SUPPORTED"; then return with HTTP.ResponseCode 406; } //get the use case from the request foreach(codeEntry in ServiceRequest.category.coding) { if (codeEntry.system == "https://fhir.nhs.uk/CodeSystem/usecase-categories-bars") { requestUseCase = codeEntry.code; } } // check that use case is applicable to the requested service if (requestUseCase not in GetServiceUseCases(MessageHeader.destination.endpoint)) { OperationOutcome.issue.code = "not-supported"; throw exception with "REC_UNPROCESSABLE_ENTITY"; then return with HTTP.ResponseCode 422; } }