OpenMRS Conventions
Contents |
Overview
This page describes common conventions to be used for the OpenMRS project. Specific topics should be referenced (linked) from this page. Our goal is not to create a long laundry list of rules, but to provide guidance such that if two developers were to create the same functionality, the resulting work would be 80% similar.
"There is no such thing as an undocumented convention" -OpenMRS
Developing Code for OpenMRS
Attribution
- Attribution is to be done within commit comments.
- When a committer is applying a patch, the author(s) of the patch should be attributed within the commit comment according to the the comment conventions.
- Attribution (either author or contributing authors) should not be placed into source code. When encountered, such attribution should be removed by (or with permission from) the original author(s). Contributions that require attribution (i.e., author(s) demanding attribution within the code contributions) will be graciously refused or removed from the repository.
- (NOTE: this attribution-free coding policy took effect after version 1.1, so you may see some attribution in the existing code; we are gradually removing attribution from our code base)
Code Style
- We use the automatic formatting feature of Eclipse to generate consistently formatted code
- Simply use
control-shift-f code> to format a file - OpenMRS formatting file for Eclipse: OpenMRSFormatter.xml
- Install at Window -> Preferences -> Java -> Code Style -> Formatter
- OpenMRS code template file for Eclipse: OpenMRSCodeTemplate.xml
- Install at Window -> Preferences -> Java -> Code Style -> Code Templates
- Simply use
- Use
control-shift-o code> for finding/formatting/organizing imports - Very useful!
Exception Handling Conventions
Create an intuitive hierarchy of exceptions within the package they belong, only creating new Exception classes for when the need to branch on different types of exception within code/webapp are needed (add as needed instead of making extra classes "just in case" for every possible variation).
For example:
org.openmrs.api.APIException org.openmrs.api.PatientIdentifierException extends APIException org.openmrs.api.MissingPatientIdentifierException extends PatientIdentifierException
and later add:
org.openmrs.api.DuplicatePatientIdentifierException extends PatientIdentiferException
when we realize the webapp needs to distinguish duplicates from invalid identifier exceptions.
See Also
- Conventions within Patching Third Party Libraries
Repository Conventions
Branches
Commit Comments
Code Review
Data Model Design
Naming tables
- We've chosen to use all lowercase names with underscores (_) between words (e.g., patient_address)
- Use the singular form (e.g. patient_address instead of patient_addresses) except where the singular form is a reserved word (e.g. users instead of user, since user is a SQL reserved word)
- Where auditing of rows is needed, use the following attributes:
- creator (FK to users)
- date_created (datetime)
- changed_by (FK to users)
- date_changed (datetime)
- voided (boolean)
- voided_by (FK to users)
- date_voided (datetime)
- void_reason (varchar 255)
Special Table Name Suffices
- _map is used for many-to-many relationship tables
- _type is used for a table that categorizes objects in another table — e.g., location_type
- _attribute is used for a table that extends another table dynamically — e.g. person_attribute
- _property is used to define Java-ish key/value properties associated with a given object — e.g. user_property
Naming columns
- Use lowercase with underscores (_) between words
- Auto-generated numeric primary keys are <table name>_id (e.g., patient_id and encounter_id)
Global and User Properties
Property Names
- All names must be globally unique amongst property names
- Dot notation, similar to Java properties, with specificity increasing from left to right
- Camel case or underscores to delineate words
- No whitespace (spaces, tabs, carriage returns)
- All properties for modules should begin with the module id + "."
- All portlet properties should begin with the portlet name + "."
Modules
Naming Your Module
- Module id should be all lowercase without spaces. We prefer no breaks in the word (e.g., htmlformentry); however, you may use hyphens if absolutely necessary.
- The Module id should correspond with the unqualified top-level package for classes in the module. So, org.openmrs.module.foo would have an id of foo.
- Avoid using a module id that conflicts with existing modules or tables that already exist in the data model — e.g., "concept" and "form" would be undesirable module ids, since there are existing tables with these names.
- Implementations of special functions should use dot notation: serialization.xstream, webservices.jaxws. (and this means a package of org.openmrs.module.serialization.xstream)
- Module name should be concise (ideally less than 20-25 letters), may contain spaces and upper case characters, but should avoid punctuation. It does not have to match the module id exactly.
Module Tables
- Module table names should begin with the module id — e.g., all tables added for a the "formentry" module should have table names beginning with "formentry_*".
- Be careful when naming your module to avoid conflicts with other modules or existing tables
Module Project Management
- While module developers are welcome to use their own ticketing system, we encourage module developers to use the Developers#Submit_Bugs OpenMRS ticketing system to track bugs and enhancement requests.
- Request a new Trac component for your module from Ben, Burke, or Paul (e-mail by name @openmrs.org).
- Add milestones for your module to the OpenMRS roadmap using the naming convention: <module name> Module x.x — e.g., FormEntry Module 1.0. Make one milestone for the current version and one for the next version. Optionally, you can make a milestone entitled "<module name> Module Someday" for enhancements not anticipated in the next version.
- Module authors are responsible for keeping tickets and milestones for their module up to date
UI Conventions
- Module admin pages should fit in with other admin pages. Users like a standard look. Fancy js windows are cool but they have their place. Mimic the core jsp pages
Logging
- Using logging — put the following line at the top of your class:
-
private Log log = LogFactory.getLog(MyClassName.class);
- Logging exceptions — use the pattern:
-
log.debug("Concise title for debug message", e); - where
e code> is the (Throwable code>) exception. Do not use the anti-pattern of: -
log.debug(e); // Do NOT do this
- There is no
Log.debug(Throwable) code> method, so the exception is cast to a string and the stack trace is lost.
- Expensive log messages — If you're going to log a message that is computationally intensive to calculate, use the following pattern:
-
- if (log.isDebugEnabled()) {
- // expensive log message creation
- String expensiveStep = webService.invoke("slowMethod");
- log.debug("We should definitely wrap an " + expensiveStep);
- }
- // These also exist: log.isWarnEnabled() and log.isErrorEnabled()
Web Application Development
User Interface
- See openmrs display tag
- Every openmrs business object needs to have a format tag for printing it to the screen.
- These should be used wherever possible on jsp pages: <openmrs:formatPatient object="${patient}"/> is much preferrable to ${patient.name}
- The tag should be called <openmrs:formatXXX ...> and live in src/web/org/openmrs/web/taglib as formatXXXTag.
- Parameters that every tag should support:
- object="${patient}" --> This is the object to be formatted
- id="1234" --> This is the id of the object to be formatted, which will necessitate a call to Context.getYYYService().getXXX(id)
- nolink="false|true" --> If false (the default) then the output might include a link, e.g. formatPatient might link to that patient's dashboard
- size="compact|normal|tooltip|descriptive|bookkeeping"
- To use a tag, you need to provide object or id (object should take precedence if both are non-null).
- If a business object is voided or retired, it should be struck through:
PIH ID 1234-1 Darius Jazayeri
Size examples
- compact -> shortest reasonable representation, e.g. just the name of a patient or form, but the shortname of a concept
- Darius Jazayeri
- normal -> normal representation, and the default when size is not specified
- PIH ID 1234-1 Darius Jazayeri
- descriptive -> The normal representation, plus extra descriptive information, such as a form's description or a patient's age and gender
- PIH ID 1234-1 Darius Jazayeri, 29 year old Male
- tooltip -> Would look like the normal-sized version followed by an 'additional info' icon. That icon's tooltip would contain the extra description you'd otherwise see when size=descriptive
- bookkeeping -> Like tooltip, but also includes all bookkeeping information (creator, date-created, modified-by, date-modified)
- We should create tag files for standard data entry widgets, under <openmrs_tag:xxxWidget>
- Parameters:
- id & name for the form field element
- size: popup | in-place
- Create these for patient/user/person, concept, location, encounter type, etc.
- Parameters:
SpringMVC
- File naming:
- user.list is the name of the page that lists users
- user.form is the name of the page that lets you edit users
- userList.jsp and userForm.jsp are the names of the actual files
- UserFormController.java and UserListController.java are the names of the controllers
- UserValidator.java is the validator
- messages.properties
- Object.property=label (eg Obs.patient=Select a Patient)
- error.property=error description (eg error.gender=Invalid patient gender)
- general.property=general label (eg general.description.label=Description)
- formname.label=label (eg formentry.title=Form Entry)
