FHIR server options


As part of the OpenHIE hack-connect-a-thon, an attempt was made to prototype using HEARTH as an FHIR server in the OpenLMIS v3 microservice. However, this work does not fully address the need to evaluating what other options are available and which one is the most suiting framework to use in OpenLMIS. This page is an attempt to document lessons learned so far and document topics/questions that may need to be answered with a full spike. 

There are two primary use cases that require or will benefit from an FHIR server implementation in the OpenLMIS architecture. 



Use case 1: OpenLMIS serving mCSD Locations (Care Service Update Provider)

In this use case, the assumption is that the OpenLMIS implementation is a Care Service Update Provider. OpenLMIS owns an authoritative list of some or all of the Locations and provides those to the federated master facility list management infrastructure. Parts of the facilities that OpenLMIS owns includes supply chain locations like stores, dispensary units.  To support such an implementation with as standard compliant service as possible, OpenLMIS can expose read-only FHIR Location resources. In the OpenHIE Architecture, we anticipate that a mediator will be able to access the FHIR Location resources that are exposed from OpenLMIS and populate the Interlinked registry. 

OpenLMIS v3.4 exposes a partial implementation of Location resource specification from the mCSD profile.  To conform with the mCSD profile, OpenLMIS needs to support the full set of operations related to resources like Location and Organization as outlined in ITI-91. To support these set of interactions, one less expensive option is to use a FHIR server running as a microservice in OpenLMIS. 

 
At the OpenHIE hack-connect-a-thon, a prototype close to this diagram was developed. However, there are a number of research topics that need to be addressed for the spike to be complete. On a higher level the most significant topic is evaluating the different available tools and deciding which one is the most appropriate dependency to be adopted in OpenLMIS as ticketed here:  OLMIS-4188 - Getting issue details... STATUS  

Using each tool, there is a need to evaluate the following. 

Using jembi/hearth

  • Run jembi/hearth as a microservice
  • Consul registration
  • Populate hearth with seed data (Partially done using nodejs script but this cannot be a permanent solution)
  • Use HAPI FHIR client to update FHIR Locations when Locations are created or updated in the openlmis reference data service. 
  • Authentication (does it support OAuth? what needs to be done?) HEARTH supports jwt. Not OAuth. However, there are two paths, 1. Use Hearth as a backend data store (not facing public) as Josh suggested, 2. Develop the OAuth support.  
  • How to run mongo in production OpenLMIS? use volumes? what would introducing this storage dependancy mean for OpenLMIS? Some thoughts about this are documented in the comments section of OLMIS-5260.
  • Check if hearth support/conforms to enough of the mCSD specification 

HEARTH supports a reasonable set of functionalities around Location and Organization. while it does not fully implement all of the APIs specification, a reasonable set of queries like, lastUpdated=gt2018-08-20, lastUpdated=lt2018-08-20, partof=, near=, id=, idnetifier= etc that would have taken a significant amount of time to implement from scratch are implemented. However, there are some missing functionalities. For example, https://www.hl7.org/fhir/http.html#history _history is partially implemented. The current implementation only supports returning the history of a specific instance only when the version is known. [Base]/Location/:id/_history/:vid is supported. It does not support [Base]/Location/:id/_history and [Base]/Location/_history. While Bundles are returned for most queries, bundles are not usable for transactions. https://github.com/jembi/hearth/issues/149. While in my assessment, a significant number of functionalities are implemented, that may not mean that all functionalities are fully developed. 

Using HAPI FHIR 

  • Run HAPI FHIR as a microservice
  • Consul registration
  • Use HAPI FHIR library from within reference data service as a data store.
  • How to populate HAPI FHIR with seed data? 
  • Update FHIR Locations when are created or updated in the openlmis reference data service. 
  • Authentication (does this support OAuth? what needs to be done?)
  • Check if HAPI API conforms to enough of the mCSD specification

Use case 2: OpenLMIS as a consumer of mCSD Locations (Care Service Update Consumer)

Assumes that there is an implementation of an Authoritative Master Facility List that supplies mCSD conforming Location resource. In this case, OpenLMIS needs to be able to consume those Locations and Updates as mCSD update consumer. The Locations from the other owners would need to be created in the OpenLMIS as appropriate entities like geographic zones and facilities. However, In OpenLMIS, no update should be allowed to be made on these Locations that are created and owned by others. 

Topics for this use case include all questions for use case 1 plus: 

Using jembi/hearth

  • How does the update get triggered? Polling? or will we customize hearth to trigger processes in OpenLMIS?
  • What happens with conflicts? 

Using HAPI API

  • How does the update get triggered? 
  • What happens with conflicts? 
  • Explore if and how the implementation option in the diagram below can be used for this use case: it is from http://hapifhir.io/

QuestionPropositionLoE
How does the update get triggered?We could create a server Interceptor which will send a request to the reference data service. To define what resource should be created in the OpenLMIS, we could use the physicalType field from a location resource.Medium / Large
What happens with conflicts? (determining FHIR resources that are "owned" by OpenLMIS vs. FHIR resources that are not, so OpenLMIS cannot update those resources)We could use an extra data property in facility/geographic zone like fhirResource and when FHIR server will send a request the flag will be set to true. To avoid problems, the flag should be editable only for service-level tokens. In other words, if a user will try to set this flag, an error message will be shown.Small / Medium
How to determine OpenLMIS resources that can be modified by OpenLMIS (because they were created in Reference Data and synced to FHIR), vs. those that cannot (because they were created in FHIR and synced to Reference Data)?With the flag that was described in the previous question, it should be easy to verify which resources are from FHIR server and which from OpenLMIS.Small
How to "ignore" mCSD FHIR resources that OpenLMIS reference data does not need to syncWith the server interceptor, it should be easy to verify which requests should be also sent to the OpenLMIS.Small

Other research topics 

  • How to more appropriately represent facilities and OpenLMIS entities?
    • Identities (should openlmis identity be used as is in FHIR server? or should the identifiers be used to reference the identifier?
    • should tags field be used in any way?  like for other attributes that we care about? facility type? or other metadata?
    • How would we want to use Organization resource from FHIR spec? Does it make sense to use it to represent some of the supply chain relationships like supervisory nodes? facilities? facility owners? public/private etc? 
    • in the current OpenLMIS Location resource, there are some references like programs in identifiers. is an OpenLMIS program an identifier for all facilities that support that program

A general comparison of Available tools


HAPI FHIRAssesmentJEMBI HEARTHAssesment
Open Source License

Apache 2

+

BSD 3-Clause License


Open Source Community Engagement

Watch: 131
Stars: 611
Forks: 533
Contributors: 68

+

Watch: 12
Stars: 6
Forks: 9
Contributors: 15


Commit Activity



Most recent release number 3.4.0
A total of 24 releases in GitHub
+

1.0.0-alpha
a total of 2 pre-releases only in GitHub


Github account / Organization nameJames Agnew (looks like a personal account)
Jembi (Organization)+
Technology Fit
Programming Language (FHIR Server)Java (spring framework)+ (same as OpenLMIS)Javascript (NodeJS)
The database used (FHIR server)the example configuration is MySQL. It uses JPA, it is possible to configure it to use PostgreSQL.+
(same as OpenLMIS)
MongoDB
FHIR Client / ParserAvailable for Java+Not Available-
AuthenticationOAuth samples here+No OAuth samples or documentation
mCSD ProfileNo mention of mCSD
Efforts to support mCSD seen with intrahealth's pull request
Mascot
OpenHIE

Same community supports this+
Practice/Support in Africa?

Jembi, Intrahealth, Path+
Performance (details)
Performance

Creation and update actions are much slower than in the Hearth (about 20x times)

For search action, it is about 4x slower than Hearth 


very fast for creation, search and update actions+

HAPI FHIR RESTful client

As part of  OLMIS-5262 - Getting issue details... STATUS  I created a proof of concept of syncing FHIR-related reference data changes to Hearth FHIR server. The synchronization process is executed when a new resource is created or when an existing one is updated. The proof of concept has been verified with DSTU3 version of Hearth FHIR server by using the generic client option but it contains implementations for other server versions (DSTU2, DSTU2_HL7ORG, DSTU2_1, R4). The following table presents service-to-service communication. For both client options, we need to create separate classes for each FHIR server version because classes that we used (like Location, Bundle, etc.) do not have same superclass or the base class does not contain required methods.

Note: I was unable to create a resource by using Annotation-Driven Client because my client was sending a request with application/fhir+xml as the Content-Type header but Hearth FHIR server accepts only application/xml. I tried to handle that situation and somehow change the Content-Type header but without any success.

StepFluent/Generic clientAnnotation-Driven Client
FHIR context creation
FhirVersionEnum version = getVersion(); // convert string property to enum
FhirContext context = version.newContext();
Client creation
// logging interceptor will log all requests and responses (good for checking
// why a request was not successfully handled by FHIR server
LoggingInterceptor loggingInterceptor = new LoggingInterceptor(true);
loggingInterceptor.setLogger(logger);

IGenericClient client = context.newRestfulGenericClient(fhirServerUrl);
client.registerInterceptor(loggingInterceptor);
// we need to define an interface for each FHIR server version
interface LocationClient extends IRestfulClient {

  @Create
  MethodOutcome createResource(@ResourceParam Location resource);

  @Update
  MethodOutcome updateResource(@ResourceParam Location resource);

  @Search
  List<Location> findByIdentifier(
      @RequiredParam(name = Location.SP_IDENTIFIER) TokenParam tokenParam);

}

// logging interceptor will log all requests and responses (good for checking
// why a request was not successfully handled by FHIR server
LoggingInterceptor loggingInterceptor = new LoggingInterceptor(true);
loggingInterceptor.setLogger(log());

// as the first parameter we provide our interface
LocationClient client = context.newRestfulClient(LocationClient.class, fhirServerUrl);
client.registerInterceptor(loggingInterceptor);
Find existing resource
// create general criterion for all versions of FHIR server
ICriterion criterion = new TokenClientParam("identifier")
    .exactly()
    .systemAndValues(serviceUrl, olmisLocation.getId().toString());

// T - type of FHIR resource
//     (must extends org.hl7.fhir.instance.model.api.IBaseResource interface)
// B - type of FHIR bundle
//     (must extends org.hl7.fhir.instance.model.api.IBaseBundle interface)
// resourceClass - class definition of T
// bundleClass - class definition of B
B bundle = client
    .search()
    .forResource(resourceClass)
    .where(criterion)
    .returnBundle(bundleClass)
    .execute();
// each version have to handle getting resource from the bundle
// because base interface does not contain a related method
T existing = getEntry(bundle);
// we use method defined in our interface
List<Location> locations = client.findByIdentifier(new TokenParam(system, value));
return CollectionUtils.isEmpty(locations) ? null : locations.get(0);
Create a new resource 
// make sure that the ID field will be empty for new resources
fhirLocation.setId((IIdType) null);
// prepare and execute create command
client
    .create()
    .resource(fhirLocation)
    .prettyPrint()
    .encodedJson()
    .execute();
fhirLocation.setId((IIdType) null);
// we use method defined in our interface
client.createResource(fhirLocation);
Update an existing resource
IIdType idType = existing.getIdElement();

// there were some problem with having version part in the id element
// some versions of FHIR server allow to have a null for this part
// but not all so that is why we need this flag
if (removeVersion) {
  idType = idType.withVersion(null);
}

// make sure that ID field will be the same for existing and new version of resource
fhirLocation.setId(idType);
// prepare and execute update command
client
    .update()
    .resource(fhirLocation)
    .encodedJson()
    .execute();
IIdType idType = existing.getIdElement();

// there were some problem with having version part in the id element
// some versions of FHIR server allow to have a null for this part
// but not all so that is why we need this flag
if (removeVersion) {
  idType = idType.withVersion(null);
}

// make sure that ID field will be the same for existing and new version of resource
fhirLocation.setId(idType);

// we use method defined in our interface
client.updateResource(fhirLocation);

OpenLMIS: the global initiative for powerful LMIS software