[GSoC][LibreHealth] Final report

Project: Implement Spring Data to LibreHealth Toolkit

Repository: gitlab.com/yashdsaraf/reactive-lh-toolkit

This project’s purpose is to replace or rather migrate the existing persistence schema of LibreHealth toolkit which uses hibernate mappings to a more modular spring data persistence mechanism.


As a part of this project I built an entirely reactive Spring Data app which uses Cassandra as its NoSql database to provide support for CRUD operations using its REST API for HAPI FHIR DSTU3 structures.

The app supports CRUD operations for Patient , Observation and Encounter and partial search operation for Patient.

Custom models:

As I was required to work with the HAPI FHIR structures directly without cloning them or modifying them locally, I needed to figure out a way to make Cassandra directly load the HAPI structures as tables. For this purpose I created custom models which directly extended the original HAPI structures and annotated various parts of those classes to implement certain functions such as defining the primary key, ignoring certain fields etc.

One of the key advantages here is that since the new custom models are direct subclasses of the original HAPI structures, they can be easily, implicitly casted to the original structures wherever needed without losing any data.

These models can be found here: https://gitlab.com/yashdsaraf/reactive-lh-toolkit/tree/master/src/main/java/org/openmrs/model

Converters:

To properly save the data Cassandra needed to understand the complex data types of HAPI structures. One way to do this would’ve been declaring these complex data types as User Data Types (UDT) in Cassandra’s schema, but this required annotating the original structures which couldn’t be done without modifying them.

Another way was to implement various converters which act as a layer between the models and the persistence layers and works to convert the complex data types to primitive data types which can be easily understood by Cassandra when writing the data and can just as easily be converted back to the complex data types at the time of reading the data.

I wrote a simple annotation which can be used to mark any class as a converter provided it implements Converter , ConverterFactory or extends GenericConverter . There converters are automatically added to Spring Cassandra’s mapping context.

These converters can be found here: https://gitlab.com/yashdsaraf/reactive-lh-toolkit/tree/master/src/main/java/org/openmrs/converter

Indexing:

Cassandra does not support LIKE operations by default on non indexed columns. This posed a problem while implementing pattern matching search based on partial keywords. This was solved by using SASI indexing in Cassandra 3. I wrote an annotation which did the appropriate configuration for indexing the target property.

Auto Cassandra Configuration via Spring Boot:

I used Spring Boot’s auto configuration instead of manually extending AbstractCassandraConfiguration. This proved to be useful in auto creating keyspaces on each run, adding a query logger to log all the CQL queries being executed by Cassandra and using the application.properties file to specify key parameters such as keyspace name, contact points, schema action, port, etc.

Workaround for app freezing on READ:

One of the more prominent issues I faced in the past couple months was that the app froze on retrieving certain properties. There was no output, no errors and the CQL queries were getting executed just fine.

After a lot of trial and error I decided to make a sample non-reactive app with just the culprit properties enabled. It did not execute successfully but I did get errors in the output instead of app freezing on execution. After debugging deeper I realized the app couldn’t find the properties and the subscribers kept on waiting for the data even after a property not found error was thrown.

A strange workaround for this was to re-declare the affected properties with new getters and setters calling the original getters and setters; and the original property marked @Transient . I wrote a custom annotation for this which marked the new property as a Cassandra table’s column and ensured that the final JSON output would not consist of the extra workaround property we’re declaring.

Mixins:

When returning the data to the browser or while storing it in the database, the serialization process for converting the data to JSON is handled by Jackson. To make sure the output to the end user looked exactly as hl7.org/fhir, I used Jackson mixins to add various Jackson annotations to the original HAPI structures without actually having to modify them. This enabled me to ignore and rename properties as well as decide the sorting order in the final output.

These mixins can be found here: https://gitlab.com/yashdsaraf/reactive-lh-toolkit/tree/master/src/main/java/org/openmrs/mixin

TODO:

  • Add CRUD operations for more HAPI resources.
  • Model existing resources exactly as per hl7.org/fhir.
  • Refine the currently primitive search mechanism and implement it to all the resources.

Additional information about my progress over the course of GSoC 2018 can be found in my weekly blog posts.

Repository: gitlab.com/yashdsaraf/reactive-lh-toolkit