Versions Compared

Key

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

...

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, or adding additional HTML templates to a view. Since the routes are initially kept accessible as pure oject representationsjavascript objects, 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 datasimple for an implementer to modify or replace.

This method of route focused extension will be easier to reason about, than following our current convention of using AngularJS decorators. AngularJS decorators rely of Angular's dependency injection system, which makes the implementation details of objects harder to reason about.

React Support

Support the React presentation framework isn't a primary focus of the v7 architecture change, but as we move the application logic and route away from the AngularJS framework we will be able to support specific routes and components to be rendered in React. Using React instead of AngularJS components is a preference of many developers, and it also would allow sections of the OpenLMIS-UI to be rendered into native Android applications. Libraries exist for supporting both React and AngularJS in the same application, one such application is ui-router react hybrid which is an extension of the same core library behind Angular-UI Router.

Layered Architecture Overview

...

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 responsibility — which will become easier to maintain and debug.

As There are 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 browserfew general conventions we will follow to keep application layers performant and decoupled. These conventions primarily apply to domain objects, infastructure repositories, and application services.

  • All methods are asynchronus. By using javascript promises as generic return values we prevent any actions from blocking the HTML DOM from rendering, and stop the OpenLMIS-UI from becoming completely unresponsive.
  • All methods should take Javascript Objects as their primary argument. This will allow for APIs to change their internal contracts, without needing to change the arguments that are passed to them.
  • Most methods exposed by a domain object shouldn't take any arguments. This will force domain objects to act more declaritively.

Below are descriptions of how domain objects, repositories, and application services will interact. Details, conventions, and examples for other sections will be developed in more formal documentation.

Domain Objects

Currently the OpenLMIS-UI implements logic that is structured around AngularJS services that closely map to OpenLMIS Services. This has lead to repeated code with complicated methods. Javascript objects 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.

...

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

Code Block
languagexml
<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 's 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 are intended to model a collection of domain objects, and are responsible for configuring connecting domain objects to the infastructure layer that communicates with OpenLMIS Services. 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.

...

An example of this would look like:

Code Block
languagejs
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);

...


        });

...


    }

...


}

export PhysicalIncentoryRepository;

What's important to note is:

...

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.Types of flow control could include:

  • Changing a repository implementation based on application configuration
  • Adding confirmation modals before a domain object's method is actually called by wrapping the domain object method
  • Redirecting a user to a different page after a successful domain object method call

An example of this would be:

Code Block
languagejs
import PhysicalInventoryRepository from openlmis-stockmanagement-ui.domain.physicalInventory
import PhysicalInventoryRepositoryImpl from openlmis-stockmanagement-ui.infastructure.physicalInventory

class PhysicalInventoryService {
	constructor() {
		let impl = new PhysicalInventoryRepositoryImpl();
		this.repository = new PhysicalInventoryRepository(impl);
	}
    findAll() {
		return this.repository.findAll();
    }
}

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:

...

provide a new way for working with the OpenLMIS-UI, without breaking any of the current work.

This will be done by:

  • Adding webpack with Typescript support to the build process
  • Create a pre-processor that will create ES6 modules from the current AngularJS focused modules that are currently implemented
  • Create a pre-processor that will define a webpack application entry point
    • This will not run if an index.js file is added to the /src file — which will be the generic location for an entry point
  • Move existing AngularJS modules into directories that reflect the domain they operate in