Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • HTML templates rendered by route files will not contain "wrapper" elements — these will be provided by the layout service
  • Allows developers to set default layouts, that can be globally overwritten by developers.

Extendable Routes

Routes are simpler to extend than the current strategy that we are using in the OpenLMIS-UI, which is decorating functions. Most of our modifications focus on changing workflows based on the data that is loaded into a view. Since the routes are initially kept as pure oject representations, they are easy to load, modify, and replace.

Ideally, extensions will replace how data is loaded for specific views by sub-classing and modifying the original services that were responsible for loading that data.

Layered Architecture Overview

In the OpenLMIS-UI, we try to keep each module focused on a single application function. Yet, in OpenLMIS v3.2.1, a module would be responsible for providing HTML for a view and sending/receiving HTTP requests — which is a bad practice in that it makes debugging difficult, and extendability much more fragile.

...

  • Infrastructure layer that interacts with local databases and external services
  • Domain layer that organizes business logic around domain objects
  • Application layer that combines infrastructure and domain layers with UI specific workflow requirements
  • Router layer where routes and simple HTML are configured to display domain object
  • Components layer where simple HTML is transformed into richer interactive experiences

This is a very traditional layered architecture, and by respecting application layers our sections of code should be able to more cleanly have a single responsiblity — which will become easier to maintain and debug.

As a general rule, all methods in the infastructure or domain layers must be asynchronus and expose promises. This is important because it will force developers to express logic in ways that don't block the main rendering thread on the browser.

Domain Objects

Currently the OpenLMIS-UI implements logic that is structured around resourceServices AngularJS services that closely map to OpenLMIS Services. This has lead to repeated code with complicated methods. Javascript objects recieved received from an OpenLMIS Service are directly passed to route and component level modules, which makes reasoning about the current behavior of a screen difficult. There are many cases where implementation logic is difficult to follow because of how objects are mutated and passed between services.

Articulating a domain layer in an application will help avoid these pitfalls by encouraging a clear object oriented style — rather than methods where javascript objects that represent the domain are mutated and passed to other objects. Ideally, domain objects will establish declaritive methods that is expressed in HTML — rather than convoluted methods that can implement business logiccan be meaningfully used in HTML to express logic.

An example of an ideal domain object used in HTML would be:

<form ng-submit="physicalInventory.submit()">
<physical-inventory-summary physical-inventory="physicalInventory" />
    <label for="submittedDate">Submitted Date</label>
    <input id="submittedDate" type="date" name="submittedDate" ng-model="physicalInventory.submittedDate" />
    <input type="submit" value="Submit Physical Inventory" />
</form>

What is worth noting in the example above is:

  • physicalInventory.submit() is directly expressed by the object's method
    • The object is taking an action on its self, rather than passing an object to the HTTP request
    • The method name is declaritive, as it expresses a series of actions but doesn't do those actions
  • There is a physical inventory component, which completely convers up details about how a physical inventory is summarized
  • The physical inventory's submittedDate property is being directly manipulated by the form

Repositories

Repositories act like a collect of objects and are responsible for configuring domain objects. In domain driven design, repositories can feel like an odd concept — as the interface for a repository is expressed in the domain layer, while the repository implementation is expressed in the infastructure layer.

To keep configuration simple between a domain object and it's repository — domain objects wilil always take a Javascript Object as their first argument, which will represent the object's properties, while a second argument will be the repository directly injecting its self.

An example of this would look like:

import PhysicalInventory from openlmis-stockmanagement-ui.domain.physicalInventory

class PhysicalInventoryRepository {
constructor(repositoryImplementation) {
this.implementation = repositoryImplementation;
}

findAll() {
return this.implementation.findAll()
.then(function(results) {
var domainResults = [];
domainResults.forEach(function(result) {
domainResults.append(new PhysicalInventory(result, this));
});
return domainResults;
});
}

submit(physicalInventory) {
return this.implementation.submit(physicalInventory)
.then(function(result) {
return new PhysicalInventory(result, this);
});
}
}

What's important to note is:

  • Repository handles calling its implementation, then taking successful results and making those results into domain objects
  • Each domain object is given a pointer to the repository
    • Ideally, the repository argument would be optional — but then that specific domain object wouldn't be able to have its state changed
  • All implementation details about how or where the repository stores the domain object is hidden in the implementation layer, and the domain's repository has no direct knowledge of those implementations

Application Services

Application services combine domain repositories with repository implementations, which are exposed through methods that implement flow control.

An example would be adding a confirmation modal before a physical inventory is submitted.

Transition Plan

To move from the implementation of the OpenLMIS-UI in OpenLMIS v3.2.1 to the v7 architecture, we will want to complete the following tasks:

  • Separate current UI files from a flat file structure to a structure that separates the applicaiton architecture layers.