Difference between revisions of "Website:Application overview"

From WormBaseWiki
Jump to navigationJump to search
Line 334: Line 334:
 
=== Types of data to store in the cache ===
 
=== Types of data to store in the cache ===
  
In the REST widget() example, we cache all data drawn from the database necessary for the widget. We do not cache generated HTML (template processing is very efficient).  Note also that it is not possible to cache data structures with globs.
+
In the REST widget() example, we cache all data drawn from the database necessary for the widget. We do not cache generated HTML (template processing is very efficient).  Should we deem it necessary, we can exploring caching of generated HTML using Template::Plugin::Cache.
 +
 
 +
Note that it is not possible to cache data structures containing globs.
  
 
=== Cache Duration/Expiry ===
 
=== Cache Duration/Expiry ===

Revision as of 00:41, 9 August 2010

Directory Structure

-rw-rw-r--    Changes
-rw-rw-r--    Makefile.PL
-rw-rw-r--    README
drwxrwxr-x    build
  Staging directory for building third party libraries.  Current releases should be symlinked to the correct unpacked source.

drwxrwxr-x    conf
  Application and third-party library configuration.

drwxrwxr-x    design
  Design elements and ideas.

drwxrwxr-x    extlib
  Directory containing builds of third party libraries.

drwxrwxr-x    lib
  The core application modules.

drwxrwxr-x    logs
  
drwxrwxr-x    private
  Docs and presentations.

drwxrwxr-x   root
  /root contains static files and templates.

drwxrwxr-x   script
  Helper scripts and the stand-alone server.

drwxrwxr-x   sql
  SQL statements for various databases.

drwxrwxr-x   src
  Third party sources.

drwxrwxr-x   t
  Application test suite.

-rw-rw-rw-  wormbase.yml
  Application-wide default configurations.

-rw-rw-r--   wormbase_local.yml.template
  Template for local configuration.  Entries here will override defaults.

Configuration

Catalyst offers a powerful configuration system. We use it to provide application-wide, per-page, and per-session configuration. In addition, local configuration files can be used to override any configuration option for production or development deployment.

Here, $ROOT refers to the document root of your application.

$ROOT/wormbase.yml

This file contains the default configuration for the application. The application defaults to using remote data sources. If you would like to override this, see wormbase_local.yml.

$ROOT/wormbase.yml.template

Move this template file to wormbase_local.yml and edit values to locally override the default configuration variables. This file is maintainted in SVN as a template so that individual development preferences are not obliterated by an inadvertent svn commit.

Quick Overview

The general structure of the application mirrors the organization in Acedb. Separate Model::* packages correspond to classes in Acedb.


Pages

Pages are composed of widgets, in turn composed of fields.

Widgets

Widgets are small segments of a page comprised of a series of fields. Widgets roughly correspond to sections of the old WormBase site.

The widgets available for any page are specified in the primary configuration file. They are comprised of:

  • a Controller action named after the widget.
  • a template file (if required).

Fields

Individual sections of each widget are referred to as fields. They are comprised of:

  • a Controller method implementing a chained action. This calls a...
  • a Model method(s) that collect and massage the appropriate data. The primary method should correspond to the name of the field.
  • a template file (if required). The name of the field corresponds to it's section name in the presentation layer.
  • field names are: stash keys (dynamic), controller actions (dynamic), template filenames (static), section names in templates.
  • fields are also URL and REST targets.

Example

http://localhost/gene/*/identification

Access a widget called identification that collects together the fields that comprise the widget.

http://localhost/gene/*/ncbi

Access the ncbi field of the identification widget. Note that it is not necessary to know the containing widget.

Drawbacks

Problem: Objects must be fetched for each widget.

Solution: Have a gateway search that redirects to page where widgets are loaded by their ID, or cache the current object the first time it is fetched.

Advantages

  • Each action becomes its own URL target opening up many possibilities for presentation.
  • Can I make these REST services, too?

Implementation Questions

Q: Does it make sense to forward to a chained action?

Q: For customizable data, at which point should display decision be made?

If made prior to fetching data, the code will be faster. If made at the display level, the code is slower but easier caching. (NO!) The caching unit should probably be the actual Perl data structures, not HTMLized data.

Dynamic creation of controller actions

The following actions are created dynamically directly from the configuration.

- Each widget is a controller method called "name_widget".

- This method is an action chained to fetch.

- Each field corresponds to a PathPart of a Chained reaction:

     sub description : Chained('fetch') PathPart('description') Args(0) {


Details

Models

Generic Model methods

Controllers

Generic Controller Actions

ie: fetch, search, references

Dynamically created Controller actions

Actions will be created dynamically for each widget and each field specified in the configuration file.

Currently, these correspond to:

Widget: sub $name : Chained('fetch') PathPart('$name') Args(0) { my ( $self, $c ) = @_;

# Set the name of the widget $c->stash->{widget} = $widget_name;

# page is $c->namespace; my @fields = @{ $c->config->{pages}->{$page}->{widgets}->{$name} }; foreach my $field (@fields) { $c->forward($field); }

if ( $c->is_ajax() ) { $c->stash->{noboiler} = 1; }

# We still need to specify the template. It defaults to the name of the action. $c->stash->{template} = 'gene/widgets/$name.tt2'; }

Where $name is the name of the widget and $page is the current page/class.

               sub $field_name : Chained('fetch') PathPart($field_name) Args(0) {

my ($self,$c) = @_; $c->stash->{$field_name} = $c->model('WormBase::Model::' . $class)->$field_name($c);

           }

Where $field_name is, well, the field name, and $class corresponds to the class/page.

Views

Required Controller actions

Required Model methods

Views

Setting up a new page

Creating new Models and Controllers

1. Create your new Model or Controller using the wormbase_create.pl script. This script provides stub formatting, documentation, and test files.

$ROOT/scripts/wormbase_create.pl model Test

Creates...

[todd@micos trunk:23]$ ./script/wormbase_create.pl model Test
exists "/Users/todd/projects/wormbase/website/trunk/script/../lib/WormBase/Model"
exists "/Users/todd/projects/wormbase/website/trunk/script/../t"
created "/Users/todd/projects/wormbase/website/trunk/script/../lib/WormBase/Model/Test.pm"
created "/Users/todd/projects/wormbase/website/trunk/script/../t/model_Test.t"

2. Add the new files to the SVN repository

svn add lib/WormBase/Model/Test.pm t/model_Test.t
svn commit lib/WormBase/Model/Test.pm t/model_Test.t

Configure the page

Add configuration specifying the name of the page, the order widgets should be loaded, and each widget and the fields it contains.

This configuration is currently stored in WormBase.pm but is subject to change!

__PACKAGE__->config->{pages}->{$class_name} =>
	{ widget_order => [ ],   # order widgets should be loaded
                             # in the sidebar or tabbed interface, say
      widgets      => {         # a hash ref of available widgets
             widget_name => [ ]   # an array ref of available fields 
                      }
    }

Write your WormBase::Model::* methods

Remember: The names of these methods should correspond to the names of your widget fields! If they do not, your template stash will not be populated.

If you want to use the generic widget and field templates, please refer to the section on standardized formatting of data in the template stash. Whatever data your model methods return *must* be in this format to be rendered correctly by the templates.

Create templates (if necessary)

Create templates as necessary. These should be located in:

The widget template: root/templates/$class/widgets/$your_widget_name.tt2

The field templates: root/templates/$class/$your_field_name.tt2

Caching

To ease server load and accelerate page load times, the web application implements a number of caching mechanisms of elements that are computationally intensive to generate.

File cache of computationally intensive methods

Required modules

  • Catalyst::Plugin::Cache
  • CHI
  • CHI::Driver::File

Usage

 # Check the cache for the presence of your data.
 # my_cache_id can be any string you want (see below for details).

 # $cache_id is the provided string with the current version of WormBase appended
 # $cached_data is data returned from the cache, if any
 my ($cache_id,$cached_data) = $c->check_cache("my_cache_id");

 unless ($cached_data) {
     my $data = some_computationally_expensive_method();
     $c->set_cache($cache_id,$data);
  }

Cache ID

When using the check_cache/set_cache approach, the initial cache ID that you provide will have the current version of WormBase appended to it. This lets us automatically expire entries when a new version of the database is released. Note that this ONLY works if you use both check_cache() and set_cache(). You should always follow this strategy as checking the cache is extremely efficient.

Here's an example from the REST controller for widgets:

sub widget_GET {
    my ($self,$c,$class,$name,$widget) = @_; 
			     
    # Does the data for this widget already exist in the cache?
    my ($cache_id,$cached_data) = $c->check_cache($class,$widget,$name);

    # The cache ONLY includes the field data for the widget, nothing else.
    # This is because most backend caches cannot store globs.
    if ($cached_data) {
	$c->stash->{fields} = $cached_data;
    } else {
  
	# Load the stash with the field contents for this widget.
	my @fields;

	# Widgets accessible by name
	if (ref $c->config->{pages}->{$class}->{widgets}->{$widget}->{fields} ne "ARRAY") {
	    @fields = ($c->config->{pages}->{$class}->{widgets}->{$widget}->{fields});
	} else {
	    @fields = @{ $c->config->{pages}->{$class}->{widgets}->{$widget}->{fields} };
	}
       		
	foreach my $field (@fields) {
	    my $data = {};
	    $data = $object->$field if defined $object->$field;
	    
	    # Conditionally load up the stash (for now) for HTML requests.
	    # Alternatively, we could return JSON and have the client format it.
	    $c->stash->{fields}->{$field} = $data; 
	}
		
	# Cache the field data for this widget.
	$c->set_cache($cache_id,$c->stash->{fields});
    }
    
    $self->status_ok($c, entity => {
	class   => $class,
	name    => $name,
	uri     => "$uri"
		     }
	);
}

Types of data to store in the cache

In the REST widget() example, we cache all data drawn from the database necessary for the widget. We do not cache generated HTML (template processing is very efficient). Should we deem it necessary, we can exploring caching of generated HTML using Template::Plugin::Cache.

Note that it is not possible to cache data structures containing globs.

Cache Duration/Expiry

Caching is enabled for *all* installations. This enables us to test the caching mechanism in development.

Cached items in development installations are set to 4 seconds to ensure that code changes are reflected in page reloads. Cached items in production installations are set to expire after 4 weeks. These values can be modified in lib/WormBase/Web.pm.

Cache Location

The cache is written to /tmp/wormbase/file_cache_chi. This is specified during in Web.pm during application setup.

NFS?

Component and page-level caching via reverse proxy

Production websites site behind a caching reverse proxy. This proxy caches many (but not all) dynamically generated content as well. Due to performance considerations, the front end proxy cache expires sooner than that of the back end application cache. See documentation on squid3 elsewhere on this wiki.