Background
We need a way for concepts to be classified into different types of orderables (e.g., some will be drugs, some will be tests, etc.). We realize that it's unlikely that one classification scheme will satisfy everyone's needs. In addition, we'd like a design that supports the notion of someone being able to come along and add new types of orders through a module. The most straightforward approach would be to use concept classes (similar to how we have already been doing) to classify concepts (via their Concept Class) into the appropriate order type – i.e., drug for drug orders, test for test orders, etc. The downside of this approach is that it requires that we align all implementations one a single classification scheme and does not allow for further granularity of classes (e.g., splitting tests into lab tests, radiology tests, procedures, etc.). So, we're looking for a design with the following requirements.
...
- Allow concepts to be mapped to specific types of orders (and thereby identify which concepts are orderable).
- Allow for flexibility in the mapping, so different implementations can choose to use different levels of granularity or different numbers of classifications of their concepts – e.g., one implementation is fine with "drugs" and "tests" while another implementation wants to specify orderables as "drugs", "serology lab tests", "microbiology lab tests", "pathology tests", "radiology tests", etc.
- Provide a mechanism for modules to introduce new types of orders.
- Provide an unambiguous pathway to get from an orderable concept to a specific type of order (e.g., DrugOrder vs. TestOrder vs. SomeModuleOrder).
- Allow for a simple hierarchy – i.e., the knowledge that both lab tests & radiology tests are tests – without having to hardcode this knowledge.
Design Ideas
A relatively simple first pass could be done with:
Code Block | ||
---|---|---|
| ||
public class OrderType {
public final static OrderType DRUG = ...;
public final static OrderType TEST = ...;
}
public class Order {
public OrderType getOrderType() { ... }
public boolean isOrderType(OrderType) { ... }
} |
We played with a number of design ideas, including elaborate Java class hierarchies, tagging of concepts, and even the notion of "concept class types," but finally settled on the following approach, since it seemed to be the simplest approach that could meet all of our requirements.
Basically, we create a mapping of concept classes to order types within the order service. Within the application, this would appear as a screen amongst the order administration screens like "Concept Class Mappings." Using this table, you map any existing concept classes that represent orderables to the appropriate order type. The types of orders are supplied by the system – e.g., starting with DRUG and TEST, but allowing a module to register a FOOBAR order type if it wanted. So, we have table like this:
Gliffy | ||
---|---|---|
|
- order_type_id – autonumber primary key
- name – name of order type
- description – description of order type
- java_class – a reference to a Java class of type
? extends Order
- parent_id – link to parent order type (null if no parent)
Gliffy | ||||
---|---|---|---|---|
|
...
...
- order_type_class_map_id – autonumber primary keyconcept
- order_classtype_id – a reference to a order type
- concept_classorder_typeid – a reference to a Java class of type
? extends Order
concept class (should be unique within table, since a concept class may be mapped to only one order type)
In the OrderService, there would be methods to register new types of orders along with methods to inspect the mappings:
Code Block | ||
---|---|---|
| ||
class OrderType { String name; String description; ConceptClass[] conceptClasses; Class<? extends Order> clazz; OrderTypeHandlerOrderType handlerparent; } class OrderService { /* Returns all known types, independent of hierarchy */ public OrderType[] getOrderTypes(); public void registerOrderType(OrderType newTypegetAllOrderTypes(); /* * Returns immediate children of given order type (e.g., Given TEST, you * might get back LAB TEST and RADIOLOGY TEST; given LAB TEST, you might * get back SEROLOGY, MICROBIOLOGY, and CHEMISTRY). */ public OrderType[] getOrderSubtypes(OrderType orderType); public OrderTypeConceptClass[] getOrderTypegetConceptClassesByOrderType(ConceptOrderType conceptorderType); public OrderType getOrderTypegetOrderTypeByConceptClass(OrderableConceptClass orderableconceptClass); public ConceptClass[]void getConceptClassesForOrderType(OrderType orderTyperegisterOrderType(OrderType newType); /* Returns null if concept is not orderable */ public OrderType getOrderType(Concept concept); } |
Under a "Concept Class MappingsManage Order Types" section of Orders Administration in the application, we would provide a way for administrators to edit and define new mappings of concept classes to order types. For example, they would see a list of known order types by name (e.g., "Drug", "Test", "Referral", etc.) – each one corresponding to a class of type ? extends Order
within the API or provided by a module. Alongside each type of order, the administrator could enter 0-to-n concept classes that should map to that order type. A concept class would not be required to be an order type (most classes, like Question or Misc, would not be orders) and any given concept class could only be associated with a single order type (e.g., you would not be allowed to associate the concept class "Drug" with multiple order types).
...
- It defines the list of orderable concepts (only those concepts whose concept class is mapped to an order type are orderable).
- It allows implementations to define concept classes in any granularity they choose, since many concept classes can be mapped to the same order type.
- It allow for new types of orders to be introduced by modules (they are simply added to the list of order type and ready for concept classes to be associated with them).
- It provides an unambiguous path from any concept (via its class) to the associated order type and vice versa (from order type to appropriate concept class(es)).
- There is a simple hierarchy (e.g., you can find all the classes of tests by asking for concept classes that are mapped to the test order type).
Notes
Steps for OpenMRS Platform 1.10
- Create OrderType domain object
- Implement
Order.isType(OrderType)
andOrder.
...
getOrderType()
methods- The
Order.isType(OrderType)
method should respect hierarchy by returning(this.orderType == orderType || parent.isType(orderType))
- The
- Deliver OrderType.DRUG and OrderType.TEST out of the box
OrderService.getOrders(...)
andOrderService.getActiveOrders(...)
should useOrderType
as a parameter, not a java class