OpenLMIS v3 OOAD Requisition Redesign
Note: this is mostly for me to remember the OO design best practices we'll need to do for the Requisition object for 3.1 (3.x?)
Currently there is considerable tech debt in terms of how the Requisition domain object (and related parts) has been designed and coded. We'll need to redesign and refactor it to make it more OO. This will be explained in a way here with an example.
When initiating a new requisition, various business logic needs to be done in order to populate the requisition properly. One is to populate each requisition line item with quantity received as a result from a previous requisition, either through a proof of delivery (POD), or through stock management. These received quantities will be coming from an external microservice (fulfillment or stock management). Currently (v3.0), this is done by adding a ProofOfDeliveryDto to the list of parameters in Requisition.initiate().
- DTOs are data transfer objects, so they pretty much deal with data transfer and should not be in the domain layer. They should be in the application service layer.
- Adding this DTO as a parameter to the initiate() method means the signature changes and all code that calls this method must change as well. This does not seem extensible since each time new logic needs to be added to populate the requisition, the method signature must change again.
- It does not make sense conceptually for the process of intiating a requisition to know anything about a POD or POD DTO. It seems like tight coupling and an unnecessary dependency.
How to Refactor:
- If we use a builder pattern (RequisitionBuilder), that should be the interface to build up a Requisition object. Requisition methods should be package private so that requisition objects are primarily constructed through the builder.
- DDD (domain-driven design) has something called Value Objects, which are similar to DTOs, but have a bit more behavior in them; a Value Object can be used as a DTO
- The RequisitionBuilder can have Value Objects passed to it in order to do the received quantity logic. The ProofOfDeliveryDto could be converted into a Value Object like ReceivedData or StockData
- The LineItemFieldsCalculator is close to a domain layer service; its methods look more like functions. To make it a domain layer service, we might add properties like currentRequisition and previousRequisitions and the methods could work on those objects to do the calculations and set the values in the currentRequisition
Application Services
- Handles external communication
Domain Layer Services
- This is different from a domain object because it handles logic where it is necessary to know about multiple domain objects.
- This can also know about the domain repository (RequisitionRepository), but only the interface, not the details (it shouldn't have to deal with things like entity managers)
- No external communication
Domain Objects
- This deals with logic that only deals within the object (ex: only within Requisition)
- This can also know about the domain repository (RequisitionRepository), but only the interface
Random thoughts:
- Since we are using the Importer/Exporter pattern in Requisition, the Requisition object should be handling all import/export, not the RequisitionBuilder. The Requisition defines the interfaces. You pass a DTO to the Requisition.export() method and it handles filling in the DTO with setters. You pass a filled-in DTO to a constructor/static factory method and the Requisition constructs an object using the DTO data.
OpenLMIS: the global initiative for powerful LMIS software