Showing posts with label databases. Show all posts
Showing posts with label databases. Show all posts

02 April 2013

Why the PhyloPic Relaunch Took So Long

Or, A Lesson in Development Strategy.

As I announced last week, my website, PhyloPic, has been relaunched with a massive update. One of the key updates is a public API for developers. A lot of people have been looking forward to this, and it was actually almost ready for release last summer. So why didn't I release it?

Failure to Branch

Basal tracheophyte.
Public Domain.
As I was writing up the documentation for the API, I learned of Bootstrap, a CSS/JavaScript framework. I realized that it could solve a lot of the design issues I was having — problems with the site on mobile devices, older browsers, etc.

What I should have done: Created a new development branch for adding Bootstrap while continuing to polish up the API branch. That way, I could have released the API shortly while still being able to work on the design issues in parallel.

What I actually did: Continued working in the same branch, ensuring that I couldn't release the API update until the Bootstrap update was complete.

Having Other Projects

By the end of summer I was mostly done with the revisions, but there was still some cleanup to do. By now some other projects I'm attached to, one with other collaborators, were suffering. So I spent most of my free time in the autumn working on those. (I have a full-time job and a toddler, so that isn't much.)

Homo habilis.
Public Domain.

Becoming Enamored of New Technology

In the autumn, Microsoft release a preview version of TypeScript, and I quickly saw that it was going to be extremely useful. So I rewrote PhyloPic's client-side code — it wasn't too hard and it made further development a lot easier. This caused some delay up-front, but I don't regret it.

Becoming Enamored of the Wrong New Technology

Around this time I also realized that I could finally do away with the last bit of Flash on the website: the Image Submission Tool. HTML5 had become mature enough to do all the image manipulation in the browser itself. I did a lot of research, learning about the Canvas, Typed Arrays, etc. And after a lot of work I actually created an image-processing workflow that work in HTML5-enabled browsers. As a bonus, I got a little standalone project out of it: Pictish.

But there were problems. One is that the best existing JavaScript library for creating PNG files doesn't use Typed Arrays — it uses strings, which means that it is slow for large files. I tried creating my own PNG encoder, or adapting that one, but soon realized it was far too much work. Another problem is that I was no longer supporting older browsers (although this was a trade-off against supporting mobile platforms, so I didn't feel too bad about it).

But there was a much more fundamental danger: doing the image-processing in the client side meant that the API had to trust the client to do it properly. What if some developer used the PhyloPic API to add images to the database but didn't do it right? That could be disastrous.

Octopus bimaculatus.
Public Domain.
I realized I would have to do things the old-fashioned way: on the server. After a bit of research, I identified Image Magick and Inkscape as the best tools. The new methodology was so completely different that I ended up making a lot of database changes, too. Until recently, all files were stored in the database — now they're just stored as flat files. The good news is that this makes load times faster.

Doing Things the "Right" Way

Throughout all this I had been making an effort to "dogfood" my own API, i.e., to use it on the site itself. This has the advantage of making load times faster, since the basic page can be cached and then the data can be loaded in secondarily in a much smaller format. Unfortunately this meant a lot of rewrites for how the pages are rendered.

After a while, the code to generate pages from the data had gotten really complex (mostly involving on-the-fly element generation using jQuery). Around the time I was redoing the Image Submission Page, I realized my whole approach was untenable. I needed a cleaner way to divorce presentation logic from control logic.

I ended up using Knockout for the entire site. It made things a lot more manageable.

In Summary

The biggest problem was my branching model, or, rather, my lack of one. Solitary developers often fall into this trap: we think that, since we're doing all the work, there's no need to have more than a single branch of development. At work, we've been using this model and found it very successful. Going forward, I plan to do this on PhyloPic as well. No more massive updates where everything is different. Just incremental features and fixes.

05 March 2011

PhyloPic Week 2: Lineages, Browsing, and API

Another good week for PhyloPic. There are now well over 200 silhouettes in the database. I also rolled out some new features and enhancements.

Redesigned Lineage Pages

Lineage pages now provide taxonomic and license information for each image. As a visual touch, figures now fade as they go deeper and deeper into the past. Here's a few of my favorite lineage pages so far:
Yes, they're all bilaterian animals. There's a definite bias.

Image Browser

Now you can peruse the entire gallery much more easily, with the Image Browser. Use the arrow(s) on the side to navigate through pages of silhouettes.


Developer API

For any developers out there who want to use the PhyloPic database to create their own apps, now you can. I've provided an initial API, available both as a JSON service and an AMF service for Flex apps.

Also of news to developers: I've opened up the code base for viewing and cloning. (Still need to add the licenses, though.) It's a Django app, written in Python. Feel free to poke around.

Thanks


I'd like to thank everyone who's submitted images so far, especially FunkMonk, Scott Hartman, Matt Martyniuk and Maija Karala for their many contributions. (Each of them has submitted at least a dozen.) Thanks also to Steven Coombs, Craig Dylke, Mo Hassan, Neil Kelley, Dann Pigdon, Ville-Veikko Sinkkonen, Patrick Strutzenberger, Reka Szabo, David Tana, Michael P. Taylor, and Emily Willoughby!

21 February 2011

Introducing PhyloPic: An Open Database of Reusable Silhouettes

Ever had this problem? "Boy, I could sure use a silhouette of [some kind of organism] for this diagram I'm working on. But I can't find anything on the web! Well, except for a few images which are copyrighted...."

What if there were a website with an open database of reusable images, available under Creative Commons licenses? What if you could do phylogenetic searches, so that, even if there weren't a silhouette for the taxon in question, you could at least find something close? What if you could build images like this...


...without having to look all over the web for figures?

Well, now you can! I've launched a new site called:


It's currently in public alpha, which means it's not quite done. So, I have some caveats:

  • I'm pulling most taxonomic data from uBio. It's great because it's really comprehensive. But it's also a huge mess because it stores multiple classifications, many of which are outdated and disagree with each other. (This isn't uBio's fault, as its goal is to store all these classifications, not to offer one nice, neat classification.) So you may (will) find some errata in the phylogenetic system. I'm working on cleaning it up, but there are a lot of taxonomic names out there....
  • It's still early on, so there are only about a hundred images in the database. It will grow over time, but don't be surprised if the closest image it has for your favorite invertebrate is some kind of indiscriminate worm.
  • There are some known bugs (and I don't mean Hemiptera). The Issues Page is open to all, though, so you can read the known issues and report new ones. (Please do!)
It's a work in progress, but I think it has enormous potential. And I think it's reached a state where it's ready for public use and feedback. So have a look, see what you think, and let me know! (And, if you're artistically inclined, please consider submitting some silhouettes of your own.)

05 March 2010

Names on Nodes: Cutting Out the Fat

While pondering the headaches of homonymy recently, I started to ask myself, What am I doing with my life? Why am I worrying about this?

Seriously, though. I've been working on Names on Nodes on and off for about three years, and I still haven't launched it. And it's because I've been so focused on getting things like this right. (Well, that and having a day job.)

But things like this aren't part of the core functionality. The core functionality is the automated evaluation of phylogenetic definitions (encoded as MathML) within the context of phylogenetic hypotheses (modeled as directed, acyclic graphs). That part of it's been done for quite a while. So why am I wasting time on the rest?

By cutting out the entire database portion of the project, I could actually have something launched this year. Sure, it'd be nice to have a repository of taxonomic names, definitions, authorities, etc. But it's not necessary. It's phase II, not phase I. In fact, by the time I'm ready for a phase II, there will almost certainly be other services out there that already perform those things.

So, here on out, I'm going to be focusing on getting a lean, mean version of Names on Nodes up. Here's a quick summary of what you'll be able to do with it:
  1. Open bioinformatics files (NEXUS to start with, other formats like nexml going forward).
  2. View the phylogenies in a pretty graphical interface.
  3. Merge phylogenies.
  4. Tweak phylogenies (adding or removing parent-child relations, adding or removing taxonomic units, equating taxonomic units, etc.).
  5. Formulate phylogenetic definitions using a spiffy interface.
  6. Apply these definitions to the phylogeny.
  7. Save your work as MathML files.
And that should be manageable (although there is still much work to be done, especially for the user interface). Once that's launched and working, then I'll look into connecting to other services and/or launching an associated database.

23 July 2009

Two "Names on Nodes"-Related Launches

I'm still a clear way away from launching the beta application, but I've just made a couple of launches related to my long-time work-in-progress, Names on Nodes.

First up, and probably of more interest to most people, I've begun the documentation for the MathML definitions used by Names on Nodes. The document includes general reviews of relevant mathematical and biological concepts, a quick review of MathML and the technologies it's based on, some comments on correlating mathematical and biological concepts, and definitions for all entities (including operations) used by Names on Nodes. Note that this covers a lot of the same ground as in my 2007 paper, with a few minor changes in the symbols and terminology (e.g., I now call the ancestor of a clade a "cladogen" rather than a "cladogenetic set").

Secondly, I've made the project open-source, by moving it to Google Code. If you are a developer interested in checking this out, go here. It's incomplete, so I don't know if anyone will have any real interest in looking at it yet. (Honestly, I mostly posted so that, on the off chance that I unexpectedly kick the bucket, my magnum opus won't be lost forever.)

This information is also on the new Names on Nodes home page.

29 May 2009

One Name, One Taxon

One of the primary goals of a nomenclatural code should be to make sure that names refer to one taxon and one taxon only. This principle is mentioned in the ICZN's preamble (emphasis added):
The objects of the Code are to promote stability and universality in the scientific names of animals and to ensure that the name of each taxon is unique and distinct. All its provisions and recommendations are subservient to those ends and none restricts the freedom of taxonomic thought or actions.
This point is reiterated in Article 52:
52.1. Statement of the Principle of Homonymy. When two or more taxa are distinguished from each other they must not be denoted by the same name.
Yet there are numerous cases in zoological nomenclature where this rule is flagrantly ignored. A few:
  • "Echinoidea" is the name of a superfamily containing Echinus (a sea urchin genus), but also the name of a class containing that superfamily.
  • "Ophiuroidea" is the name of a superfamily containing Ophiura (a brittle star genus), but also the name of a class containing that superfamily.
  • "Chelonia" is a genus of turtle, but also used as the name of the order containing all turtles.
  • "Pterodactyloidea" is the name of taxon given various ranks (usually suborder) including most short-tailed pterosaurs, but also the name of a superfamily within that taxon.
This is getting to be an actual problem for me, because parts of Names on Nodes rely on the principle that a name only has one meaning under a given authority. When I create a database entry for urn:isbn:0853010064::Pterodactyloidea, is it for a suborder or a superfamily? ICZN rules actually dictate that the superfamily has precedence, since Family Pterodactylidae Meyer 1830 has precedence over Suborder Pterodactyloidea Plieninger 1901. (The ICZN considers the naming of any taxon whose rank is in the family group as implicitly naming taxa for all ranks of the family group; thus, naming Family Pterodactylidae implicitly names Superfamily Pterodactyloidea, Subfamily Pterodactylinae, Tribe Pterodactylini, and Subtribe Pterodactylina.) People who use "Pterodactyloidea" for a suborder, beware! You are violating the rules of the ICZN! (WhooOOOOoo!!)

The situation with "Echinoidea" is even worse. As near as I can tell, Family Echinidae was named by Gray in 1825 (thus implicitly naming Superfamily Echinoidea), but Class Echinoidea was already named by Leske in 1778. And the ICZN mandates that the superfamily including Echinus must be named "Echinoidea" if the family is named "Echinidae". I'm not sure how this is supposed to play out ... does Echinus simply not get a name for its superfamily? Those poor wee urchins....

(And people wonder why I support an alternative nomenclatural code without mandated suffixes for ranks!)

In the case of Chelonia, people are generally using another name ("Testudines") for the order nowadays, but in other cases I've got a real problem, especially if I hope to automatically pull a lot of this data from other databases.

23 March 2009

Logging In Without a Password

I probably have hundreds of online accounts: email, discussion forums, social networking, online shopping, server hosting, issue reporting, etc. Trying to remember all the passwords is a pain. Often, when going to a site I haven't been to in a while, I just reset it, or have it sent by email, or have a new one sent by email, or however the site in question works.

I might want to try something different for Names on Nodes. As hinted at in earlier posts, users will be considered "authorities" in Names on Nodes, along with publications, bioinformatics files, specimen repositories, nomenclatural codes, etc. All authorities are associated with one or more unique URIs, such as website addresses, ISBN numbers, DOIs, LSIDs, etc. For users, the primary URI will be an email address, in the form <mailto:myname@somedomain.tld>.

Why have an account? Well, because then, as an authority, you get to "authorize" your own datasets and taxon identifiers (and, by proxy, taxon definitions). Datasets and taxon identifiers are "qualified" objects, meaning that they each refer to an authority, and they each have a "local name" unique under that authority. A qualified name is formed by joining the URI of the authority and the local name. So, for example, if you wanted to create a new phylogenetic hypothesis about mushrooms, it might have the qualified name <mailto:me@myemailprovider.com::dataset:basidiomycota+phylogeny>. If you wanted to provide your own definition for the name "Eumetazoa", it would be attached to a taxon identifier with the qualified name <mailto:me@myemailprovider.com::Eumetazoa>. And so on.

How do you log in without a password? I'm thinking of a system involving IP addresses, the numerical code that identifies your computer's connection. For most environments, these are relatively stable, although if you use, e.g., a DSL modem it may reset once in a while. Here are some potential use cases:

Initial Login


Preconditions.—User has never logged in. User's email is unregistered. User is 13 years of age or older.
Trigger.—User tries to do something that requires login.
Course of events.
  1. User is prompted for their email address. They are also prompted on whether they want to stay logged in across sessions.
  2. User is prompted for their birthdate, full name, and family name.
  3. User gets a notice telling them that they have been sent a confirmation via email. The notice includes an input field for a "key".
  4. User checks their email, and sees an email message with a link. There is also a "key", a string of letters and numbers that they can copy and paste.
  5. User clicks on the link.
  6. Names on Nodes reopens, with the user logged in.
Alternate course of events.
  1. User copies and pastes the "key" into the input field.
  2. User is now logged in.
Postcondition.—User's email and current IP address is registered. User can perform the action that triggered this use case.

Subsequent Login, Registered IP Address


Preconditions.—User's email is registered. User is not logged in, having logged out or having declined to stay logged in across sessions.
Trigger.—User tries to do something that requires login.
Course of events.
  1. User is prompted for their email address. They are also prompted on whether they want to stay logged in across sessions.
  2. User is now logged in.
Postcondition.—User can perform the action that triggered this use case.

Automatic Subsequent Login, Registered IP Address


Preconditions.—User's email is registered. User indicated that they wanted to stay logged in across sessions the last time they logged in.
Trigger.—User visits website.
Course of events.
  1. User is automatically logged in, and their name is shown in a "Welcome" message.
Postcondition.—User can perform any action that requires being logged in.

Subsequent Login, Unregistered IP Address


Preconditions.—User's email is registered. User has never logged in from their current IP address.
Trigger.—User tries to do something that requires login.
Course of events.
  1. User is prompted for their email address. They are also prompted on whether they want to stay logged in across sessions.
  2. User enters their email address.
  3. User gets a notice telling them that they have been sent a confirmation via email. The notice includes an input field for a "key".
  4. User checks their email, and sees an email message with a link. There is also a "key", a string of letters and numbers that they can copy and paste.
  5. User clicks on the link.
  6. Names on Nodes reopens, with the user logged in.
Alternate course of events.
  1. User copies and pastes the "key" into the input field.
  2. User is now logged in.
Postcondition.—User's current IP address is registered. User can perform the action that triggered this use case.

Unregistering IP Addresses


Preconditions.—User's email is registered, and user is logged in.
Trigger.—User decides to invalidate other IP addresses, perhaps fearing someone else may log in as them from another computer.
Course of events.
  1. User selects the "Block Other Locations" option.
  2. User is prompted to confirm this request.
  3. User confirms the request.
  4. User receives notification that other locations have been unregistered.
Postcondition.—User's current IP address remains registered, but all others are not. User must now register other addresses again if they try to log in from a previously-used address.

I'm not sure if this would be too convoluted in practice, but somehow I doubt it. If anything, it seems no worse then the usual type of system, except possibly for people who use laptops and are constantly on the move.

What do people think? Ideas, questions, concerns?

17 March 2009

Refactoring "Names on Nodes" Entities, Part II

As I discussed previously, the Names on Nodes project had reached a point where the schema just wasn't working out. I went through a list of what was wrong with it: confusing nomenclature, various unnecessary classes, unnecessary references, and major practical problems with looking up contextual relations.

Another big problem was the home-brewed keyword search system I had going. Synchronizing the keyword lists was becoming problematic, and I realized there are already perfectly good (better, even) tools out there such as Hibernate Search. That's a chief rule of programming: don't reinvent something that people smarter than you, with more time on their hands, have already invented.

After a clear, honest look at the contextual relations, I came to a realization: they should be in the client, not the back end. No need to bog down the server with computing definition applications when it can be done in the client. That simplified things a great deal.

Another thing I didn't really need was categories. They were basically an ad hoc form of class inheritance, e.g., a species name is a nomen, a nomenclatural code is a publication, etc. For a little while I considered implementing this as a class hierarchy, as I had in earlier versions. But, really, this is irrelevant data—Names on Nodes doesn't really need to know what category an identifier falls in.

Finally, I had another problem in the way datasets and taxon identifiers (=signifiers) used qualified names. Each one was supposed to have a unique qualified name. While I was able to guarantee uniqueness within datasets and within taxon identifiers, I wasn't able to guarantee that qualified names would be unique between datasets and taxon identifiers.

So, here's the new version (click to magnify):
Again, white arrows indicate "is-a" relationships ("inheritance")—so a PhyloDefinition is a type of Definition, a Dataset is a type of Qualified object, etc. And black diamonds indicate "has-a" relationships ("composition")—so a TaxonIdentifier has one (and only one) Taxon, an Equation has at least two TaxonIdentifier objects, etc. (I've left out a few non-core classes, like BioFile and UserAccount.)

Brief discussions of each class:

Authority.—An authority can be a publication, a person, a bioinformatics file, a database, a specimen catalogue, etc. Each authority has a canonical name (e.g., "Yale Peabody Museum: Vertebrate Paleontology Collection") and an optional abbreviation (e.g., "YPM-VP").

AuthorityIdentifer.—One or more identifiers may be used to indicate an authority, each one associated with a unique URI. Examples:
Qualified.—This new abstract class makes it possible for qualified names to be unique across all classes that use them. Each refers to an authority identifier and contains a local name, which is unique to that identifier. When combined, the identifier's URI and the local name form a qualified name, e.g., <urn:isbn:0853010064::Homo+sapiens> or <http://peabody.yale.edu/collections/vp::1450>.

TaxonIdentifier & Taxon.—Formerly called "signifiers", taxon identifiers are qualified objects that each refer to a taxon. Taxon identifiers may be scientific names, vernacular names, specimen identifiers, character state descriptions, etc. As with authorities, each taxon may have more than one identifier referring to it. For example, the following qualified names all refer to the same species: <urn:isbn:0853010064::Abeillia+abeillei>, <http://iucnredlist.org::species:142883>, and <http://iucnredlist.org::common_name:Eng:Emerald-chinned+Hummingbird>.

Label.—Authorities, datasets, and taxon identifiers are all labelled entities, possessing one label object. Each label has a name, an optional abbreviation, and a flag telling whether it should be italicized. Labels are merely cosmetic, and need not be unique. They are used as the targets of searches, using Hibernate Search.

Definition.—Each definition has one taxon identifier, and only one definition pertains to that taxon identifier. How do I accommodate differing definitions, then? I use a concept from the PhyloCode: conversion. Consider the name "Aves". Under the ICZN, it refers to a suprafamilial ranked taxon with no type. According to Sereno's TaxonSearch, it refers to a node-based clade including Archaeopteryx. According to Gauthier and de Queiroz (2001), it refers to a crown group. But instead of having multiples definitions for the same identifier, I consider each definition to define a different identifier, each indicating a (potentially) different taxon: <urn:isbn:0853010064::Aves>, <http://www.taxonsearch.org/Archive/stem-archosauria-1.0.php::Aves>, and <urn:bici:0912532572(200112)%3C7:FDFDCD%3E2.0.TX;2-H::Aves>, respectively. In cases of conversion, the definition also indicates the original identifier.

PhyloDefinition & RankDefinition.—These have not changed much, except that they now refer directly to their specifers and types, respectively. No more useless "Anchor" class.

Dataset.—Instead of storing a bunch of relations of unspecified type, each type of relation falls within its own set. I've also added optional ratios for converting weights in phylogenetic networks to generations and/or years.

Equation.—I almost called this "Synonymy". This is a new type of relation, which asserts that two or more identifiers refer to the same taxon.

Heredity & Inclusion.—Heredity was previously called "Parentage". The new nomenclature better reflects its real meaning, since it models ancestor-descendant relationships, not necessarily parent-child. These two classes are little changed, except that now they don't both descend from a useless Relation class, so their nomenclature can be clearer (predecessor and superset used to be "a"; successor and subset used to be "b").


This schema is much cleaner, and will make for a more efficient server-side. I've already implemented the entities, removed deprecated code, and updated the relevant code. After some hiccups with a Hibernate upgrade, unit tests are working again. The back-end should be complete fairly soon (pending some ideas about user accounts), and then it will be time to look at some massive refactorings for the front end!

13 February 2009

Using Conservation Status to Automatically Apply Phylogenetic Definitions

To briefly summarize some relevant points in the last post (Extinct or Extant?):
Since 2001, the IUCN Red List has used the following categories:
  • EX: Extinct
  • EW: Extinct in the Wild
  • CR: Critically Endangered
  • EN: Endangered
  • VU: Vulnerable
  • NT: Near Threatened
  • LC: Least Concern
  • DD: Data Deficient
  • NE: Not Evaluated
As mentioned in earlier posts, Names on Nodes uses URIs (URLs, ISBN numbers, DOIs, etc.) for authorities and qualified names (URI + unique local name) for taxonomic signifiers. Thus, these states can be stored as signifiers in the Names on Nodes database. Examples for the 2008 assessment: One wonderful thing about the IUCN database is that you can export query results as XML (also CSV): Here's an example of an entry:
<species id="148296">
  <scientific_name>
    Zosterops xanthochroa
  </scientific_name>
  <kingdom_name>
    ANIMALIA
  </kingdom_name>
  <phylum_name>
    CHORDATA
  </phylum_name>
  <class_name>
    AVES
  </class_name>
  <order_name>
    Passeriformes
  </order_name>
  <family_name>
    Zosteropidae
  </family_name>
  <genus_name>
    Zosterops
  </genus_name>
  <species_name>
    xanthochroa
  </species_name>
  <authority>
    Gray, 1859
  </authority>
  <synonyms>
    <synonym>
      <scientific_name>
        Zosterops xanthochrous
      </scientific_name>
      <genus_name>
        Zosterops
      </genus_name>
      <species_name>
        xanthochrous
      </species_name>
    </synonym>
  </synonyms>
  <common_names>
    <name lang="Eng">
      Green-backed White-eye
    </name>
  </common_names>
  <assessment
      version="3.1"
      year="2008">
    <category>
      LC
    </category>
  </assessment>
</species>
This provides a source not only for the conservation status of species, but also for the species themselves and some of their higher taxa as well. This one XML snippet can provide all of the following signifiers:It also authorizes a number of superset-subset relations, e.g., "Zosterops includes Zosterops xanthochroa" and "Least Concern (2008) includes Zosterops xanthochroa". The latter identifies Z. xanthochroa as an extant species during 2008. Because of relations like this, we can build a MathML set for the set of all organisms (or populations, whatever) which were extant in 2008 according to the IUCN Red List:
<apply xmlns="http://www.w3.org/1998/Math/MathML">
  <union/>
  <csymbol
    definitionURL="urn:isbn:2831706335::categories:EW:2008"/>
  <csymbol
    definitionURL="urn:isbn:2831706335::categories:CR:2008"/>
  <csymbol
    definitionURL="urn:isbn:2831706335::categories:EN:2008"/>
  <csymbol
    definitionURL="urn:isbn:2831706335::categories:VU:2008"/>
  <csymbol
    definitionURL="urn:isbn:2831706335::categories:NT:2008"/>
  <csymbol
    definitionURL="urn:isbn:2831706335::categories:LC:2008"/>
</apply>
Presto, now I can apply modified node-based definitions and total group definitions! Thanks, IUCN, for helping to enable the automated application of phylogenetic definitions! (And, you know, also for all the "saving threatened species from extinction" stuff.)

08 January 2009

Using SQL to make phylogenetic queries

"Computer, can any dinosaurs fly?"

It would be great if we could ask computers questions this easily. Unfortunately, computers lack our natural linguistic skills. But this doesn't mean they are incapable of answering such questions—the questions just need to be rephrased.

SQL (pronounced either as "S.Q.L." or "sequel", and standing for "Structured Query Language") is the most commonly-used language for querying relational databases. This past week I have been adapting the phylogenetic operations in my 2007 paper into SQL (specifically the PostgreSQL version) as part of my Names on Nodes project. There have been a few difficulties to do with the fact that the database only records taxa (sets of organisms), while my published algorithms work at the organism level. But this is a minor difficulty so far, and things seem to be working.

To get an idea of how the system works, let's look at the question posited at the beginning of this post. How can the question be translated into a SQL query?

First, a quick summary of what's in the database: taxa (sets of organisms) of all kinds are represented by a table of "signifiers". Each signifier references an "authority", which could be anything from a publication to a nomenclatural code to a systematics dataset to a personal opinion. Each signifier also has a name which is unique under that authority. For example, under the authority of the International Code of Zoological Nomenclature (ICZN for short), the name "Homo sapiens" signifies a particular taxon (a species). Under the authority of the Yale Peabody Museum's Vertebrate Paleontology collection, the name "1450" refers to a specimen, and by proxy the set of organisms (in this case, just one) which that specimen represents. Under the authority of a particular NEXUS file, "data/flight/present" may refer to a character state, and by proxy the set of all organisms exhibiting that state.

Each authority is represented by one or more Universal Resource Identifiers, or URIs. Website addresses (URLs) are one kind of URI, so, for example, the Yale Peabody Museum's Vertebrate Paleontology collection can be represented as http://www.peabody.yale.edu/collections/vp. ISBN numbers are another type of URI, so the ICZN can be represented as urn:isbn:0853010064. Systematics files are a little trickier; my current solution is to use a custom schema and a unique "hash" string of characters programmatically derived from the data in the file, e.g., something like biofile:08192A3 (actually longer, but I'll keep it short for this discussion).

Each signifier can be represented as a "qualified name" (or "QName"), which combines the authority's URI and the signifier's name. Examples:
  • http://www.peabody.yale.edu/collections/vp::1450
  • urn:isbn:0853010064::Homo_sapiens
  • biofile:08192A3::data/flight/present
(A fuller discussion of the Names on Nodes database can be found in this earlier post.)

Definitions may be stored as MathML, as indicated in my 2007 paper. Let's suppose that someone defines Dinosauria as, "the final common ancestor of Megalosaurus bucklandii von Meyer 1832, Hylaeosaurus armatus Mantell 1833, and Iguanodon bernissartensis Boulenger in Beneden 1881, plus all descendants of that ancestor." This can be rendered as:

<apply xmlns="http://www.w3.org/1998/Math/MathML">
<csymbol definitionURL="http://namesonnodes.org/phylo/math​::nodeClade"/>
<csymbol definitionURL="urn:isbn:0853010064​::Hylaeosaurus_armatus"/>
<csymbol definitionURL="urn:isbn:0853010064​::Iguanodon_bernissartensis"/>
<csymbol definitionURL="urn:isbn:0853010064​::Megalosaurus_bucklandii"/>
</apply>

Basically, this means, "Apply the nodeClade function to the the union of the signified taxa." The nodeClade function corresponds to the phrase, "the final common ancestors and all descendants thereof". It relies on three other phylogenetic functions: allDescendants, maximal, and commonAncestors. ("Maximal" in this context means "all members of a set which are not ancestral to any other members in that set"—a more precise way to say "final".)

Names on Nodes will take a MathML definition like this and convert it into a SQL query like this:
  SELECT * FROM node_clade(
ARRAY(SELECT * FROM resolve_signifier_identities(
ARRAY[
'urn:isbn:0853010064::Hylaeosaurus_armatus',
'urn:isbn:0853010064::Iguanodon_bernissartensis',
'urn:isbn:0853010064::Megalosarus_bucklandii'
])),
0);
The resolve_signifier_identities function converts an array of qualified names into a set of identity numbers corresponding to signifiers, using a query like this:
  SELECT identity_id
FROM signifier
WHERE qname = ANY(qname_list);
This is a relatively simple SQL query. The grammar is fairly intuitive (at least for a computer language). The query for the node_clade function is far more complex, though. To discuss that, I'll have to go into the database structure a bit more.

A phylogenetic definition cannot be applied without some kind of phylogenetic hypothesis. Names on Nodes is capable of reading phylogenies encoded as Newick trees, including those in NEXUS files. Let's suppose someone uploaded a NEXUS file with this Newick tree in it:

((Iguano_bern, Hylaeo_arma), (Megalo_buck, (Tyrann_rex, (Anas_plat, Passer_dome))))

Let's suppose the URI for this file is biofile:08192A3. Then this file authorizes some new signifiers:
  • biofile:08192A3::taxa/Iguano_bern
  • biofile:08192A3::taxa/Hylaeo_arma
  • biofile:08192A3::taxa/Megalo_buck
  • biofile:08192A3::taxa/Tyrann_rex
  • biofile:08192A3::taxa/Anas_plat
  • biofile:08192A3::taxa/Passer_dome
These can be objectively equated with other signifiers:
  • urn:isbn:0853010064::Iguanodon_bernissartensis
  • urn:isbn:0853010064::Hylaeosaurus_armatus
  • urn:isbn:0853010064::Megalosaurus_bucklandii
  • urn:isbn:0853010064::Tyrannosaurus_rex
  • urn:isbn:0853010064::Anas_platyrhynchos
  • urn:isbn:0853010064::Passer_domesticus
(The last two could conceivably be equated with vernacular names from a field guide as well, e.g., urn:isbn:0679428518::Mallard and urn:isbn:0679428518::House_Sparrow, respectively) Equated signifiers share the same identity. This file also authorizes some less visible signifiers: the hypothetical ancestors, such as the ancestor of Iguano_bern and Hylaeo_arma, the ancestor of Anas_plat and Passer_dome, etc. Names on Nodes assigns these ancestors arbitrary, numerical names.

The phylogeny in the Newick tree can be represented as a set of arcs, each pointing from a parent signifier (or "head") to a child signifier (or "tail"):
  ~1 → ~2
~1 → ~3
~2 → Iguano_bern
~2 → Hylaeo_arma
~3 → Megalo_buck
~3 → ~4
~4 → Tyrann_rex
~4 → ~5
~5 → Anas_plat
~5 → Passer_dome
These get stored in the database as rows representing parentage relations. Similar relations are used for supersets and subsets (e.g., to indicate that Tyrannosaurus includes Tyrannosaurus rex). Phylogenetic functions, such as maximal, all_ancestors, and common_ancestors, utilize the data in these relations.

Some of these functions use another function called find_parentage_arcs to get a list of all parent-child relations which involve any of a given set of signifiers. For example, maximal uses a query like this:
  SELECT id
FROM signifieridentity
WHERE id = ANY(identity_list)
EXCEPT
SELECT arc.head
FROM find_parentage_arcs(identity_list, context_id)
AS arc;
In other words, select all signifier identities from a given list, except those which are the parent (head) in any parent-child arc whose parent and child are in that list. (The context_id variable specifies which datasets to use and which to ignore, but that's an essay unto itself.)

The node_clade function uses a query like this:
  SELECT * FROM all_descendants(ARRAY(
SELECT * FROM maximal(ARRAY(
SELECT * FROM common_ancestors(identity_list,
context_id)),
context_id)),
context_id);
In other words, select all descendants/members of all maximal members of the common ancestors of the specified signifiers. (Again, context_id specifies which datasets to use; a value of zero means that all datasets should be used.)

Running the Dinosauria SQL query mentioned earlier will now yield a set including all of the signifiers mentioned so far. In other words, according to the provided definition and the provided phylogenetic hypothesis, I. bernissartensis, H. armatus, M. bucklandii, T. rex, A. platyrhynchos, and P. domesticus are all members of Dinosauria (as are the hypothetical ancestors in the phylogenetic tree).

Suppose the aforementioned NEXUS file also had a character matrix including the character "flight". This would be scored as "present" in Anas_plat and Passer_dome. Thus, the table of inclusions would include these arcs:
  flight/present → Anas_plat
flight/present → Passer_dome
(Names on Nodes also extrapolates the arc flight/present → ~5, but, again, that's another essay.)

A function called subsets retrieves all signifiers indicated as subsets of a given signifier. Thus, the question that began this essay can be written in SQL as:
  SELECT COUNT(dinosaur) > 0 AS verdict
FROM node_clade(
ARRAY(SELECT * FROM resolve_signifier_identities(
ARRAY[
'urn:isbn:0853010064::Hylaeosaurus_armatus',
'urn:isbn:0853010064::Iguanodon_bernissartensis',
'urn:isbn:0853010064::Megalosarus_bucklandii'
])),
0) AS dinosaur
JOIN subsets(
resolve_signifier_identity(
'biofile:08192A3::data/flight/present'
),
0) AS flier ON dinosaur = flier;
The verdict: true.

10 November 2008

Names on Nodes: Entities

Here's a UML diagram of the latest class schema for the core Names on Nodes entities:

The white arrows indicate inheritance, i.e., "is-a" relationships. For example, a PhyloDefinition is a type of Definition. The black diamonds indicate composition, i.e., "has" relationships. For example, a Definition has any number of Anchor entities, each of which has exactly one Signifier entity.

Some comments on the major classes of entity:

Signifier.—This is what everything revolves around. A Signifier signifies a set of organisms, that is, a taxon. Signifier entities may be scientific names, specimens, character states, or taxonomic units in systematic studies.

Several Signifier entities may share the same SignifierIdentity, indicating that they are different ways of referring to the exact same thing. For example, Felis leo (ICZN) and Panthera leo (ICZN) are objective synonyms. (Subjective synonyms do not share the same identity.)

Authority.—Every Signifier is unique within an Authority. Authority entities may be publications, nomenclatural codes, personal opinions, specimen repositories, or bioinformatics files. Every Authority is associated with a unique URI (e.g., a web address, a DOI, an ISBN number, etc.).

Like Signifier entities, different Authority entities may share an identity (AuthorityIdentity). These Identity entities are hidden from other entities, so that Authority and Signifier entities can be equated or differentiated without affecting other entities in the database.

Relator.—A Relator is a set of Relation entities, each of which represent a statement about two Signifier entities, either Inclusion (i.e., a is a superset of b) or Parentage (i.e., a is immediately ancestral to b).

Definition.—A Definition defines a Signifier according to an Authority, and may have any number of Anchor entities, each of which tells whether a given Signifier is objectively a subset of the defined Signifier.

RankDefinition.—A RankDefinition consists of a rank and some number of internal Anchor entities. For example, under the ICZN (an Authority), Hominidae (a Signifier) is defined as the family (a rank) typified by Homo (a Signifier referenced by an internal Anchor).

PhyloDefinition.—A PhyloDefinition consists of a formula, expressed prosaically and mathematically. For example, according to Gauthier & de Queiroz 2001 (an Authority), Aves ( a Signifier) is defined as, "the crown clade stemming from the most recent common ancestor of Ratitae (Struthio camelus Linnaeus 1758), Tinamidae (Tetrao [Tinamus] major Gmelin 1789), and Neognathae (Vultur gryphus Linnaeus 1758)." This Definition is specified by three internal Anchor entities, respectively referencing the species Struthio camelus, Tetrao major, and Vultur gryphus (all of which are Signifier entities).

Dataset.—The relations in a Dataset are based on observation or hypothesis. A Dataset entity's Authority may be a bioinformatics file, a publication, or a personal opinion. As with Signifier entities, every Dataset can be uniquely identified by a qualified name, combining the URI of its Authority and a local name.

Context.—Applying phylogenetic definitions requires a Context, which is essentially a set of Dataset entities. All Definition and DefinitionApplication entities are implicitly included under every Context.

DefinitionApplication.—This is sort of the crux of the whole idea behind this project: that a PhyloDefinition can be automatically applied under a given Context.

19 October 2008

This is why I never get anything done.

I was getting pretty close to presentable with Names on Nodes, when I had a revelation. Now I have to rewrite most of it.

The revelation was this: nomenclatural codes, bioinformatics files, publications, specimen collections, and people are all the same thing. They are authorities.

Scientific names, taxonomic units, character states, and specimens are all the same thing. They are signifiers. They each signify a taxon (a set of organisms).

Signifiers are authorized by an authority. For example, Homo sapiens is a species authorized by the International Code of Zoological Nomenclature. YPM-VP 1450 is a specimen authorized by the Yale Peabody Museum's Vertebrate Paleontology Collection. "Wings used for powered flight," is a character state authorized by Gauthier & de Queiroz (2001). "30. Number of stamens: ten or fewer," is authorized by the NEXUS file registered as M331 in TreeBASE, as are the taxonomic units Phytolaccaceae and Lardizabalaceae.

Signifiers may share the same identity. For example Tyrannosaurus bataar (ICZN) and Tarbosaurus bataar (ICZN) signify the same taxon, no matter what. The identity is only accessible to the signifiers themselves, which means that signifiers can be equated and differentiated without disrupting references to them. (A similar identity property holds for authorities.)

Every authority may be associated with an absolute URI (universal resource identifier). Publications (including nomenclatural codes) may be associated with DOIs, ISBNs, etc. People may be associated with OpenIDs. Anything may be associated with a web address. It's a bit trickier for NEXUS files, but I figure that they can be uniquely identified by an ad hoc schema plus a SHA-1 hash of their textual data.

Examples:
  • http://www.peabody.yale.edu/collections/vp — Yale Peabody Museum: The Collections: Vertebrate Paleontology
  • http://uppsaladomkyrka.se — Uppsala domkyrka (cathedral)
  • urn:isbn:0080-0694/146 — The International Code of Botanical Nomenclature (Vienna Code)
  • http://openid-provider.appspot.com/keesey — Timothy Michael Keesey
  • http://threelbmonkeybrain.blogspot.com — Timothy Michael Keesey (also!)
  • urn:isbn:0-912532-57-2/chapter1 — Gauthier & de Queiroz 2001
  • biofile:5b2f349967­c18006233f­c89b8643ff­6c57be2858 — the NEXUS file of Rodman & al. 1984
Each signifier, then, can have a unique local name under its associated authority, which forms a unique qualified name when combined with the authority's URI. Examples:
  • http://www.peabody.yale.edu/collections/vp::1450 — a specimen
  • http://uppsaladomkyrka.se::Carolus+Linnaeus — a specimen
  • urn:isbn:0-85301-006-4::Homo+sapiens — a species
  • urn:isbn:0-912532-57-2/chapter1::wings+used+for+powered+flight — a character state
  • biofile:5b2f349967­c18006233f­c89b8643ff­6c57be2858::CHARACTERS/19._Crassulacean_acid_metabolis/present_in_at_least_some_specie — a character state
  • biofile:5b2f349967­c18006233f­c89b8643ff­6c57be2858::TAXA/Menispermaceae — a taxonomic unit
This basically means four things:
  1. I don't have to track that much information about each thing, since that information is held in other resources. I really just need to reference other resources (authorities and signifiers) and maybe provide a convenient name for each one (a canonical name in the Names on Nodes database).
  2. It is possible to create an extremely flexible data model capable of accomodating just about any data set, nomenclatural act, or taxonomic opinion.
  3. When using Names on Nodes, you'll be able to filter out authorities you don't want to use.
  4. I gotta redo a lot of stuff.
One thing I still have to completely figure out is the idea of relators. A relator is an entity which contains a set of relations, each of which relate a signifier to another. Two major types of relations are inclusion and precedence (i.e., ancestry). Examples:
  1. Precedence.—nexus:5b2f349967­c18006233f­c89b8643ff­6c57be2858::TREES/Fig._2/a (a hypothetical ancestor) is ancestral to nexus:5b2f349967­c18006233f­c89b8643ff­6c57be2858::TAXA/Caryophyllaceae according to nexus:5b2f349967­c18006233f­c89b8643ff­6c57be2858::TREES/Fig._2.
  2. Inclusion.—urn:isbn:0-85301-006-4::Homo includes urn:isbn:0-85301-006-4::Homo+sapiens according the rank-based definition authorized by urn:isbn:0-85301-006-4.
  3. Inclusion.—urn:isbn:0-85301-006-4::Homo+sapiens includes http://uppsaladomkyrka.se::Carolus+Linnaeus. according the rank-based definition authorized by urn:isbn:0-85301-006-4.
In case #1, the relator is a tree in a NEXUS file. In cases #2 and #3, the relators are rank-based definitions authorized by the International Code of Zoological Nomenclature.

So, once this is set up, the application will be able to automatically apply phylogenetic definitions, given a certain set of relators. This set will typically include a tree (or network) and a character matrix (optionally). But it could also include many trees, or a custom phylogeny. It gets a bit complex, though, since definitions themselves are relators (mandating the inclusion of types or internal specifiers), as are contextual applications of definitions (indicating other, non-essential inclusions).

Still some details to work out, but I think I'm on a good track here.

30 May 2008

Names, Nodes, and CRUD

The Third ISPN Meeting (mentioned here) is fast approaching. I'm still hoping to have an alpha version of Names on Nodes online by then, but it's going to be tricky. Currently I'm involved in a massive refactoring.

The basic point of Names on Nodes, as I've mentioned before, is to automate taxonomy. That is, you feed it:
  1. a bunch of phylogenetic definitions,
  2. a phylogeny, and
  3. specifier links, that is, correlations between definition specifiers (apomorphies, species, specimens) and units in the phylogeny (e.g., taxa in NEXUS trees).
Once properly fed, it spits out taxonomies, that is, associations of scientific names with sets of subtaxa.

That part of it is pretty much done. But there are two tricky parts I have yet to complete: data persistence and user interface. And what both basically boil down to is CRUD.

I've mentioned CRUD before here. It's an acronym for the four basic operations involved with data persistence, the four things you can do to a stored object. Those are:
  • Create
  • Retrieve
  • Update
  • Delete


As often happens in programming, I started out with an ad hoc sort of approach that worked well enough up to a point, but then I started finding myself in trouble. Duplicated code. Unnecessarily complex code. Code that was hard to update.

It was time to refactor.


Here's a breakdown of the new system.

Every user interface involved with persistent objects is managed by something called a FlowManager. This stores a navigable tree of FlowEntry objects, each associated with a visual FlowComponent.

The main application handles requests from all components within. These are sent to the application as bubbling events, that is, events that "bubble" up through the display hierarchy (say, from a button, to the form that contains it, to the flow manager, on up to the main application). Two of the major types of request are EditEntityEvent and DemandEntityEvent.

An EditEntityEvent comes with a persistent object (e.g., a species, a definition, a NEXUS file, etc.). The application's responsibility is to create a FlowComponent object that will display the entity and allow the user to edit it (Update).

A DemandEntityEvent comes with a class of persistent object (e.g., Species, Definition, Nexus, etc.). The application's responsibility, then, is to provide the "demander" with an object of that class, either by making a new one (Create) or by selecting an existing one (Retrieve).

(Note that I haven't mentioned Delete. I'm leaving that out of the first version. Any deletions will have to be made by myself, manually.)

One problem with my old approach was that I had different user interface components for each combination of CRU[D] action and persistent object. Take a couple dozen types of persistent object and multiply by three: that's how many components I had to make.

The new system, though, has just two types of FlowComponent: one for handling EditEntityEvent requests and the other for handling DemandEntityEvent requests.

The first one is the simplest: EntityEditor. It basically consists of the following components:
  • a form which shows all data in the persistent entity;
  • a bit of text that summarizes the entity in its current state (e.g., for a Binomen entity, something like "Prenomen nomen Author 1970");
  • a button that updates the entity in the database, or, if it is a new entity, creates it in the database; and
  • a "cancel" button.
The form has to be done individually for each type of persistent entity, but the rest is completely generic.

The other FlowComponent object, EntitySupplier, actually uses an EntityEditor object. Once the EntitySupplier object is given the class of entity that it's supposed to supply, it creates a default instance of that class and gives it to an EntityEditor, which it displays to the left. To the right, it displays a SuggestionBox, a grid showing various existing entities that might match what's in the editor.

The SuggestionBox uses a nonvisual object called an EntitySuggester, whose job is to watch for changes that the user makes to the persistent entity and come up with relevant, existing suggestions. For example, suppose the user is editing a new Publication object and starts to type in the authorship as "Linn". The PublicationSuggester sees this and checks the database for possible matches, coming up with "Linnaeus 1758". If this is the publication that the user wanted, then they can select it from the SuggestionBox without having to fill in any more information.

I still have to create a form and a suggester for every type of persistent entity, but this is a much neater division of labor than I had in the last version, where forms generated their own suggestions (which would be wasteful if you were just editing an existing entity).

Hope to get much done this weekend.
(Have to get much done this weekend....)

03 March 2008

Names on NEXUS: Under the Hood

I nearly have the basic data model and data processing functions pinned down for Names on NEXUS. Once again, that's my project, hinted at in a paper of mine (Keesey 2007), to relate the data in NEXUS files (Maddison et al. 1997) to definitions of names as governed by the PhyloCode.

I've had to learn some new technologies and code packages to accomplish this. Here's a rundown of some key ones:

BioJava
This is the most recent addition. Originally I had built my own library in ActionScript 3.0 to parse NEXUS files. But it had some limitations. NEXUS is a rather old format (as bioinformatics formats go), and different applications produce somewhat different versions. So rather than use my own ad hoc library, I decided I should get an open source one.

There aren't any in ActionScript, of course, but there are some in Java. This meant I had to switch NEXUS parsing from the front end to the back end, but in some ways that's better. It means I can stored parsed data in the database instead of having client application parse NEXUS data every time. In fact, it means that the client never has to actually see raw NEXUS data—it can just fetch the pre-parsed data.

I first looked into using the NEXUS-parsing code in Mesquite, an open-source phylogenetic analysis program. But it's not set up for simply using the parsing engine on its own—the parser is tied into a whole file-browsing package. Then I found BioJava, which had exactly what I needed. Just looka this package!

Unfortunately there are still some problems with opening certain NEXUS files. I downloaded some samples from TreeBASE and they flagged errors in the TREES section. The reason, as I found after hours of searching and considering whether it might be better just to write my own parser after all, turns out to be an extra comma in the TRANSLATE section. Still not exactly sure how I'm going to solve that one. But it works when I remove the comma!

Hibernate
Remember how I wrote a post a while ago about building classes that map from the Java back-end to the database? Turns out that was all unnecessary. Hibernate is a persistence layer that provides pretty seamless integration between Java and a database (in this case, a PostgreSQL database). Augmented by Hibernate Annotations and Hibernate Validator, it makes it fairly easy to set up and use a complex, well-organized database.

Well, okay, there's a bit of a learning curve first, but it's totally worth it. Incidentally, the book I used to learn it has what is possibly the best title ever.

Flex Data Management Services
Basically, Hibernate is to Java and databases as mx.data is to Flex and Java. It provides a persistence layer so that I don't have to keep track of whether or not I need to request certain data from the Java back-end. I just create DataService objects, tie them to Assembler classes on the back end, and it's all taken care of.

FlexUnit and JUnit
I've already extolled the virtues of unit testing. These wonderful (and, yes, comically-named) packages (huh huh) make it possible. I haven't built enough unit tests, really, but the few I have have been enormously useful in hunting down peculiar errors. And aside from that, since Eclipse can run JUnit tests natively, I can even use them to perform certain important tasks, such as setting up the database from annotated classes via Hibernate.

So What's Left For Me To Do?
Plenty. Although these premade packages help out enormously, I've still had to build an entire mathematics library, a MathML parser, and some tools for handling URIs. I've still got tons of work left to do on the user interface. (Event bubbling is helping a lot with that, by the way.) And, even when stuff is already built, just hooking up one pipe to another pipe can be more complicated than it seems.

Here's a rough list of what's left:
  • Finalize the servlet for uploading and parsing NEXUS data. (I'm very close on this one.)
  • Finish the required behind-the-scenes "search" features. Some of these might be a bit involved, like the ones that suggest possible links between NEXUS taxa and species or specimens or between NEXUS character states and apomorphies.
  • Overhaul the way Names on NEXUS entities (particularly specifiers) are referenced in MathML.
  • Finish the user interface. So far I just have a few forms. I still have to do tree visualization, stylesheets, high-level navigation, transitions, etc.
  • Constrain access to certain functionality. Names on NEXUS is going to be a pretty open, collaborative tool, but I need to set a few boundaries. (E.g., I can't have any old person delete data.)
  • Make sure the server's all optimized, with a static, JNDI-named Hibernate factory, etc.

And here are some things that aren't, strictly speaking, essential, but would be awfully nice:
  • Create a servlet to provide permanent links for Names on NEXUS entities.
  • Create unit tests for all relevant classes.
  • Add JavaDoc and ASDoc comments to all code.

Part of me is also thinking about renaming the project. I mean, it's a good name for what it does right now, but what if I start to bring formats other than NEXUS into the fold? (Not that there are many, but....) Well, I'll probably cross that bridge when I come to it.

My goal is to get an alpha version online sometime this Spring and go open source with it by the Fall. We'll see....

06 November 2007

The Wonderful Process of Refactoring

In many ways, programming is as much an art as a science. Good code is like good writing: crisp, clear, streamlined, and well-structured. But it's not always possible to foresee the best structure. When working on a project, you just do what seems best at the time. Eventually you'll finish it and work out the bugs, but the underlying code may not be elegant. There may be duplicated code, unclear functionality, unoptimized sections, and sections that do too much with sections that do too little. When you come back to the code to add or refine features, you often face a major headache.

This is why many developers use a general process called refactoring. Refactoring means that you change the design of the code without changing the functionality. An analogy would be rewriting a news article so that it reads better without changing what the article is actually about.

Ensuring that the functionality remains the same is not always easy. It involves frequent testing and retesting. Changing one section might affect another section, so the retesting has to be thorough. Testing a particular section during development is called unit testing.

Sometimes it's worth the extra time to create a program that actually does the testing for you. During heavy refactoring, this can save oodles of time. If you make changes, then you can just run a test that takes a few seconds, quickly showing whether you broke anything (and generally showing exactly what you broke).

I went through this process recently while working on March of Man.

The Setup

Typically a rich Internet application (RIA) has three tiers. The client-side application is something you view in a browser (for example, this website). The database is an application on the server that stores data. The server-side application facilitates communication between the database (and potentially other server-side information) and the client-side. To sum up:

user ↔ client-side app ↔ server-side app ↔ database

In case you were curious, for March of Man, the client-side application is an interactive Flash movie made with Flex Builder, the server-side application uses Java running on JBoss, and the database is PostgreSQL.

All this communication between tiers can get a bit complicated. Fortunately, Adobe provides an excellent tool called LiveCycle Data Services (LCDS) for communicating between the client- and server-side applications. What exactly does it do? Well, March of Man deals with certain types of data: user accounts, images, hominin taxa, location/time ranges, etc. In programming, these types are referred to as classes, and classes are instantiated (i.e., realized) as objects. Specifically, these ones would be considered value objects. So, for example, there is a class called Account that specifies certain types of data, like email address, personal name, password, etc. An instance of Account (i.e., an Account object) might represent a particular person. For the client-side, the Account class is written in ActionScript. For the server-side, it's written in Java. What LiveCycle does is take care of all of the translation: when I send an ActionScript Account object to the server, it is automatically recreated as a Java Account object, and vice versa when the server reports to the client.

As an example, when you log in, the client collects your email address and password, and sends them (encrypted) to the server, specifying a login method. That method looks up the information for the specified account, packages it as an Account object, and sends it back to the client. Voilà, you are logged in.

Unfortunately, there isn't a similarly easy tool for translating from the server-side application to the database, where data is stored in tables as rows and columns. The only way to interface is using SQL, a common language used for database queries. Java provides some ways to facilitate this (the java.sql package of classes), but it can't do everything.

So I created a package of classes called org.marchofman.mappers to map the information from Java to PostgreSQL and back. Each mapper class corresponds to a type of value object, e.g. AccountMapper handles Account objects, ImageMapper handles Image objects, etc. They all have certain functionality in common, such as what are called the "CRUD" commands: create, retrieve, update, and delete. When you register, the Java service uses the AccountMapper.create method. When you log in, it uses AccountMapper.retrieve. When you change your information, it uses AccountMapper.update. And, should you ever decide you don't want an account any more (heavens forfend), it would use AccountMapper.delete.

The Problem

For the initial version of the site (still up as of this writing, but soon to be upgraded), the "mapper" classes did their job. But all was not well under the hood. Since there were many different ways to select data, I sometimes had to make multiple mapper classes for the same type of value object. For example, a user might want to pull up information on a particular image, on their own images, on the images corresponding to a particular taxon, or just on all images. Each of these required a completely different method, and I had to struggle to keep the levels of duplicated code down. Duplicated code might sound like a minor annoyance, but it can have serious effects. It really complicates both testing and updating code.

Eventually I realized that the mapper classes should not be concerned with how to decide which objects to handle, only with how to handle them once they were specified. I created a new type of object, called a Qualifier, to store information on how to specify certain objects. As an example, a TotalQualifier object specifies all value objects of a given type. A FieldQualifier object specifies all value objects with a certain piece of data, for example, all accounts with a given email address. A KeyQualifier specifies one particular object by a unique identifier. And a ConjunctionQualifier qroups other qualifiers and selects only those objects which satisfy all of those qualifiers.

Formerly I had to write methods like retrieveForOwner, retrieveAll, retrieve, retrieveForTaxon, retrieveForLogin, etc. With the new structure, I could just have one method, retrieve, and pass it any kind of Qualifier object I wanted. The number of classes in the org.marchofman.mappers package shrunk down to a fraction of what it had been. Now it was down to the ideal: one mapper class for each type of value object.

Doing all this was a fair bit of work. Fortunately, I had already built automated unit tests using the excellent flexunit framework. Once I finished my initial refactoring work, I just ran the tests and could instantly see what I still needed to fix.

The Payoff

Now not only do I have a more streamlined and flexible server-side architecture, but also some generalized mapping classes which may greatly facilitate work on future projects. Is the code perfect? No, but it's much better than it was. And I can always refactor again if I need to....