Best practices for modularity and configuration of OpenMRS 3 modules and Docker images across implementations

This post follows up on the latest discussions around O3 implementations on how to best organise modularity and configuration of OpenMRS 3 modules and Docker images across implementations - especially in the context of the growing momentum of the O3 squads and collaborative features.

For example, the MSF team currently configures versions of the OpenMRS 3 frontend and backend Docker images using Github Actions, also pushing versions of these customised images to Docker Hub with tags per environment like DEV, QA, and PROD. At execution level, Docker Compose pulls and runs these images, and configurations like metadata are loaded using Docker volumes.

While that approach works well to quickly configure images and update backend and frontend versions of the EMR, it might not be the most scalable one - especially since each environment requires a tagged Docker image.

Our question for the community is then how to collectively identify and align on the best DevOps practices for making OpenMRS modules and Docker images modular and configurable at scale.

Here is a tentative diagram trying to illustrates the shift where the reference application is more of a β€œproduct” than a β€œreference”, serving as a configurable foundation for implementations:

|603.7962294109435x169

Here are a few points/assumptions to discuss based on that potential model:

  • OpenMRS community modules and Docker images are used whenever possible, allowing to share maintenance efforts between implementers, rather than repeated for each of them.

  • Configuration files are prioritised over forks or rebuilds, allowing to make modules and product images more reusable.

  • Configurations that are specific to an implementation (at least in the context of MSF ones) live in separate repositories. Example: OpenMRS3-Iraq-Configuration.

  • Released Docker images from OpenMRS community are reusable or extendable, allowing to reuse images without rebuilding them from scratch.

  • OpenMRS frontend Docker image already implemented part of this reusability functionality in 3.0.0-beta.16. For example in frontend/DockerFile:

FROM openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.16

RUN rm -rf /usr/share/nginx/html/*

COPY ./frontend /usr/share/nginx/html/

  • Backend configurations could be packaged using Maven while building the backend Docker image for an implementation.

  • Below is a simple Git illustration of how MSF could maintain images and implementations with the OpenMRS community:

The above discussion is a work in progress, open to all suggestions that can facilitate iterative alignment among us. For MSF, the objective is to use the Iraq implementation (to start with) to measure and showcase the efficiency of the modular approach. We also aim to document those collective best practices to facilitate adoption and contributions to O3, while also acknowledging that different architectures might already be in place.

Looking forward to your insights and suggestions :pray:

@michaelbontyes @eudson @samuel34 @mksrom @mogoodrich @caseynth2 @delphinep @ibacher @grace @dkayiwa @dennis @pirupius @jayasanka @slubwama @ruhanga @mksd

9 Likes

Thank you @jnsereko for bringing up this topic.

In Ozone, we’ve been considering this challenge since the beginning to streamline its adoption.

By default, Ozone utilizes O3 Ref App, yet groups often require customization, as you pointed out.

To address this, we suggest creating an β€œOzone distribution” using Maven, enabling the provision of any custom configuration.

(Let’s only talk about OpenMRS configuration and exclude Odoo, ERPNext, SENAITE… and other systems.)

In that case, the distribution directory structure will be as follow:

β”œβ”€β”€ configs
β”‚   └── openmrs
β”‚       β”œβ”€β”€ frontend_assembly
β”‚       β”‚   └── spa-assemble-config.json
β”‚       β”œβ”€β”€ frontend_config
β”‚       β”‚   β”œβ”€β”€ frontend-config-1.json
β”‚       β”‚   β”œβ”€β”€ login-logo.png
β”‚       β”‚   └── primary-logo.svg
β”‚       └── initializer_config
β”‚           β”œβ”€β”€ addresshierarchy
β”‚           β”œβ”€β”€ ampathforms
β”‚           β”œβ”€β”€ appointmentservicedefinitions
β”‚           β”œβ”€β”€ globalproperties
β”‚           β”œβ”€β”€ locationtagmaps
β”‚           β”œβ”€β”€ ocl
β”‚           β”œβ”€β”€ privileges
β”‚           └── ...
β”œβ”€β”€ data
β”‚   └── mysql
β”‚       β”œβ”€β”€ facility_wide_updated_concepts_dump_2024_02_06.sql
β”‚       └── add_ciel_version_gp.sql
└── pom.xml

The above covers:

  • Custom OpenMRS backend configuration: see the initializer_config/ folder.
  • Custom OpenMRS frontend configuration: see the frontend_config/ folder.
  • Custom OpenMRS ESMs: see the frontend_assembly/ folder. This relies on a functionality added recently in O3 where the frontend can be built using more than one assemble file. So in our case, we always use (depend on) the O3 Ref App assemble file as a base and allow to provide a custom file for adding ESMs or excluding some if needed. Then the frontend is rebuilt with this.
  • MySQL base archives/queries: see the data/mysql/ folder for server initialization.
  • Custom OpenMRS modules: set in the pom.xml file as Maven dependency.
  • Docker images: set in the pom.xml file as Maven dependency on a generic Docker Compose project. This Docker Compose project will reuse the O3 Ref App Docker images and expect the above resources (configs, modules…) to be present.
  • All O3 configs from Ref App: set in the pom.xml as a Maven dependency

Upon building the above project, one would end up with something like this (example with KenyaHMIS)

target/kenyahmis-1.0.0-SNAPSHOT/
β”œβ”€β”€ distro
β”‚   β”œβ”€β”€ binaries
β”‚   β”‚   └── openmrs
β”‚   β”‚       β”œβ”€β”€ frontend
β”‚   β”‚       β”‚   β”œβ”€β”€ 023bd53d342b7463.woff
β”‚   β”‚       β”‚   β”œβ”€β”€ 02acc4469a2fe2e4.woff2
β”‚   β”‚       β”‚   β”œβ”€β”€ favicon.ico
β”‚   β”‚       β”‚   β”œβ”€β”€ icon_96x96.682ee334ae19d49894217370b8596f61.png
β”‚   β”‚       β”‚   β”œβ”€β”€ importmap.json
β”‚   β”‚       β”‚   β”œβ”€β”€ index.html
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-esm-care-panel-app-5.1.1-pre.157/
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-esm-login-app-5.0.3/
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-esm-patient-flags-app-5.1.1-pre.157/
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-esm-patient-registration-app-5.2.1/
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-esm-version-app-5.1.1-pre.157/
β”‚   β”‚       β”‚   β”œβ”€β”€ openmrs.177374c1f52c73be.js
β”‚   β”‚       β”‚   β”œβ”€β”€ routes.registry.json
β”‚   β”‚       β”‚   β”œβ”€β”€ spa-assemble-config.json
β”‚   β”‚       β”‚   └── ugandaemr-esm-bed-management-app-1.0.1-pre.60/
β”‚   β”‚       └── modules
β”‚   β”‚           β”œβ”€β”€ addresshierarchy-2.17.0.omod
β”‚   β”‚           β”œβ”€β”€ afyastat-1.2.2.omod
β”‚   β”‚           β”œβ”€β”€ appframework-2.17.0.omod
β”‚   β”‚           β”œβ”€β”€ appointments-2.0.0-20231101.130425-7.omod
β”‚   β”‚           β”œβ”€β”€ appui-1.17.0.omod
β”‚   β”‚           β”œβ”€β”€ attachments-3.1.0.omod
β”‚   β”‚           β”œβ”€β”€ calculation-1.3.0.omod
β”‚   β”‚           β”œβ”€β”€ cohort-3.6.0.omod
β”‚   β”‚           └── ...
β”‚   β”œβ”€β”€ configs
β”‚   β”‚   └── openmrs
β”‚   β”‚       β”œβ”€β”€ frontend_assembly
β”‚   β”‚       β”‚   β”œβ”€β”€ reference-application-spa-assemble-config.json
β”‚   β”‚       β”‚   └── spa-assemble-config.json
β”‚   β”‚       β”œβ”€β”€ frontend_config
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-frontend-config.json
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-login-logo.png
β”‚   β”‚       β”‚   β”œβ”€β”€ kenyaemr-primary-logo.svg
β”‚   β”‚       β”‚   β”œβ”€β”€ ozone-frontend-config.json
β”‚   β”‚       β”‚   β”œβ”€β”€ ozone_logo_color.svg
β”‚   β”‚       β”‚   └── ozone_logo_white.svg
β”‚   β”‚       β”œβ”€β”€ initializer_config
β”‚   β”‚       β”‚   β”œβ”€β”€ addresshierarchy/
β”‚   β”‚       β”‚   β”œβ”€β”€ ampathforms/
β”‚   β”‚       β”‚   β”œβ”€β”€ appointmentservicedefinitions/
β”‚   β”‚       β”‚   β”œβ”€β”€ cohorttypes/
β”‚   β”‚       β”‚   └── ...
β”‚   β”‚       └── properties
β”‚   β”‚           └── ozone.properties
β”‚   └── data
β”‚       └── mysql
β”‚           β”œβ”€β”€ create_db.sh
β”‚           β”œβ”€β”€ openmrs
β”‚           β”‚   └── create_openmrs_db.sh
β”‚           β”œβ”€β”€ zz_01_kenyaemr_facility_wide_updated_concepts_dump_2024_02_06.sql
β”‚           └── zz_06_add_ciel_version_gp.sql
└── run
    └── docker
        └── docker-compose-openmrs.yml

This package is deployment-ready.

Note regarding exclusions from O3 Ref App:

We rely on the Maven Resource plugin to exclude some particular files from O3 Ref App (any file: OpenMRS modules, configuration CSV, JSON…).

Setting up the distribution in the first place

To facilitate the creation of those Ozone distributions, we have put together a Maven Archetype which, in just few commands (Ozone Docs: Create Your Own Distribution), will set the directory structure as needed. Additionally, a lot of the build logic is shared through a Maven Parent project specifically designed for it so that groups don’t need to worry about maintaining this.

I would be happy to document the Maven build life cycle if anyone is interested, but like I said, a lot of the logic is documented in this reusable Maven Parent pom.xml file.


The good thing with the above approach is that all needed binaries and configs end up packaged in one single versioned file which directly feeds into standard deployment pipelines.

Even though the above describes Ozone, it could easily be implemented in OpenMRS directly.

Best,

7 Likes