Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 23 Next »

Overview

This document outlines the OpenLMIS-UI v7 architecture, which is being introduced to avoid AngularJS paradigms that make extending the OpenLMIS-UI difficult. The OpenLMIS-UI is a URL driven application with a modular architecture that allows implementers to modify workflows and logic to meet their communities needs. During the development of OpenLMIS v3.2.1, a need for a more explicit application architecture is needed to keep application components decoupled so modifications by implementers can be more easily maintained. The extensions developed by OpenLMIS Malawi implementation have shown flaws in the AngularJS-based application architecture.

The v7 architecture aims to improve the following issues that have become problematic in the OpenLMIS-UI as of OpenLMIS v3.2.1:

  • Brittle business logic
    Much of the business logic in the OpenLMIS-UI is implemented without a layered application architecture, which has made documenting and debugging the OpenLMIS-UI harder as the application has grown. The OpenLMIS-UI v7 architecture introduces a layered application architecture to formalize a separation between domain objects, UI route configurations, and a component driven design.
  • Configuration-heavy controllers
    Controllers are used to pass variables defined in the URL state to the HTML view — many of these controllers are hundreds of lines long, but don't improve code clarity or add logic to the HTML view. During the development of OpenLMIS v3.1, we found controllers difficult to extend in maintainable ways. The v7 architecture will avoid controllers entirely.
  • Tightly-coupled views: HTML is complex and often contains business logic
    HTML in the OpenLMIS-UI has become increasingly complex, as many pieces of HTML implement business logic. A best practice is to keep HTML as simple as possible, since HTML is difficult to unit test and much harder for an implementer to extend.
  • Too many singletons
    AngularJS creates lots of singletons, which are single objects persisted in memory while the UI is running in the web browser. Singletons in the AngularJS framework are often the cause of memory leaks and other performance bugs in large AngularJS applications. The OpenLMIS-UI v7 architecture will use plain javascript objects, written in modern Javascript, which will make the logic defined in the UI more reusable in other Javascript applications.  

Architecture

Below is a high level description of the v7 architecture, with a description of how and why the application behaves differently than the AngularJS-based OpenLMIS-UI that was deployed as part of OpenLMIS 3.2.1

Note: Many of the concepts in the v7 architecture are similar to OpenLMIS-UI at v3.2.1, the largest differences are in how the concepts interact. 

Router Moderated Architecture

In a URL-Driven application every screen is directly accessible by a URL, which is moderated by a "router" that loads data into the application state and renders HTML templates. Angular-UI Router provided a great start for an application, but as complexity in the OpenLMIS-UI has grown, configuring UI routes has become verbose and error prone.

To solve these issues, we are going to wrap Angular-UI Router in a way that will force presentation and business logic to be separate. The goal is to create a route registry that implements a facade pattern, such that routes are primarily responsible for loading objects into the application's current state. This way routes can focus on configuration, not presentation.

  • Page transitions only in route files — we don't want HTML or other layers to be responsible for directly showing a modal
  • Auto-inject resolved objects into view state — which will remove the need for verbose controller files, which generally just contain configuration

OpenLMIS Layout Service

OpenLMIS router states will be able to express presentational needs by adding key/value pairs to the route configuration. The route configuration will be interpreted by a completely separate layout system, which can be changed or evolve separately from the business logic. This will allow implementers to create large UI layout changes with minimal code.

  • 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.

Our solution is to adopt a layered architecture, which will consist of:

  • 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 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.

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 can 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.
  • No labels