-
Notifications
You must be signed in to change notification settings - Fork 49
Contributing
All contributions to the CQF Ruler will be licensed under Apache 2.0
All new development takes place on <feature>
branches off master
. Once feature development on the branch is complete, the feature branch is submitted to master
as a PR. The PR is reviewed by maintainers and regression testing by the CI build occurs.
Changes to the master
branch must be done through an approved PR. Delete branches after merging to keep the repository clean.
See Release Process for related information.
If you're using VS Code, the cqf-ruler repo suggests a list of Java extensions for static and style analysis. Please enable these recommended extensions, as they help detect issues with contributions prior to committing them or opening a PR.
The CQF Project has adopted an over-arching goal to contribute back to HAPI. To this end the CQF Ruler project has adopted the HAPI Coding Conventions: https://github.com/hapifhir/hapi-fhir/wiki/Contributing
The CQF Ruler repository contains an .editorconfig
file and a Checkstyle Maven plugin to enforce some of these conventions. This will cause a build failure in the event of a Checkstyle error.
Results of Checkstyle errors can be found in the corresponding checkstyle-result.xml
file.
Code should generally follow Java best-practices as outlined in Effective Java 3rd Edition.
If using VS Code, the Sonarlint plugin will be suggested to help detect issues early on.
The cqf-ruler-test
project provides scaffolding for unit and integration tests. Contributed code should generally have good test coverage, though a formal standard has not yet been established. See the example plugin for a demonstration of basic integration tests.
The extended test integration class RestIntegrationTest
has database reset method with annotation @AfterAll
. Similarly RestUnitTest
has database reset method with annotation @AfterEach
. This it could be possible that tests residing in same suite will share resources. And in circumstances when it is necessary to execute individual tests with the testing lifecycle set to PER_METHOD
.
Configuration settings for integration tests should be implemented in the properties
element of the SpringBootTest
annotation.
Example:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { HelloWorldProviderIT.class,
HelloWorldConfig.class }, properties = {
"hapi.fhir.fhir_version=r4",
"hello.world.message=Howdy"
})
The CQF Project has strict checking for Javadoc enabled. This will cause a build failure in the event of a Javadoc warning. Visit https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html for more info.
Results of Javadoc can be found in the output of the build.
Following conventions such as these make it easier for the next developer to find code that's already been implemented as opposed to reinventing the wheel. The utility conventions outlined below are one type, while the plugin conventions are another.
See the plugins directory for examples of current plugins.
In general, there is one plugin per implementation guide. All supported versions of FHIR are included in the same plugin. The plugin artifact should be named cqf-ruler-<implementation-guide-abbreviation>
, while the directory in which it's contained should be simply cqf-ruler-<implementation-guide-abbreviation>
For example, the Clinical Reasoning module is implemented in the plugins/cr
folder, which builds an artifact called cqf-ruler-cr
. In the event there's a conflict, use the name of the implementation guide in lower case and hyphenated. For example, cqf-ruler-case-reporting
.
To make contribution back to the HAPI FHIR Server easier, configuration keys for plugins should generally use a prefix of hapi.fhir.<ig-abbreviation>
prefix, For example, hapi.fhir.sdc.enabled=true
In general, reusable utilities are separated along two different dimensions, Classes and Behaviors.
Class specific utilities are functions that are associated with specific class or interface, and add functionality to that class.
Behavior specific utilities allow the reuse of behavior across many different classes.
Utility or Helper methods that are associated with a single class should go into a class that has the pluralized name of the associated class. For example, utilities for Client
should go into the Clients
class. This ensures that the utility class is focused on one class and allows for more readable code:
Clients.forUrl("test.com")
as opposed to:
ClientUtilities.createClient("test.com")
or, if you put unrelated code into the class, you might end up with something like:
Clients.parseRegex()
If the code doesn't read clearly after you've added an utility, consider that it may not be in the right place.
In general, all the functions for this type of utility should be static
. No internal state should be maintained (static final
, or immutable, state is ok). If you final that your utility class contains mutable state, consider an alternate design.
Examples
- Factory functions
- Adding behavior to a class you can't extend
If there is behavior you'd like to share across many classes, model that as an interface and use a name that follows the pattern "ThingDoer"
. For example, all the classes that access a database might be DatabaseReader
. Use default
interface implementations to write logic that can be shared many places. The interfaces themselves shouldn't have mutable state (again static final
is ok). If it's necessary for the for shared logic to have access to state, model that as an method without a default implementation. For example:
interface DatabaseReader {
Database getDb();
default Entity read(Id id) {
return getDb().connect().find(id);
}
}
In the above example any class that has access to a Database
can inherit the read
behavior.
Examples
- Cross-cutting concerns