Page tree
Skip to end of metadata
Go to start of metadata

Background

OpenMRS now has a few different mechanism for adding custom datatypes to the system.  This wasn't planned; rather, it happened organically.  Our hope is to bring these mechanisms closer together in design with the hope to eventually combine them into a single approach that meets all needs.  First, we'll provide a brief history of how we've gotten to the point we are now.  Custom datatypes have been introduced in several parts of OpenMRS, including person attributes, complex observations, global properties, and visit attributes.

Person Attributes

When the person table was initially introduced into OpenMRS, all implementations were constrained to use the same attributes.  Then, a bright young Ugandan (Daniel Kayiwa) suggested that we introduce the same flexibility of an entity-attribute-value (EAV) approach into the person table as we had done with our observations in the obs table.  This approach allows implementations to virtually extend the person table to meet local needs.  For example, in Tanzania, they wanted to track "ten cells", which are a type of addressing used in Tanzania.  To accomplish this flexibility, we added a person_attribute table to contain implementation-specific data about persons and a person_attribute_type table to define the new "attributes" (virtual columns) for the person table.  Using this mechanism, an implementation in Tanzania could define a new person attribute for "ten cell" and use it to store data for each person in their system without having to alter the core data model.  Person attributes have been widely used & appreciated, since they allow OpenMRS to meet local needs through local extensions to the data model while allowing many implementations to continue sharing the core platform.

Complex Observations

In an unrelated effort, we had designed a method for handling complex observations.  One of the primary features of OpenMRS is to gather clinical observations about patients (pulse, weight, test results, answers to questions, etc.).  Sometimes these data can be more complex than a simple number or string.  For example, storing an chest x-ray image, an electrocardiogram of the patient's heart rhythm, or a lab results for a blood culture that is delivered as a rich text document.  Complex observations were designed to accommodate these observational data – i.e., data that aren't primitive datatypes.

One of the benefits of migrating complex observations to a more generic "custom datatype" approach is that it allows OpenMRS to accommodate a larger set of datatypes for clinical data, which opens the door for OpenMRS to find common ground with OpenEHR archetypes.

Settings (formerly Global Properties from 1.8 downwards)

Internal settings and module configuration data are persisted within the database as "settings (formerly Global Properties from 1.8 downwards)."  To date, these properties have been stored as strings, leaving it up to the modules and services that store & retrieve the values to convert non-string values into string representations.  We have wanted a way to define various datatypes for settings (formerly Global Properties from 1.8 downwards)– e.g., dates, numeric values, lists, etc. – and standardize the approach of defining the property's datatype, the UI widget used to manipulate the datatype, validation, etc.  Eventually, we'd like not only to have standard datatypes for settings (formerly Global Properties from 1.8 downwards), but also allow modules to add new custom datatypes.

Visit Attributes, Location Attributes, Provider Attributes

For OpenMRS 1.9, we decided to add the same extensibility for the visit table as we had for the person table, since the metadata needed about patient visits can vary greatly from one implementation to another.  Given our positive experience with person attributes, we planned to introduce visit attributes from the very start along with the visit table.  However, having learned a bit from the use of person attributes, Darius designed visit attributes to be a bit more flexible, introducing an abstracted "handler" interface instead of relying solely on regular expression formatting. Due to this generalized design, we were able to add attributes to the location and provider objects with little extra effort.

Requirements for Custom Datatypes

There are several shared requirements for these various custom datatypes:

  • An abstracted handler interface
    • Validation
    • Choices
      • When applicable, the handler must provide a list of possible values.
      • When applicable, the handler should be able to return a list of possible values, given a search (query) string.
    • Return value with basic views (e.g., raw, uri)
  • A UI extension of handler interface
    • Rending of data
    • User interface widget
  • The API should provide a String reference and optional String display property for handler to persist the value
    • For datatypes with large payloads (e.g., an image, video, document, etc.), the handler may provide a "key" to the value within an external data store.
    • For simple datatypes, the handler may embed the value within the reference.
  • The API should provide a String configuration property that the handler can use to manage configurations
  • The handler should be able to expose configuration settings 
    • Initially this can be presented as a textarea to the user when setting the handler and the value will be passed to the handler when setting it up.
    • Eventually, this can evolve into a map of properties or even into handlers being able to provide custom configuration pages.

Design Considerations

  • For complex observations, we adopted a convention of displayable string + delimiter + reference key to ensure that a displayable version would be available even when values are retrieved in bulk and it's not feasible to call the handler to render every value.  If we treat absence of the delimiter as displayable == reference key and include a mechanism to escape the delimiter, then this approach could be used across all custom datatypes.
  • We'd like to maintain our separation of data & web layers, meaning that the handler interface may need to be separated into Handler and WebHandler interfaces (as Darius has done with visit attributes).  In the future, the majority of OpenMRS instances may not be using the web application, so we should strive to maintain as much non-web-dependent functionality within the API layer as possible.
  • To what extent can we use or learn from the earlier fieldGen work.

Rendering Values

Part of the job of the handler is to handle rendering. The intent was that an application (OpenMRS, module, etc.) would call the API asking for the value rendered for a specific "view" (where the view could things like raw, thumbnail, link, html, json, etc.). View names would be somewhat standardized (e.g., some provided by core like raw, thumbnail, link, html but others invented by modules like flash, foobar, ...) and handlers would be able to report which views they supported. So, the API sends the view and value reference to the handler and the handler takes care of rendering the result and returning it. This model allows the API to support views without having to bind itself to web-based views and leaves the rendering to the handler.

For web support of rendering, it would be nice to have a web-aware extension of handlers – e.g., a method for providing supporting JavaScript (i.e., if a handler is being used it could inject some javascript on the page once even if there are 30 of its thumbnails being rendered on the page).

Notes

http://notes.openmrs.org/2011-06-01-Custom-Datatypes

http://notes.openmrs.org/Design-Forum-2011-09-14

5 Comments

  1. Unknown User (mseaton)

    We might want to add in how we can also converge global properties into this common mechanism as well.

  2. Unknown User (surangak)

  3. Unknown User (surangak)

    Responding to Burkes input on "Rendering Values"

    Looking at the existing obs code,

    It occurs to me that in regard to handler ‘views’, we could merely keep adding if conditions, like its already done.

    @Override

                public Obs getObs(Obs obs, String view) {

                             if (WebConstants.HYPERLINK_VIEW.equals(view)){}

     else if (WebConstants.HTML_VIEW.equals(view)){}  else{}
                            return super.getObs(obs, view);

                }

    But I don’t think it’s nice to do so.

    So Burke, trying to work on your idea (I’m hoping that I’m on a parallel, if not similar track to what you are saying)

    Each view type should be associated with a separate handler. (patientJsonHandler, PatientThumbnailHandler etc.)

    Each data type should have a basic class, which will be extended by others.

    For example, PatientHandler is the basic class, and will be extended by others.

    However, instead of loading only the class with the highest priority, we will load all the handlers.

    As the basic handler, Patienthandler will be aware of the other available handlers that extend it (for example, PatientFieldGenHandler)

    When creating a complex concept, we don’t need to specify which handler class type it uses, since all is well as long as we have the basic handler.

    The different handlers (fieldGen, etc.) come into play when we are trying to create a complex obs.

    Assuming that PatientHandler is extended five times, there are five different ways that users can create a complex obs.

    So the question is, do we select the specific handler to be used at the time we are creating a complex obs?

    Cons: if we follow this pattern, then we need to store a reference to the handler class used in the db.

    Ex : instead of

    John D. Patient (100-8)| 3

    We need

    John D. Patient (100-8)| 3| FieldGenHandler

    Also, in reference to your comment on a ‘web-aware extension of handlers ‘  

    Currently, the complexObsValue.tag takes care of how the patients are being rendered

    However, in image handler, there is a web layer which does just that, without the intervention of a tag.

    By your comment, I’m assuming that you prefer to switch to such a system, with fieldGen tags etc. being injected using web layer handler, and not a tag?

    However, as another con, I wonder how this will fit with patient and visit attributes. I fear that we may be doing some ‘diverging’ instead of ‘converging’. :-)

  4. Unknown User (r.friedman)

    This sort of parallels a discussion I've been having with Darius on the new paradigm for AttributeTypes.

    I think we should use the existing AttributeType fields, format (i.e. java class) and foreign key (for use with concept, program, etc.).  Then let the handler have any name and config it wants/needs.  This will enable us to identify unambiguously the data type which is being contained and will allow us to introspect.  For example, there are going to be multiple order subtypes (e.g. org.opermrs.DrugOrder), if we have a general order handler that can deal with any of them, it can determine that DrugOrder is a subclass of Order and therefore it can handle it.

  5. Unknown User (darius)

    Synoposis of the 14-Sep-2011 design forum discussion:

    I will spec out / mock up what it would look like to take the current implementation and change it so that:

    1. attribute types can optionally define a specific handler class they want to use (otherwise the framework will choose the best handler for the chosen datatype)
    2. we get rid of the Spring @Order-based prioritization, and use class hierarchy instead
    3. we add a render(Object value, String view) method to the AttributeHandler interface
    4. we look into the work that Jelena did during GSoC on improved attribute types, and see what we can incorporate from there (or does it mainly include further implementations of handlers? in that case we'd just refactor those to the new model and incorporate them)
    5. look at Suranga's work on Complex Obs, and see if this new handler interface can function for both complex obs and attributes.

    Next week we'll continue discussing this.