(Note that while this is currently a part of the OpenHMIS Cashier module it will shortly be moving into the OpenHMIS Commons module to make it easier to reuse. The commons project is hosted on github here: https://github.com/OpenHMIS/openmrs-module-openhmis.commons)
Designing, testing and using application services can be difficult within the OpenMRS ecosystem. Service interfaces can easily get very large while service implementations often repeat very similar code over and over. Testability of services is also suspect because many methods simply call into the DAL where the actual logic is done. We created the Entity Service Architecture to simplify the design and implementation of application services. This architecture provides a basic end-to-end system for getting data from a database, displaying/editing that data in a web page, validating the data, and saving data back to the database. It also provides a basic structure and implementation for service unit tests with minimal configuration and repeated code.
OpenMRS core services abstract Hibernate from the service via DAO classes. This means that you could, theoretically, switch out Hibernate for another ORM and, theoretically, not have to change your service implementation. Our service design differs from this traditional service design found in OpenMRS in one key area: we removed the separation between the service implementation and the hibernate DAO.
The current OpenMRS Service Design
Why did we decide to move away from this more flexible Service > DAO > Hibernate implementation? We believe that this abstraction adds negligible benefit while simultaneously adding complexity to services and reducing the ability to create a cohesive end-to-end service framework. An example of this can be found in the org.openmrs.api.impl.VisitServiceImpl which, as recent addition to OpenMRS, one would assume is using the current best practices. This service has a total of 29 service methods (ignoring the get/set DAO methods), of those only 10 have any logic beyond simply calling an identically named DAO method. Only 3 of those 10 logic methods do any of the type of processing which would normally necessitate a service layer method. The net result is:
This design effectively nullifies the benefit it is supposed to provide; the service layer is more flexible only because it doesn't do very much and the DAO layer becomes complex because it takes on the responsibilities normally handled by a more robust service layer. We believe that there is a disconnect here because OpenMRS services are not the application services for which the current design would make sense, but are instead data services that make the DAO layer redundant.
To facilitate API development we created an OpenMRS service framework that:
The entity service design and relationship to existing OpenMRS service classes
To create a new servce:
As a concrete example, here is all the code that is required to create a service for the cashier item Department model:
That is all the is needed to get a service that provides the following methods: save, purge, getAll, getById, getByUuid, retire, unretire, findByName, All of these methods also support paging and the expected overloads for various parameters, where appropriate.
At the core of this design is a new generic DAO class we've (inventively) called GenericHibernateDAO. This DAO class is responsible for all interaction with the database, through Hibernate, and is driven by Hibernate criteria which are set up by the service. The DAO API is very simple and allows services the full range of data operations they might need to do.
Work-in-progress: Sub-pages for:
Using the entity framework, service tests because much easier to write as all unit tests for the core methods already exist. The entity service class hierarchy has been carried down to the test level as well, so to create tests you simply extend one of: IEntityServiceTest, IDataServiceTest, IMetadataServiceTest. Following the Department example above, here is the test class:
This will provide tests for all of the entity service methods with minimal code and will hopefully make the barrier to writing quality unit tests that much lower.