Module/Plugin Integration

While module integration has been an ongoing topic on our issue threads



I want to continue on the developer forum as we start implementing/integrating using lessons we’ve learned and trying to consolidate to a single point of discussion.

First i’d like to clarify what my opinion of a module and a plugin are.

  • A module should be loosely integrated with core. Meaning, the module would be developed as if it could mostly stand on its own as if a client to openemr. The module can be reliant on core however, core can not be reliant on module.
    I know this brings up difficulties with UI interactions so, openemr will need to provide a mechanism for these interactions. These mechanisms are vague currently but, if one reads the issue threads you can get a sense of where it is going.
  • Plugin/Addon could and most likely will be, as tightly/loosely integrated with core as needed. These are current/new features that to start, would be spun off current openemr core. Thus getting us closer to a modular openemr.
    @mdsupport has been kind enough to give us an example of this in this example PR OeModule by mdsupport · Pull Request #3076 · openemr/openemr · GitHub

An added benefit to doing this will be moving away from managing features from globals and placing that responsibility with module setup on install. Of course, openemr will need to provide the arbitration/dispatching to support but, concepts have been and continue to be discussed.

Currently most emphasis has been using composer for installs. We recently implemented openemr root project version in composer where version conflict arbitration can be setup on module installs. Soon I will be changing our openemr release tagging to the more appropriate semantic versioning. Eventually i’d like to see a complete openemr install from composer. We have the packagist account so, doable.
Module upgrades/uninstall still need to be worked out but most likely will be left to module developer to provide those kind of actions.

So at this point in the project, I see my job as trying to tie some of this together and get integrated into codebase. This way the opportunity for others to use and contribute will be easier.

My goal:

  • write interface classes for fax and sms. Using these we can implement interfaces to halifax or faxsms module. Keeping in mind to allow fax and/or sms for those that might want just one.
  • split up faxsms module to support interface. Currently I arbitrate vendors in module dispatcher but may break that out.
  • pull halifax to plugin
  • implement the module setup logic to zend installer as @mdsupport purposes with some additions
  • possibly creating some traits we can share with modules/plugins.

And lastly, umm remember how to do all of that.:slight_smile:
Hopefully, once done, it can firm up a way forward.
Thanks

1 Like

Currently we have modules in two locations, openemr/modules and interface/modules.
Looking for suggestions on consolidation.

  • leave openemr/modules and use as location for core feature modules
  • move existing sms_email_reminder to interface/modules. remove openemr/modules
  • just pretend it’s not there!!!

In a way that may work best until MU design review comes up with type of support needed from installer and core application!

Before @sjpadgett or anyone interested in this subject spend time, I will try to provide thinking behind OeModule PoC

TL;DR

This is based on assumption that full migration to a formal framework is unlikely to happen in mid-term future. As a short-term solution, following primitive approach will try to recognize the current structure in basic software components without requiring extensive development and related testing. Specific steps involved are:

  1. Create OeModule as a root of every component. This is software model synonym for PHP root namespace OpenEMR.
  2. For software components, create submodules that allow collection of PHP subnamespaces. Some possibilities to explore OeModuleForm, OeModuleStorage, OeModuleService(?) as abstract classes extending OeModule. For business components, possibility would be OeModuleRx, OeModuleBilling, OeModuleScheduling etc.
  3. Design of each would follow diverse routes. E.g. OeModuleForm could further have abstract class that represent several existing forms that include the new, edit and view pattern. Or it could be actual class such as OeModuleFormLayout.
    Till this step, existing code is not impacted.
  4. Implementing the module will start bringing in the structure in a planned manner. E.g. OeModuleScheduling will be extended as OeModulePostnuke which can start at most minimal level by eliminating the ‘Use Calendar’ checkbox in globals and switch to active status of the module. Changes throughout the codebase will involve replacing $GLOBALS['disable_calendar'] by call_user_func($objModules->OeModuleScheduling .'::isActive');. In this case 2 scripts will need to be changed after figuring out how knockout menu enable/disable feature would access modules in addition to $GLOBALS.
    I expect this step can be implemented by developers with limited understanding of the project.
  5. Full implementation would involve identifying all functions required to be supported by that module, moving settings from globals to module and refactoring the code. To continue example of calendar, that could mean
    a) Define scheduling specific methods used in current code
    b) Replace code outside the interface/main/calendar directory to use those methods
    c) Moving all references to openemr_postcalendar_* to interface/main/calendar.
    Prize at the end of that work will be switch to another scheduling system will involve no changes to existing screens.
  6. if OeModule type of approach is adopted, @sjpadgett’s fax module would be expected to start as full implementation. Since it was designed as a module, changes to the code would be minimal. Base class of that module would extend OeModule, implement 3-4 methods with a simple return true; or provide actual code related module actions.
  7. This approach also opens possibility for integrating vendor provided and maintained modules. If @juggernautsei’s Weno contributions were to be OeRxWeno module, it could stay in vendor directory and integrate itself by extending OeModuleRx. Full implentation effort will involve isolating NewCrop related code out to a OeRxNewCrop module and standard code referencing only OeModuleRx methods and properties. That will enable eRx services from other countries to be added with minimal efforts.
1 Like

Very well explained

Next iteration of OeModule approach provides a better validation of original concept of driving modules purely using composer’s optimized classloader library. The manager is able to show all modules (zend and oemodule) as cards with ability to invoke different configuration scripts as well as standardized datastore functions for OeModule configurations.

First ‘module’ installed by default when first invoked by administrator is a Module Manager. It shows skeletal code needed to register and potentially enable an module with default configuration. That provides a way for an module to be placed on top of existing code with possible migration of current settings. Another feature(TBD) will allow module to perform upgrade actions.

Still trying to use common modules & modules_settings tables as datastore for this. As we have no plans to touch zend, exclusive OeModules use should not have any issues. But it would be nice for both features to coexist.

@sjpadgett, Since you have worked on custom modules, are you aware of any parts of zend code that needs change to skip OeModule related rows in modules table? There was warning from ModulesApplication class based on modules.type field. It is temporarily resolved by adding mod_type != ‘OeModule’ clause.

Hi, yep this was next after fixing the various confusion that module manager and loader gets into.
Currently I haven’t fully implement oeModule on my current dev because i’m closely staying with master. So no errors noted. I’ll keep an eye out.
Working with several projects i’m trying to keep up with but my plan is to get upgrades, reinstall(accidental mostly) etc done in zend. However, I keep wanting to pull custom modules into it’s on dashboard but that seems a waste of zend module manager. Maybe it’s because i’ve seen the darn thing too long. Then implement OeModule.

Built in upgrade is mainly sql. That cannot happen with current patch mechanism which is why Project Idea - Database Change Manager is needed. You keep multiple sql files that manager can iterate in version order like git repo merge.

@sjpadgett OeModules will now play nice with ModuleApplication. E.g. with minor changes, the OeModule manager now renames standard menu under Administrator -> Other to Optional Features and inserts a link to start manager. It is also able to point to any random directory as equivalent of ‘src’.

Again, entry in modules table uses mod-directory, directory (seriously!) and mod-type fields differently than zend. If you get to it, let us know if zend is impacted. Since the code is centralized, changes will be trivial.

Hi @mdsupport, Thanks. I hope to get back to this soon. I have some smart guys working fhir and will be able to leave them to it soon.
Appreciate the engineering effort and will be useful.

A few things to consider here. The above examples have a lot of abstraction and all create tightly coupled modules, something we should be avoiding. A module should work remain as loosely coupled as possible. Forcing inheritance to the tightly coupled OeModuleForm/Service/Stoarge starts creating a lot of bloat. Additionally, the more we create modules for inpatient purposes (and just the more we work to standardize a lot of the workflow) modules will have to have a Pub/Sub Event architecture. Too many complex future needs require the ability for modules to easily manipulate each other based on loose rules that may very well be given at runtime which cause a headache the tighter we couple modules.

We already have an event-driven architecture in place (Big thanks to @adunsulag for getting my initial EDA to a production-ready place) that can handle the underlying requirements of a module system. I think before we continue with any development we should seriously consider what a module system that is event driven looks like. I think we’ll find that while in the initial phase there is a bit of a learning curve, in the long run it is a simpler solution with less room for error and far more flexible.

@robert.down not sure where this idea of abstract or tight coupling being bad comes from. Give me specific issues caused by requiring 4/5 things a module should provide - things like enable or disable. Apart from the basic requirements, the module is free to do whatever including events or whatever is flavor of the month.

I would seriously caution against taking on conversion of existing code to event driven zend modules when focus should be on MU3 certification.

1 Like

Are these patterns in the codebase now and if so, could you point them out for me?

Sorry, missed this.
All registry based encounter forms have the new, view and report pattern. It is a developer shortcut to avoid putting a formal and, yes, abstract encounterform class in place. That makes it possible to blindly href new.php to add, view.php to display and report.php to print contents of an form based on registry.directory column. On the other hand, layouts are driven by their own table. Finally there are zend forms that zend manages.
When you retroactively put a module wrapper, as a developer your code becomes simpler to maintain. More importantly, e.g. if eRx and eSign were modules, there will be one time modification to each form to dedicate a single div for modules to control. At run time, module.view.render call will build a array of modules to be included and use call_user_func_array to include output from each modules’s render method.
Ideally each module render should be a tiny font-awesome icon that opens a pop-up and performs it’s function. So without affecting flow, a clinician can have a choice to write a prescription and continue updating the form.