#FHIR and Postels Robustness Principle
Dec 8, 2015An important principle in interoperability is Postel’s Robustness Principle:
Be conservative in what you send, be liberal in what you accept
There’s been quite a bit of discussion recently in various implementation forums about robustness of FHIR interfaces, and I think there’s a few things to say about how to develop robust FHIR principles.
Firstly, the FHIR core team endorses Postel’s principle - the pathway to robust interoperability is to be careful to be conformant in what you send, and to be as accepting as possible in what you receive. However, in practice, it’s not necessarily easy to see how to implement like that.
There’s also some circumstances where this isn’t what you should do. As an example, when I started writing my reference server, I followed Postel’s law, and accepted whatever I could accept. However this fostered non-conformant implementations, so on the behest of the community, I’ve been gradually tightening up the rigor with which my server enforces correctness on the clients. For example, my server validates all submitted resources using the formal FHIR validator. Somewhat unfortunately, the main effect that has had is that implementers use one of the other servers, since their client works against that server. This’ll get worse when I tighten up on validating content type codes in the lead in to the Orlando Connectathon. Generally, if an implementation is used as a reference implementation, it should insist that trading partners get it right, or else all implementations will be forced to be as generous as the reference implementation.
But let’s assume you wanted to follow Postel’s law. What would that mean in practice, using a FHIR RESTful interface?
Reading/Writing Resources
If you’re creating a resource, then you can start by ensuring that your XML or JSON is well formed. It’s pretty much impossible for a receiver to process improperly formed XML or JSON (or it’s at least very expensive), but experience shows that many implementers can’t even do that (or here), and I’ve seen this a lot. So for a start, never use string handling routines to build your resources - eventually, you’ll produce something invalid. Always always use a DOM or a writer API.
Beyond this:
- Ensure that all mandatory elements are present
- Ensure that the correct cardinalities apply
- Ensure that you use the right value sets
- Always use UTF-8
- etc
In fact, in general, if you are writing a resource, you should ensure that it passes the validator (methods for validation), including checking against the applicable profiles (whether they are explicit - stated in Resource.meta.profile - or implicit - from the conformance statement or other contextual clues).
If you’re reading a resource, then:
- Only check the content of the elements that you have to use
- Accept non-UTF-8 encoding
- Only check for modifier extensions on the elements you actually use, and don’t check for other extensions (only look for extensions you know)
- accept invalid codes for Coding/CodeableConcept data types (further discussion below)
However, there’s not that much you can be graceful about with the content; generally, if you have to use it, it has to be right.
Using the RESTful API
In practice, when using the API, clients should ensure that they:
- use the correct mime types for content-type and accept, and they always specify a mime type (never leave it to the server)
- construct the URL correctly, and all the escapable characters are properly escaped
- they use the correct case for the URL
- they look for ‘xml’ or ‘json’ in the return content-type, and parse correctly without insisting on the correct mime type
- they can handle redirects and continue headers correctly
Servers should:
- accept any mime types that have ‘xml’ or ‘json’ in them
- only check headers they have to
- accept URLs where not all the characters are escaped correctly (in practice, ‘ ‘, =, ?, and + have to be escaped, but other characters sometimes aren’t escaped by client libraries)
- always return the correct FHIR mime types for XML or JSON
- always return the correctCORS headers
- ignore case in the URL as much as possible
- only issue redirects when they really have to
Note: the full multi-dimensional grid of request/response mime types, and the _format header is long and complex, so we’ve not specified the entire thing. As a consequence, outside of these recommendations above, there’s dangerous waters to be encountered.
HTTP Parameters
One area that’s proven controversial in practice is how to handle HTTP parameters. With regard to search, the FHIR specification is quite specific: a server SHALL ignore HTTP parameter that it does not understand. This is because there may be reasons that a client has to add a parameter to the request because of requirements imposed by HTTP agents that intercept the request before it hit’s the FHIR server (this may be clients, proxies, or filters or security agents running on the server itself). In the search API, a server specifically tells a client which parameters it processed in the search results (Bundle.links, where rel = ‘self’), but this doesn’t happen in other GET requests (read, vread, conformance).
For robustness, then, a client should:
- Only use parameters defined in the specification or in the servers conformance statement (if possible)
- check search results to confirm which ones were processed (if it matters)
A server should:
- ignore parameters it doesn’t recognise
- return HTTP errors where parameters it does recognise are inapplicable or have invalid content, or where it cannot conform to the requested behaviour
ValueSet Variance
The things above really deal with syntactical variance. Postel’s Principle is relatively easy to apply in this way. It’s much harder to apply when the underlying business process vary. Typical examples include:
- exchanging data between 2 business process that use different fields (e.g. they care about different things)
- exchanging data between 2 business processes that use text/structured data differently (e.g. structured dosage vs a single text ‘dosage instructions’ field)
- exchanging data between systems that use different value sets
To grapple with these issues, I’m going to work with the last example; it’s the easiest to understand and apply, though the basic principles apply to the others as well. In this case, we have 2 applications exchanging data between them, and they support different sets of codes. There’s a few different possibilities:
- A sends B a code it doesn’t know
- A sends B a code for something which is different to the one B uses
- Either of those 3 cases, but B edits the record, and returns it to A
The way the CodeableConcept data type works is intimately linked to practical resolutions to these common cases. In order to support these cases, it has a text representation, and 0 or more Codings:
In HL7’s experience, Postel’s Principle, as applied to the exchange of coded information, says that
- The source of the information should provide text, and all the codes they know
- The text should be a full representation of the concept for a human reader
- It is understood that the codings may represent the concept with variable levels of completeness e.g. the Concept might be ‘severe headache’, but the coding omits ‘severe’ and just represents ‘headache’
Note: there’s a wide variety of workflows that lead to the choice of a concept, and the process for selecting the text and the multiple codings varies accordingly. Since the subtle details of the process are not represented, the most important criteria for the correct choice of text is ‘does a receiver needs to know how the data was collected to understand the text’
- a receiver of information should retain the text, and all the provided codes
- When displaying the information to a user, the text is always what should be shown, and the formal codings may be shown additionally (e.g. in a hint, or a secondary data widget)
- Decision support may choose one of the codes, but the user should always have a path back to view the text when (e.g.) approving decision support recommendations
- When sending information on, a receiver should always send the original text and codes, even if it adds additional codes of it’s own
- When a user or process changes the code to another value, all the existing codes should be replaced, and the text should be updated
Note: this implies that there’s a process difference between ‘adding another code for the same concept’ and ‘changing the concept’ and this change should be reflected in the information level APIs and surfaced in the workflow explicitly. But if there’s no difference…
- if a system receives an update to a coded element (from UI or another system) that contains a different text, and codings, but at least one of the codings is the same, then this should be interpreted as ‘update the existing concept’. The text should be replaced and the codings merged
Many, if not most, systems, do not follow this advice, and these often have workflow consequences. Note, though, that we’re not saying that this is the only way to manage this; more specific workflows are appropriate where more specific trading partnership details can be agreed. But the rules above are a great place to start from, and to use in the general case.
Beyond this general advice, specific advice can be provided for particular contexts. Here, for instance, is a set of recommendations for RxNorm:
- Don’t throw away codes (as suggested above). The system originating data needs to expose RxNorm codes, but has good reason to include any “real” underlying codes it used, if different (e.g. FDB). And downstream proxies, CDRs, EWDs, interface engines, etc. shouldn’t remove codes. FHIR has a way to indicate which was the“primary” codecorresponding to what a clinician saw.
- Senders should expose valid RxNorm codes at the level of SCD, SBD, GPCK, or BPCK prescribables, not ingredients or dose forms. Namely, these codes should appear in RxNorm and mean the thing you want to say. It’s possible they may not be in the current “prescribable” list at the time you generate a data payload (e.g. for historical records), but active prescriptions should be. Furthermore, the conservative practice is to always use an up-to-date release of RxNorm. (And by RxNorm’s design, any codes that were once valid should be present in all future versions of RxNorm, even if obsolete.) These codes might not be marked “primary” in FHIR’s sense of the word
- Recipients should use the most recent version of RxNorm available, and should look through all codings in a received medication to find an RxNorm SCD, SBD, GPCK, or BPCK. If you find such a code and don’t “understand” it, that’s a client-internal issue and it should be escalated/handled locally. If you don’t find such a code, that’s a potential data quality issue; clients should log a warning and use any code they understand, or display the text value(s) to a user.
There’s a whole art form around version management of terminologies. I’ll take that up in a later post.
Dealing with People
One last comment about Postel’s principle: Interoperability is all about the people, and the same principle applies. If you want to interoperate, you need to get on with people, and that means that you need to use Postel’s principle:
Be generous with what other people say, be disciplined with what you say
A community of people who want to interoperate with others - I’d like to be part of that. But no, I already am! The FHIR community has been very good at this over the last few years.
p.s. this post dealt with the RESTful interface, but the basic principles apply in other contexts of use as well.