Extreme Code Makeover: PetClinic Application

Introduction

This PetClinic application is built based on the original PetClinic sample application from SpringSource. The requirements of this application are documented in the tutorial that is bundled with the original Spring's PetClinic sample. We implemented all requirements listed in that document.

Besides significant reduction in the amount of code, this implementation uses eager loading extensively which improves performance as compared with Spring's original implementation.

Analysis

PetClinic is a fairly simple application with only seven models. The following chart shows all the models and relationships among them.



We can easily translate the relationships into model code.

For example, we can have the following for models vet, vet_specialty and specialty:

public class Vet extends ActiveRecord {
    public void registerRelations() {
        hasMany("vet_specialties");
        hasManyThrough("specialties", "vet_specialties");
    }
}

public class VetSpecialty extends ActiveRecord {
    public void registerRelations() {
        belongsTo("vet");
        belongsTo("specialty");
    }
}

public class Specialty extends ActiveRecord {
    public void registerRelations() {
        hasMany("vet_specialties");
    }
}

There is no need to specify foreign keys here as Scooter can detect the foreign key through naming convention.

Scooter uses routes to manage actions. vets, owners, pets and visits are treated as resources. And their routes are automatically generated based on their resource definitions in routes.properties file.

By default, restful style routes are created by Scooter. The following table shows routes created for resource pets:

HTTP verbURLcontrolleractionused for
GET /pets pets index display a list of all pets
GET /pets/add pets add return an HTML form for creating a new pet
POST /pets pets create create a new pet
GET /pets/1 pets show display a specific pet
GET /pets/1/edit pets edit return an HTML form for editing a pet
PUT /pets/1 pets update update a specific pet
DELETE /pets/1 pets delete delete a specific pet

In this application, deleting a pet is not listed as a requirement. Therefore we remove it from the routes by using key except as follows:

resources.name.pets=\
    controller:pets; except:[delete]

Implementation

We follow the following steps to implement the site. These steps are true for almost all applications built by Scooter.

  1. Enter scooter dir: cd scooter
  2. Create app: java -jar tools/create.jar petclinic mysql
  3. Start up web server: java -jar tools/server.jar petclinic 8080
  4. Browse app website: http://localhost:8080/petclinic
  5. Generate code, or edit code
  6. Refresh browser
  7. Repeat the above two steps until the whole app is done
Petclinic Home Page

Scooter's PetClinic implementation takes advantages of many capabilities of Scooter, such as code generation, eager loading, routes, etc.

For example, you can immediately see a list of vets after just the following step:

prompt>java -jar tools/generate.jar petclinic scaffold vet

Then you can see a list of vets by hitting url http://localhost:8080/petclinic/vets

Petclinic All Vets Page

Unlike the original implementation by Spring, eager loading is used as much as possible. This is achieved by using key include. For example, when displaying an owner view, the owner information and all his/her pets and their respective visits are displayed by only one database sql query.

//OwnersController
public String show() {
    ActiveRecord owner = Owner.where("owners.id=" + p("id")).includes("pets=>visits, pets=>type").getRecord();
    if (owner == null) {
        flash("notice", "There is no owner record with primary key as " + p("id"));
    }
    else {
        setViewData("owner", owner);
    }
    return null;
}

//sql generated by Scooter
SELECT OWNERS.ID AS OWNERS_ID, OWNERS.FIRST_NAME AS OWNERS_FIRST_NAME,
       OWNERS.LAST_NAME AS OWNERS_LAST_NAME, OWNERS.ADDRESS AS OWNERS_ADDRESS,
       OWNERS.CITY AS OWNERS_CITY, OWNERS.TELEPHONE AS OWNERS_TELEPHONE,
       PETS.ID AS PETS_ID, PETS.NAME AS PETS_NAME, PETS.BIRTH_DATE AS PETS_BIRTH_DATE,
       PETS.TYPE_ID AS PETS_TYPE_ID, PETS.OWNER_ID AS PETS_OWNER_ID,
       VISITS.ID AS VISITS_ID, VISITS.PET_ID AS VISITS_PET_ID,
       VISITS.VISIT_DATE AS VISITS_VISIT_DATE, VISITS.DESCRIPTION AS VISITS_DESCRIPTION,
       OWNERS_PETS.ID AS OWNERS_PETS_ID, OWNERS_PETS.NAME AS OWNERS_PETS_NAME,
       OWNERS_PETS.BIRTH_DATE AS OWNERS_PETS_BIRTH_DATE, OWNERS_PETS.TYPE_ID AS OWNERS_PETS_TYPE_ID,
       OWNERS_PETS.OWNER_ID AS OWNERS_PETS_OWNER_ID,
       TYPES.ID AS TYPES_ID, TYPES.NAME AS TYPES_NAME
 FROM OWNERS
      LEFT OUTER JOIN PETS ON OWNERS.ID=PETS.OWNER_ID
      LEFT OUTER JOIN VISITS ON PETS.ID=VISITS.PET_ID
      LEFT OUTER JOIN PETS OWNERS_PETS ON OWNERS.ID=OWNERS_PETS.OWNER_ID
      LEFT OUTER JOIN TYPES ON OWNERS_PETS.TYPE_ID=TYPES.ID
WHERE OWNERS.ID = 4

Of course there are tradeoffs when a query becomes too complicated. This just shows the power of include when doing eager loading.

Petclinic Find Owners Page

Petclinic All Owners Page

Petclinic Owner Details Page

Petclinic Edit Owner Page

Petclinic Add Pet Page

Petclinic Add Visit Page

How many lines of code we have

The amount of code of Scooter's PetClinic application is not large. Here is a summary.

prompt>java -jar tools/codestats.jar webapps/petclinic/WEB-INF/src
   webapps/petclinic/WEB-INF/views/owners webapps/petclinic/WEB-INF/views/pets
   webapps/petclinic/WEB-INF/views/vets webapps/petclinic/WEB-INF/views/visits

-------------------------------------
                code    total   files
-------------------------------------
java            281     535     12
jsp             432     481     10
-------------------------------------
summary         713     1016    22

Considering that there are 17 actions implemented in this app (see routes from index 22 to index 38), it is roughly 15 lines of Java code and 25 lines of JSP code per action.

Other deployment options

Besides using embedded Jetty web server that comes with Scooter, you may also use Apache's Tomcat. You may deploy a war to other web servers too. For more details, please refer to Scooter's deployment document.