Introduction
In the RefApp, we currently have OpenAPI documentation (formerly called Swagger) of REST API endpoints that are both under-utilized and under-maintained. The documentation, which are both human and machine readable, can be valuable if they are completely and accurate. Besides giving developers an easy way to understand the API endpoints, it can be used to automatically generate REST API clients for the frontend, greatly reducing the boilerplate code that we need to make to do the same thing.
The page documents the existing architecture of the REST module, the existing documentation around it, and the challenges we face to improve it.
High Level Overview of REST Request Handling
When a GET request is sent to a REST endpoint (ex: /ws/rest/v1/patient/<uuid>?v=<rep>
), the following happens:
MainResorceController handles the request, determines which resource the request is for (PatientResource), and calls that resource's
retrieve()
function.DelegatingCrudResource (which is the superclass of most *Resource.java files) provides the retrieve function, which calls
asRepresentation()
to that reads the <rep> parameterasRepresentation()
attempts to convert the <rep> to a list of Patient properties (fields) to be retrieved using a combination of different strategies:PatientResource.getRepresentationDescription()
is first called. This function (usually) lists out the properties that should be retrieved for the "standard" representations commons to most resources (default
,full
andref
).if the above function returns
null
, thefindAnnotatedMethodForRepresentation
function is called to find the method in the *Resource.java class that is annotated with@RepHandler
to handle the specified<rep>
.If the above fails, it assumes that the <rep> is a customer representation (ex:
custom:(uuid)
), and callsgetCustomRepresentationDescription
to retrieve the list of properties.
The
BaseDelegatingConverter.convertDelegateToRepresentation()
function gets called to loop through each property name and converts it to actual values by callingDelegatingResourceDescription.Property.evaluate()
The evaluate() function tries a combination strategy of reflection and annotated methods to retrieve the value of the property. This process can be recursive as the property can itself be another Resource.
The evaluated object gets returned by
MainResourceController.retrieve()
to be serialized into the appropriate data format (xml or json)
Challenges and Ideas for Improvement
current documentations are in 2 places: we have OpenAPI docs are generated from the backend, and we also have rest.openmrs.org. Can we merge them?
rest.openmrs.org can have narrative explanations that are useful in addition to normal javadocs. However, its shell / java / javascript code examples are probably better represented / maintained in OpenAPI.
It would be really nice if javadocs get included into the OpenAPI docs. It should be possible, see this.
We only have OpenAPI documentation for REST Resources, but we have other web APIs (like controllers) that need to be documented.
Example: Appointments have a REST-like API, but is actually implemented as a Controller, so it doesn't show in the OpenAPI docs.
EMRAPI contains web APIs that are just not REST-like at all. Those need to be documented as well.
Most REST resources have "default", "full", and "ref" representation, but it is possible to have more via "NamedRepresentaton". Examples:
ConceptResource1_8.java:
@RepHandler(value = NamedRepresentation.class, name = "fullchildren")
AdmissionLocationResource.java:
if ((rep instanceof NamedRepresentation) && rep.getRepresentation().equals("layout")
There does not seem to be an easy way to get what named representation is supported by a Resource
We need to inspect the logic of the
getRepresentationDescription
function, and also any function annotated with@RepHandler
REST resources can also have "custom" representations. It would be nice to document what properties can be included, but that's difficult. It is possible to have properties that are not in "full" representation. Example:
The
Patient
class extends thePerson
class, andPatientResource
allows for getting fields from thePerson
object as if it's from thePatient
object. These properties are not documented in the Patient full representation, but they can be specified in custom rep. For example, theattributes
anddead
fields actually belong in thePerson
class, but you can also retrieve them from the Patient resource:There can be "alias" properties. For example, the
Person
resource haspersonName
which is an alias topreferredName
:
We use
getRepresentationDescription()
to define the fields that get returned for a given resource's representation, but we usegetGetModel()
to document what fields are in a representation. This can lead to inconsistencies. Can we combine them?Similarly, can we use
getCreatableProperties()
to replacegetCREATEModel()
, andgetUpdatableProperties()
to replacegetUPDATEModel()
?PR Where
getGETModel()
is introduced: https://github.com/openmrs/openmrs-module-webservices.rest/pull/288/filesomod-common/src/main/java/org/openmrs/module/webservices/rest/web/resource/impl/DelegatingResourceHandler.java
With our current implementation, there is no way to type search parameters. For example, ObsResource1_8.java has a
doSearch()
function that takes in aRequestContext
, and from that we extract the paramspatient
,encounter
andq
. Is it possible to re-architect the search function to take in a better typed object?There are also resources that have different search implementations depending on which params are passed in (See ObsResource1_8.java, VisitResorce1_9.java). For those, it might make sense to have different search functions (with different input types) instead of trying to cramp the logic into one function.
OpenAPI 2.0 does not support
oneOf()
types, but upgrading to 3.0 should help. Where that can be useful:when we do
/patient?v=default
, we should returnPatientGetDefault
, when we do /patient?v=full, we should returnPatientGetFull
v=
parameter can be oneOf(enum('ref', 'default', 'full'), string)