Monday, October 22, 2012

OpenMRS style tip: how you should expose module Global Properties

I've seen OpenMRS modules expose configurable functionality in about ten different ways. The most common is to expose a global property (e.g. "concept.weight") whose value you set to the primary key of an object in the database (e.g. "5089"), and then have the code littered with things like this:
This is ugly for a few reasons:

  • a helper method would save a lot of typing, and allow consistent reporting of error messages if the GP is configured wrong.
  • proper unit-testing of any code that calls this requires you to use static mocks (thanks Mario and Alex for harping on this!)

Today Mark and I sat down (virtually) for a pairing session and wrote out our proposal for the "right way" to do this going forwards. Here's how it works in the EMR module:

  • An EmrProperties class
    • It has strongly-typed getter methods for the module's settings
    • These getters are not static. The class is annotated with @Component, so we can autowire it into services that need it.
    • This contains settings configurable via GP and hardcoded constants (so developers don't have to think about whether to look in a "properties" file or a "constants" file, and so if an initially-hardcoded setting is made configurable, this don't require structural refactoring)
  • A ModuleProperties helper class, so other modules that depend on the EMR module (which will be most PIH modules going forwards) don't have to reimplement code that fetches an object from the database based on a global property.
    • eventually we'll improve this to support PKs, UUIDs, names, etc.
The bolded point above is the key difference from things we've done before. So to use this class, we autowire an EmrProperties object (for production), providing a setter for testing. This lets us use the class in our code as conveniently as if we were making static calls, while letting us mock the EmrProperties object and inject it into our service, letting us write proper unit tests.

Thank you Keelhaul!

I wanted to highlight a cool little thing that we got from OpenMRS's open-source community.

Back in late 2008, when dinosaurs still walked the earth, Firefox was on version 3, and we had just elected a guy named Barack Obama president, we had an email thread around Location Hierarchies (at the time, there was no OpenMRS mechanism for saying that one location was inside another one) and we were debating whether we also needed location tags. (I said we did, though for the wrong reasons.)

So, along came a developer named Andrey Koshushkov (who goes by the nickname Keelhaul, and was at University of Göttingen, and I think worked with Ghislain Kouematchoua). Andrey implemented the location hierarchy and tags, which made it into OpenMRS 1.5.

Almost four years later we are finally getting to use the location tag feature at PIH for the first time. And we're very thankful that it's been sitting there, implemented well, just waiting for us. So, if you're reading this, thanks Andrey!

PS- Since I was just giving Burke props in my last post for having been very right about Visits, and how flexible they need to be, I wanted to balance that by pointing out something he was wrong about (I had to look back 4 years!); but when I read the whole conversation, I see that he wasn't wrong. :-)

Visits vs Visits

It's been a long time since I've posted anything. My apologies, I blame travel: two weeks in Porto Alegre doing a kickoff with the Thoughtworks developers, and a week at the OpenMRS Implementers meeting in Manila were pretty time consuming. But I promise to get back on track!

I've just spent a couple of days working on our API around Visits and ADT, and I was surprised to find that we're not going to be using visits on the Mirebalais project the way I'd assumed they would work when we built them in OpenMRS.

Scenario: a very sick patient shows up at the outpatient clinic, checks in, sees a doctor, who gets him admitted to the inpatient ward, where he stays for two days.

While we were building visits in OpenMRS, I was assuming that we'd represent this with two visits:

  1. visit type = "Outpatient", location = "Outpatient Clinic", start = 9am, stop = 10am
  2. visit type = "Inpatient", location = "Inpatient Ward A", start = 10am, stop = 2 days later
However the clinical and M&E teams really want us to represent this as just a single visit. Which makes sense if you're not wearing a developer hat. :-)

So instead we'll represent that scenario as:

  • One visit, with location = "Mirebalais Hospital", start = 9am, stop = 2 days later
  • An encounter of type = "Check In" at "Outpatient Clinic" at 9am
  • An encounter of type = "Outpatient consultation" at "Outpatient Clinic" at 9:30am
  • An encounter of type = "Hospital Admission" at "Inpatient Ward A" at 10am
  • An encounter of type = "Hospital Discharge" at "Inpatient Ward A" two days later
We've started to capture the business logic (up through check-in at time of writing) in an ADT Service (interface, implementation, unit tests) in the EMR module.

I actually think I like this better than the approach I'd originally assumed we'd take. And I'm really happy that our implementation of Visits in OpenMRS is flexible enough to let us model our business logic however we want. (Props to Burke for constantly reminding us of that!)