Within OpenMRS, a complex observation (complex obs) may contain any arbitrary value. It may be a short string or it may be a giant digital video. In most cases, the complex observation's value is something that isn't easily stored in one of the basic datatypes (i.e., concept code, date, number, or string). It's easiest to imagine a complex obs as a document or an image. The job of a complex obs handler is to encapsulate the knowledge of how to store, retrieve, and render the complex value. Each type of complex obs (whether it's an image, a document, a video, or something else) has a corresponding handler that knows how to "handle" it. The purpose of this abstraction is to allow the API to work with these observations very much like any other observation without having to know how to handle any arbitrary datatype that might come along.
The complex obs value is managed through two properties on the obs with very similar names that can be confusing:
- valueComplex – this aligns with our other obs value types (e.g., valueDate, valueNumeric, valueText) and contains a reference to the payload. A more descriptive name might be valueComplexReference, but we're too lazy to type names that long.
- complexValue – this is the payload (a title and an "object" of whatever datatype is used for the complex data)
The API itself doesn't control what goes into either of these properties; rather, it defers to the complex obs handler to set these values. The handler takes a payload (obs.complexValue), stores it however it pleases, and then sets a reference (obs.valueComplex) that it can use to find the payload again when needed. When a complex obs is being fetched, the API hands the obs to the complex obs handler and the reference (obs.valueComplex) is used to find and populate the payload (obs.complexValue).
Here are some sequence diagrams to demonstrate the steps involved in getting and setting complex values:
So, the API defers to the complex obs handler for both storing and retrieving the complex values. When an observation with a complex value needs to be rendered, the API asks the complex obs handler and provides a "view" that defines the representation needed (a URL, short text, html, raw, etc.). These views are meant to be a relatively small enumeration of typical ways in which data are viewed or transmitted.
This design allows for almost any type of complex value to be added to the system, since all the actual management of the new type is done within the handler, which can be plugged into the system via modules. An image handler might store the images in the database or the file system. A video handler might use an external server to store the videos.
Example of Creating a new Complex Obs Handler
Let's say we want to introduce a new complex observation to support a "Whirligig" datatype. In other words, we want to be able to store "Whirligigs" in the patient's medical record as observations.
Create a Complex Obs Handler
First, we create a complex obs handler for our "Whirligig" datatype. As a complex obs handler, our
WhirligigHandler will need to implement the ComplexObsHandler interface. As part of that interface, the handler is responsible for:
|Given an observation containing a Whirligig, store the Whirligig and place a reference to that Whirligig into the observation's valueComplex.|
|Retrieve Whirligigs||Given an observation containing a reference to a Whirligig in its valueComplex and a requested view, load the actual Whirligig into the observation's ComplexData using a representation corresponding to the requested view.|
|Purge Whirligigs||Given an observation containing a reference to a Whirligig in its valueComplex, ensure the corresponding Whirligig payload is purged (permanently deleted).|
|Support different views||Report which views are supported for Whirligigs. All handlers should support the RAW view (i.e., return the Whirligig in it's native form); however, most handlers will be able to return alternative representations useful for different contexts. For example, our handler may be able to represent a Whirligig in HTML, as JSON data, and in a plain text format as well.|
Register the Handler
We create a module to add the capability for OpenMRS to store Whirligigs as observations. The module contains a reference to our
WhirligigHandler in its
ModuleApplicationContext.xml configuration, which is used by OpenMRS as the module is started.
Define Concept(s) with Whirligig Datatype
We create a concept for "Whirligig", setting it's datatype to complex and specifying our
WhirligigHandler as the handler for that concept.
Now that you have a handler to manage Whirligigs and at least one concept in the system that is "answered" with a Whirligig, you can start using them. In practice, there are three key perspectives to consider: the client, the API, and the handler.
|Client||For the client, they can just send & receive Whirligig observations to the API. When creating a new observation, the client attaches a Whirligig to the |
|API||The API serves as a proxy to the handler. It has no idea what a Whirligig is, how to store them, or how to retrieve them. When the client submits an new Whirligig observation to be saved, the API notes it is complex and passes the observation to our Whirligig handler before saving it. When retrieving a Whirligig observation, the API passes it to the handler before returning it to the client, allowing the handler to attach the Whirligig. The API does support a |
The handler doesn't need to interact directly with the client; in fact, as mentioned above, the client needn't know the handler exists. The handler just speaks to the API and performs all operations related to Whirligigs.
When the client submits a Whirligig observation to the API, that observation is passed to the handler. At that point, the handler has two primary jobs: (1) store the Whirligig data and (2) place a reference to the Whirligig in the obs' valueComplex field. The handler will get this valueComplex reference back when the API needs to retrieve, update, or purge the Whirligig.
When the client asks for an observation that contains a Whirligig, the API turns to the handler to fetch the actual Whirligig. The API passes the observation containing the reference (in valueComplex) and the handler is responsible for attaching the Whirligig to the observation. The handler may fetch the Whirligig from its own table, from a separate database, from a web service call, or (for small values) from within the valueComplex reference itself.