Trails


Table of Contents

RAD That Ain't Bad: Domain-Driven Development with Trails 
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 

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

by Chris Nelson
06/23/2005

Contents 

  1. What's the Problem?
  2. What's the Solution?
  3. Introducing Trails
  4. Our First Trails Application
  5. How 'Bout Some Code?
  6. Step One … and We're Done!
  7. How It Works
  8. Relationships
  9. Conclusion
  10. 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 "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, 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:

[... rest omitted ...]

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.

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.

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.

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.

Trails will give you, free of charge, a <select> list of all of the Category objects for you to choose which Category a Recipe belongs in. This is where the toString() and isEquals() methods come into play. The toString() method is used to display the label in the select list, and isEquals() is used to determine which Category was selected.

Conclusion 

Let's take stock of what we've done. We've built a complete (though simple) J2EE application that lets us manage recipes and assign them to categories. The only code we've written has been our domain model, and in return, we have an application that includes a web UI and database persistence. We have solid architecture that builds on proven frameworks such as Spring, Hibernate and Tapestry. And we've built this in just a few minutes.

In the next installment we will explore Trails in greater depth. We will learn how to customize a Trails application to override the assumptions Trails makes. We will also see how Trails also handles relationships more complex than a simple many-to-one. And finally, we'll explore how Trails supports validation by annotating your domain classes.

Resources 

  • Code from this article:

    • For those who want to play along with our contestants at home, a .zip of the source
    • For those who always skip to the answers at the back of the book, the completed .war file ready to deploy
  • Trails
  • Tapestry (see also O'Reilly CodeZoo: Tapestry)
  • Hibernate (see also O'Reilly CodeZoo: Hibernate)
  • Spring (see also O'Reilly CodeZoo: Spring)
  • Ruby on Rails
  • "Rolling on Rails," by Curt Hibbs

Chris Nelson is the founder of the Trails project and has been developing server-side Java applications since 1997.

documented on: 2008-02-09