Skip to main content

RAD That Ain't Bad: Domain-Driven Development with Trails

June 23, 2005

{cs.r.title}









Contents
What's the Problem?
What's the Solution?
Introducing Trails
Our First Trails Application
How 'Bout Some Code?
Step One ... and We're Done!
How It Works
Relationships
Conclusion
Resources

By now, there is a good chance you have at least heard of
Ruby on Rails. For those
who haven't, Rails is a framework using the Ruby language that
allows one to create database-driven web applications in a fraction
of the time it would normally take. I'm not going to cover Rails in
this article, as Curt Hibbs has already done a masterful job in
" "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html">Rolling
on Rails
." Instead, this article will focus on how we can do
Rails-esque "rapid application development the right way" in
Java.

I first heard of Rails as I was hanging out after a meeting of
my local Java users
group
with my good friend Jim Weirich. Jim is a well-known Ruby nut and all
around good, smart guy. So when he started talking very excitedly
about "this new Ruby web thing I can't remember the name of," I
decided to hang out a little longer and take a look at a video he
wanted to show me. I probably wouldn't have bothered to read a long
article about it, but heck, even I can spare a few minutes to watch
a video. The video was a screen capture of a developer creating a
fully functional database-driven web application in ten minutes.

As a Java developer my first reaction to Rails was sheer,
unabashed envy. Developing a web application in Java, even with
best-of-breed technologies such as Spring, Hibernate, and Tapestry,
is still much more difficult than cranking out a Rails application.
My next reaction was to think about how we can bring some of the
brilliant ideas of Rails to Java. I'm here to say with certainty
that we can, and I've spent the last several months working to make
it possible for any Java developer to do it.

The fruit of this effort is a framework named, unoriginally
enough, "http://trails.dev.java.net/">Trails. Despite the name, Trails is in no way a port of
Rails. Rather, it is a framework designed to bring a similarly
radical productivity increase to J2EE web application
development.

What's the Problem?

The first thing we need to figure out is what makes Java
development with our current technologies and methods more
difficult than we would like. To highlight the problem, let's
imagine we are developing a J2EE web application using Spring and
Hibernate, and that we need to add a new type of domain object
called Person to the application. The precise steps will vary
depending on what web framework we select, but here are the steps we would typically need to perform:

  1. Create Person class.
  2. Create PersonDAO class.
  3. Create Person table in database.
  4. Define PersonDAO in Spring application context XML
    file.
  5. Create Person page or action class.
  6. Add Person pages to web framework XML
    configuration files.
  7. Create personList page to list Person
    instances.
  8. Create personEdit page to edit Person
    instances.

Of course, these steps will vary, depending on our specific
application design and the frameworks we select, but in general
they are representative. I'm hoping that seeing these list of steps
has you thinking "Phew, that's a lot of work!" Can we do
better?

What's the Solution?

What is the real problem here? I'm going to suggest that we need
to stop repeating ourselves. In fact, this point is so important
that it's worth saying again: We need to stop repeating
ourselves.
All we really want is to add a new type of entity to
our system, yet we have at least eight different things we need to do.
What if we could dramatically reduce the number of steps required?
What if we could reduce the number of steps to:

  1. Create Person class?

How could that be possible? Well, let's think about those eight
steps again. I'm going to propose that all of the information we
really need to produce a simple, working application is contained
in the Person class. From it, we can determine:

  • What kind of attributes a person can have.
  • The name of each attribute.
  • The type of each attribute.

Using just this information, we can make enough assumptions to
produce a working application. What if we assume:

  • For each entity, we want screens in our application to perform
    basic operations such as create, retrieve, update, and delete
    (CRUD).
  • We want each entity to be persisted in a database.
  • We want a database table to be created for each entity.
  • We would like screens to manage the relationships between
    different entities.

Of course, these assumptions will not always be correct, but in
many applications they will be. If we had a framework that could
use these assumptions to produce a working application based on our
domain model, we could greatly accelerate development in many
cases. Furthermore, if this framework let us easily override these
assumptions where necessary, we could quickly produce a working
prototype application and "flesh it out" into our final
application.

Introducing Trails

Trails is a domain-driven development (DDD) framework for Java. Its goal is to allow
us to develop J2EE web applications with the fewest redundant
steps. The term "domain-driven development" refers to the process
of developing an application with a rich domain model: in the most
basic example of a Trails application, the domain model will be the
only code we write! Trails uses this domain model as the only
source of information it needs to dynamically create a basic
application. As mentioned above, it makes a lot of assumptions to
be able to do this, and we'll explore how to override those
assumptions in a future installment. But that's getting ahead of
ourselves. First, let's start with a very simple Trails
application.

If you have not already done so, download and unzip Trails. You
will also need the following installed on your system to use
Trails:

  • The Java 5 JDK.
  • Tomcat 5.5 or
    later.
  • Ant 1.6. Note: Be sure to
    have your ANT_HOME system property set correctly.
    Trails will use this property to add a .jar to
    ANT_HOME/lib.

Our First Trails Application

For this article, we will gradually recreate the Recipe
application from Curt Hibbs' RoR article. To create our application,
you need to be in the same directory where you unzipped Trails. In
this directory, do ant create-new-project. You will be
prompted for the following:

  • Base directory
  • Name of project

For the name of the project, enter "recipe." This will build a
project with the following directory structure:

/recipe/ The directory containing your new project. This contains a
build.properties file you will need to customize as
well as a build.xml file. Point your IDE at this
directory.
/recipe/src The directory in which to place your source code. The
compile and build-hibernate-config
targets will start from here.
/recipe/context This contains your web application.
/recipe/context/WEB-INF This directory contains the web.xml file and the Tapestry page
definitions. The hibernate.properties file is also
located in this directory. Editing it will allow you to change your
database configuration.
/recipe/lib This contains all of the .jars Trails depends upon.

Be sure, if you have not already done so, to add a user to
Tomcat with privileges to use the manager application, as the
Trails Ant build will be unable to deploy our application
otherwise. By default, Tomcat does not have such a user, but it's
easy to add one. Edit your conf/tomcat-users.xml file
(relative to where you installed Tomcat). Add a line like this:

[prettify]
<user name="craigmcc" password="secret"
      roles="standard,manager" />
[/prettify]

To complete the setup process, edit the build.properties
file in the directory where you installed Trails. Below is a list
of the properties in this file.

tomcat.home The directory where Tomcat is installed.
tomcat.url The URL to use to connect to your Tomcat server.
manager.username The username to use when connecting the Tomcat manager
application.
manager.password The password to use when connecting the Tomcat manager
application.

How 'Bout Some Code?

Alright, now that setup is out of the way, let's write some
code. If you have been paying attention, you can probably guess
what code we'll develop first. If you said "domain object," give
yourself a pat on the back. Domain objects in Trails are just plain
old Java objects (POJOs). Because Trails uses Hibernate to persist
our domain model into a relational database, we will also need to
add some JSR-220 persistence annotations to tell Hibernate some
extra information it needs. For a first domain object, let's create
a Recipe class, as follows:

package org.trails.recipe;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratorType;
import javax.persistence.Id;

@Entity
public class Recipe
{
    private Integer id;
   
    private String title;
   
    private String description;
   
    private Date date;

    @Id(generate=GeneratorType.AUTO)
    public Integer getId()
    {
        return id;
    }

    public void setId(Integer id)
    {
        this.id = id;
    }

    public String getTitle()
    {
        return title;
    }

    public void setTitle(String title)
    {
        this.title = title;
    }

    public String getDescription()
    {
        return description;
    }

    public void setDescription(String description)
    {
        this.description = description;
    }
   
    public Date getDate()
    {
        return date;
    }

    public void setDate(Date date)
    {
        this.date = date;
    }
   
}

This is a fairly simple JavaBean: we've got properties for
Title, Description, and Date,
and an Id property. We also have two JSR-220
annotations. We have an @Entity annotation that tells
Hibernate that this class is persistent. We also have an
@Id(generate=GeneratorType.AUTO) that tells Hibernate
which property is the identifier property (a "primary key," in
database parlance), and that we want this property to be
automatically generated. Notice that we don't need to explicitly
mark our other properties as persistent. This is because Hibernate,
in conformance to the EJB3 spec, will assume all of our properties are
persistent unless we explicitly mark them as
@Transient.

Step One ... and We're Done!

Believe it or not, we've now developed our first Trails
application. Let's deploy it and see it in action. If it is not
already running, start your Tomcat instance. Next, go into the
directory where you created the project and do

ant
deploy
. This will build our application and deploy it in our
running Tomcat instance. For maximum simplicity, Trails uses HSQL
as a simple in-memory database and lets Hibernate create all the
tables (this is, of course, configurable). To see our application
in action, we simply point your browser at
http://localhost:8080/recipe. For nothing more than
the cost of our simple domain class, Trails gives us a simple
application that will lets us work with recipes.

The default home page of a Trails application will show a list
of all of the domain object types in our application, as seen in
Figure 1.

<br "Application Home Page" />
Figure 1. Application home page

Following the List Recipes link takes us to a page which, if you
can believe it, lists all the recipes. As you can see in Figure 2,
there aren't any yet.

List Recipes
Figure 2. List recipes

Following the New Recipe link takes us to a screen that will
let us create a new recipe, shown in Figure 3. Notice the date
widget provided for us at no extra charge.

Edit Recipe
Figure 3. Edit recipe

Not bad for just coding one class, eh?

How It Works

Some of you already thinking "How is all that code being
generated?" There's a short and simple answer to that question:
It's not. Trails eschews code generation for the simple
reason that code generated is still code to maintain. Rather than
generate code, Trails dynamically creates your application at
runtime. For each domain class, a set of metadata is built up
through a combination of reflection and Hibernate mapping
information. Intelligent web components then use this metadata to
produce a UI.

Sounds simple enough, but as you probably can guess, there's a
lot going on under the covers. Fortunately, Trails has a lot of
help. One of the key goals of Trails is to eliminate unnecessary
code, so it only makes sense that Trails avoids reinventing wheels
wherever possible. In fact, Trails leverages other frameworks to do
a lot of the heavy lifting. Trails uses:

  • Hibernate for persisting domain objects to the RDBMS, as well
    creating the tables.
  • Spring for dependency injection and configuration.
  • Tapestry as the web component application framework.

Relationships

We have an application, but it's not very interesting. What
makes an application interesting is not objects in isolation, but
objects and their relationships. Let's introduce the concept of
categories to our domain model and assert that a
Recipe is in exactly one Category. We'll
start by creating a Category class:

package org.trails.recipe;

import javax.persistence.Entity;
import javax.persistence.GeneratorType;
import javax.persistence.Id;

import org.apache.commons.lang.builder.EqualsBuilder;

@Entity
public class Category
{
    private Integer id;
   
    private String name;

    @Id(generate=GeneratorType.AUTO)
    public Integer getId()
    {
        return id;
    }
    /**
     * @param id The id to set.
     */
    public void setId(Integer id)
    {
        this.id = id;
    }
   
    public String getName()
    {
        return name;
    }
    /**
     * @param name The name to set.
     */
    public void setName(String name)
    {
        this.name = name;
    }
   
    public boolean equals(Object obj)
    {
        return EqualsBuilder.reflectionEquals(this, obj);
    }
   
    public String toString()
    {
       return getName();
    }
}

Like Recipe, this is a basic POJO with two
annotations. Notice that we have overridden two methods from
Object: toString() and isEquals(). These
methods are necessary for Trails to build a web interface for
objects that are related to Category. The
toString() method is necessary to tell Trails how to
display a Category. The isEquals() method
is necessary for Category objects to work properly
when used in a List. We will see how these are important
shortly.

Now that we have created a Category class, we can
add a category property to our Recipe class:

    private Category category;
   
    @ManyToOne
    public Category getCategory()
    {
        return category;
    }
   
    public void setCategory(Category category)
    {
        this.category = category;
    }

Nothing fancy here, just a simple JavaBean property with an
annotation to tell Hibernate what kind of relationship this is.

Now we'll probably need to actually get into the nitty gritty
and start typing some HTML, right? Nope, not yet. Trails will give
us an application that manages the Recipe-Category relationship for free, no grunt coding
required. Don't believe me? Run ant redeploy.

When you visit the initial page, notice the link to List
Categories. Follow this link and click on the New Category link.
Create a couple of categories. Now go back to the Recipe page and
create a new Recipe. You should see now see a Category
field on the Edit Recipe page, as seen in Figure 4.

<br "Assigning a Category to a Recipe" />
Figure 4. Assigning a category to a recipe

Trails will give you, free of charge, a