Building a frontend Maven package

PIH is going to be building the frontend application in a separate process from the distro build. We’re planning to create a Maven package containing all the frontend files, and then manually unpack it into the frontend/ directory on servers.

We were thinking of calling it openmrs-frontend-pihemr / org.openmrs.frontend.pihemr. This would establish a new convention, so we’d like to get feedback from others in the community.

@mksd @ibacher @burke @mseaton @mogoodrich

1 Like

Hi @bistenes,

Thanks for sharing this. It would be helpful to get a bit more background as to why this approach vs what the current 3.x distro Ref App does.

Maybe meat for a TAC call as well btw?

Thanks for the reply @mksd and the reminder to check what has been done in refapp 3.x.

First - what @bistenes is discussing here is not inconsistent with the approach that is there - namely to build a distribution zip artifact that contains the war, omods, and frontend (as well as owas and config as appropriate).

I think it is helpful to look at config as an example, before we look at frontend. With config, there are situations where a distribution may want to keep their configuration files in the same github project as their distribution itself. This is what the refapp does, and is what we do in our rwandaemr-imb-butaro and rwandaemr-imb-rwinkwavu distributions at PIH. But although the refapp includes the configuration in the same codebase, it is ultimately built independently (eg. it is built into openmrs-config-refapp-xyz.zip, and published to maven at org.openmrs.distro:referenceapplication-config:x.y.z). And the distribution build process that pulls this in does so by pulling in the built maven artifact and unpacking it for inclusion in the distribution zip. So ultimately it doesn’t matter whether this config is maintained in the same github project or not. A distribution may choose to do so or not do so based on their needs. The config is a project that builds to a versioned artifact, and the distro includes a particular version of this artifact. And for our PIH-EMR distributions, we have chosen to maintain our configuration in separate github repositories from our distribution.

For the frontend, we are currently doing the same thing that the refapp is doing. Namely, we have an openmrs-distro.properties file that lists out all of the various spa.frontendModules.xyz that we want to include in our distro. However, what @bistenes and I are working on is an idea to evolve away from this to a model similar to what we do with configs. Namely - build the frontend in a separate Maven project that publishes this frontend code (eg. the contents of the spa directory in the distro) to Maven, and then have our distribution build process pull this artifact in and unpack it just like we do with the configuration. Ultimately the distribution build process will arrive at the same result. It’s really just a matter of whether one views building the frontend artifact as a fundamental part of building the distribution, or whether the frontend artifact is something independent that one or more distributions may choose to include at a particular version.

In principle, I think separating these out is a cleaner approach, but there is no reason why we shouldn’t support both and leave it to the distribution authors to decide what works best for them. In practice, I’ve personally found that the inclusion of the frontend build process in the distribution build is unpleasant as it dramatically slows down the distribution build itself. The frontend build with npx takes a decent amount of time to run, whereas building a distribution has historically been (and I’d like it to continue to be) quick - simply copying Maven artifacts into a package. If I can offload the frontend build to a CI server and then just pull in that artifact (and cache it locally with maven like all of the others), I’d like to do so, particularly if it isn’t anything I am actively working on or concerned with.

Where I think I’d like us to move is to a point where our openmrs-distro.properties can refer to configuration artifacts and frontend artifacts in the same way that it currently refers to omod, war, and owa artifacts.

We’d simply have some new properties like:

config.refapp=x.y.z
frontend.refapp=x.y.z

And these would be used:

a) During the distribution build (via the openmrs-sdk plugin) to pull the appropriate artifacts from Maven, and unpack their contents into the distribution package zip

b) During SDK setup/deploy to set a new SDK server up with configurations and frontend that are necessary, as defined in the distro properties, such that doing an openmrs-sdk:setup followed immediately with an openmrs-sdk:run will fully run a distribution instance with no other steps. Currently this is not possible as configuration is not installed by the SDK.

Ultimately, all of this is where I’d like to head. And @bistenes question is really just around - if we were to start doing this at PIH, do we want to do so with any community conventions in mind. For example, we have the following conventions currently:

Github conventions:

Maven conventions:

  • org.openmrs.module:module_id:version
  • org.openmrs.distro:distroname:version

And do we want any conventions around configuration packages and frontend packages that we follow.

At PIH, we have already started establishing our own configurations, as there wasn’t really an existing standard, and also it seemed like there would be an argument for branching beyond the “org.openmrs” domain. So we started using “org.pih.openmrs” as our groupId, and “openmrs-config-xyz” as our artifactIds. For example: openmrs-config-zl/pom.xml at master · PIH/openmrs-config-zl · GitHub

It’s probably most straightforward for us to continue this pattern with a github.com/PIH/openmrs-frontend-pihemr repository that builds a Maven artifact at “org.pih.openmrs:openmrs-frontend-pihemr:version”, which we will put into our own Maven repository here.

But it’s worth discussing if we want to establish any standard naming conventions that either build off of this approach that we have been using, or recommend a slightly different standard.

Happy to take this to a TAC call, but figured I’d lay these thoughts out here first.

2 Likes

I think the “frontend” convention makes some sense. I’d actually like to see us getting away from using org.openmrs.module and org.openmrs.distro everywhere. E.g., it would almost more sense for this to be org.pih.frontend. Maybe we can adopt the naming conventions and add an easier path in the SDK to specify “use this package if the default can’t be found”. So the distro.properties could have something like:

package=org.pih

frontend.pihemr = x.y.z

And then the SDK would (probably) look for org.pih.frontend.pihemr falling back to org.openmrs.frontend.pihemr.

Honestly, I wouldn’t mind moving away from supporting the spa.frontendModules. stuff we have right now. It would allow us to adopt a model where the frontend stuff is largely handled by NPM-centric tooling and then we can just use the Maven repo to store the resulting zip.

We also need a bit of a convention around the format of the zip (e.g., should everything be bundled into a openmrs_frontend/ folder in the zip or is it just flat files unpacked into the distro openmrs_frontend/ directory? (Should we rename the current directory spa to openmrs_frontend? Seems to be the direction everything is headed in…).

Yes, see the end of my above post, where I describe that we are already using an “org.pih.openmrs” groupId for our config artifacts, and it likely makes sense for us to continue this. I don’t see much value in suggesting that everyone tries to coordinate fitting everything into an “org.openmrs.*” namespace.

This actually already exists in the SDK tooling (at least for modules, but I assume we’d apply the same for everything). In the case of modules, the default group id is assumed to be “org.openmrs.module”, but you can override this with specific properties. See how we do this for the “event” module already, for example.

Yeah. Since it already exists as a feature of the SDK, we would need to think about removing it. There still might be value in having SDK features that can facilitate building a frontend package out of a config like it does now though.

That would be my preference.

Yeah, I was thinking about something a bit more generic. The module feature works by specifying something like omod.event.groupId and then that applies to that one module.

I was thinking of a slightly more generic mechanism so that you could have one configuration key to specify a fallback and it would use that as the base for searching for these so you could have that set once and the SDK would use it for, e.g., frontend, config and possibly even OMODs, basically to reduce the pain of having non-OpenMRS packages.

E.g.

package=org.pih
frontend.pihemr=x.y.z
config.pihemr=x.y.z
config.pihemr.haiti=x.y.z
config.openmrs-base=x.y.z

And the SDK would find and combine, e.g., org.pih.frontend.pihemr, org.pih.config.pihemr, org.pih.config.pihemr.haiti, and org.openmrs.config.openmrs-base.

Another nice-to-have for the frontend zip file would be some kind of file that describes the actual versions of things included; this is necessary to be able to support the feature where we can do a sanity check between the versions bundled in a distro and the requirements of packages / configurations.

Things get a bit more complicated than this, and we use the openmrs-packager maven plugin for this at PIH. This essentially allows creating config projects that “inherit” from one or more dependent config projects, and can override or extend them in specific ways.

In any event, I think a thoughtful overhaul of the openmrs-distro.properties file is worthwhile, ideally moving to something like openmrs-distro.yaml as has been discussed here-and-there in the past, and which would allow us to do such things a bit more easily.

Agreed. In theory, this could be the same as the distro.properties file, getting included in the zip, no?

I’m not opposed to overhauling things into a new format, but for backwards compatibility purposes, we’re stuck supporting the distro.properties format, so we’d need a really compelling reason to move; if you have a list of some short-coming we can overcome, that’d be helpful. (The reason we’re stuck with the distro.properties format is because of the way the SDK builds dev servers, which is to read the distro.properties file from the org.openmrs.distro:referenceapplication-package:<version>:jar file and download the OMODs, OWAs, etc. specified there; this goes to the broader discussion of how backwards compatible we need to actually be).

The nice thing about removing the spa.frontendModules feature is that it’s quite new and more or less a beta. Doing it now should be relatively painless. Doing it in 6 months might be impossible.

Yes.

It occurred to me: we might be able to get some mileage out of using jackson-dataformats-text?

How does this affect the process of distributing a vertical solution across implementations/distributions?

Imagine someone builds a new hypertension (HTN) management solution for OpenMRS and PIH wants to install it within their implementations. The solution contains metadata (concepts, forms, reports, etc.), a couple of modules for new backend capabilities, and handful of ESMs. How would this be incorporated into your proposed approach?

I’m assuming the HTN solution could be built in separate repos or a mono repo and the SDK would be used to organize all of its parts into a standard folder hierarchy, zip it up, and it would be shared as a “OpenMRS package” maven artifact.

Would PIH have to enumerate the various components of the HTN solution and individually refer to them and make sure to coordinate versions across separate frontend, backend, and config repositories? Or would you refer to “HTN package v1.2.3” as one of your dependencies and your CI or build processes would pull out the parts needed (e.g., a frontend build process would get the full HTN package, but just use the ESMs from it while your local backend development workflow would use the same HTN package but only need to pull in metadata & modules)?

All good questions @burke . I’m not sure it has to be either / or, but I do think that the quickest and safest initial path to supporting packages involves heavy use of build-time processes. In the absence of that, I think vertical packages can simply serve as versioned, tested wrappers of features that require multiple dependent artifacts.

My gut feeling is that published vertical packages will serve to:

  1. Document the specific components that have been tested and verified to work together
  2. Provide a reference implementation for a green-field, otherwise empty OpenMRS instance

In this way I think they are likely to be very similar to the OpenMRS reference application - useful as a starting point or an example, but rarely if ever implemented as-is except maybe by the implementation who developed it.

For example, I’d consider our PIH EMR distribution as “based on the reference application” even though it excludes some modules and adds or uses different versions of other modules, and doesn’t include the “referencemetadata”. There isn’t the tooling right now that would allow us to define this as “referenceapplication:2.3.4” with specific exclusions and inclusions, but we simply use the refapp as a model and ensure war, modules, owas, etc are updated as new versions are published based on known and tested compatibility. I could see vertical packages being used similarly.

Where I’m trying to get in this thread is to say that all components of a distribution should be able to be defined such that the distribution itself is really just a properties file that defines a tested collection of versioned artifacts that work together. And I should be able to define everything I need in this distro properties file to get a fully functional OpenMRS distribution up and running. That means I need to be able to define not just the war, module, and owa artifacts that it currently supports, but also config (iniz) and frontend (esm) artifacts so that these can be deployed into a server and it can be fully functional.

We may want to take a similar approach where packages are just like distributions, with the main difference that they can’t run on their own, and may need to define a range of compatible versions of artifacts rather than specific fixed versions.

Interested in discussing more.