Tyndale Step High Level Design

From CrossWire Bible Society

Jump to: navigation, search

Contents

Overall structure

The project can be roughly divided into four parts:

step-web

Presentation layer

All the HTML/Javascript code is contained within the step-web project and is abstracted away and self-contained. It communicates with the server via AJAX requests (facilitated by the JQuery library). The AJAX requests follow a notation similar to the REST protocol (more on this in the section below). All requests are defined in src/main/webapp/js/ui-hooks.js

There is very little HTML markup. Most of the UI is generated via Javascript using JQuery. The HTML code is used to provide the basic structure and layout and ease development.

Functionality locations

All HTML code is defined in src/main/webapp. This directory contains the following elements:

The following files are noteworthy:


Web-tier Java layer

The step-web is built as a Front Controller pattern, where all client requests are caught by the FrontController. This then dispatches requests to the relevant method in the relevant controller. The FrontController is also responsible for serialising responses. At time of writing this takes 2 forms:

FrontController

The FrontController object takes in a request URL, calls the relevant controller method and finally returns a serialised UTF-8 encoded response. The FrontController is configured by the web.xml descriptor to listen to all requests under /rest/.

For example,

http://localhost:8080/step-web/rest/timeline/getTimelineConfiguration
  1. Tomcat (or another container) passes the request to FrontController
  2. The FrontController looks at the remainder of the url /timeline/getTimelineConfiguration
  3. Identifies the controller based on the first portion: /timeline => TimelineController (simply suffixing the word Controller)
  4. Using Guice (see section on dependency injection), it retrieves the identified Controller singleton instance
  5. Reflectively calls the method called "getTimelineConfiguration" based on the next section
  6. finally if any parameters are passed in, these are passed to the method in order of appearance.

step-core

The following section outlines the key areas in this project


Data

The data is currently stored in CSV files and the Loader and TimelineModuleLoader are currently used to load the database into an in-memory database. This is currently done manually by calling the URL http://localhost:8080/step-web/rest/setup/installDefaultModules.

Eventually an installation page will be written so that this process happens on installation. The alternative is to have this process happen at build time so that we can distribute a database that has already been built.

Dependency Injection & Guice

Description

Dependency injection or "Inversion of Control" are two design patterns whereby a service can be told what dependencies it will work with.

The following example does not use IoC:

class LookupService {
  private PhonebookImpl p = new PhonebookImpl();

  public String getPhoneNumber() {
    return p.getPhoneNumber();
  }
}

In the above example, it is impossible to switch out the phonebook object for a different phonebook implementation. So, if PhonebookImpl was using a database backend and we wanted to switch to a Lucene backend, this would be hard. It is also hard to test, as to test the getPhoneNumber method, I will have to ensure that the creation of PhonebookImpl is possible (i.e. that it can create all its dependants, has a database, etc.). It is hard to mock out that object.

The example below decouples the implementation and creates an interface. It also allows an external service to inject the Phonebook service (whether that be a mock service or a real implementation

class LookupService {
  private final Phonebook p;

  public LookupService(Phonebook p) {
    this.p = p;
  }

  public String getPhoneNumber() {
    return p.getPhoneNumber();
  }
}

In the above example, the object that instantiates LookupService is responsible for providing (injecting) the correct implementation using the constructor provided (constructor injection). You can also achieve the same thing using setters however the caller will then need to be careful to call all the relevant setters and private fields cannot be set to final.

Guice

The example above has been reworked to show the Guice injection. The only difference below is the constructor which is now annotated with @Inject.

class LookupService {
  private final Phonebook p;

  @Inject
  public LookupService(Phonebook p) {
    this.p = p;
  }

  public String getPhoneNumber() {
    return p.getPhoneNumber();
  }
}

In addition to this, the developer needs to tell the StepCoreModule that Phonebook should use the PhoneBookImpl implementation:

public void configure() {
  bind(Phonebook.class).to(PhonebookImpl.class);
}
Personal tools
Namespaces
Variants
Actions
Navigation
Miscellaneous
Toolbox