Realsolve Logo
Articles
 

Introducing Impala, a dynamic framework for Spring

A great leap forward for Spring application development

By Phil Zoio, May 23, 2008

The Spring Application Platform (Spring AP) and the Spring Dynamic Modules for OSGi (Spring DM) are two projects recently to emerge from SpringSource, the company behind the Spring Framework, to answer the growing need for effective techniques for modularising enterprise applications. In this article, we introduce a third option, the Impala Framework. While both Spring AP and Spring DM are based on OSGi, Impala is also built on the Spring Framework, but is not inherently tied to OSGi. Instead, it's primary focus is on providing the simplest, most practical and most productive way of achieving modularity using existing enterprise Java libraries and tool sets.

The need for modularity

One of the biggest challenges that developers face on a daily basis is the battle against complexity. To a large extent this complexity arises because of the complex nature of requirements that enterprise software must address. The challenge is to eliminate any unnecessary complexity that accumulates as software evolves. One of the principal techniques for achieving this is modularity. By writing applications in a modular way, it is possible to reduce unnecessary coupling between unrelated parts of a complex system, effectively making them much simpler than they would be without modularity.

What is Impala?

Impala is a dynamic module framework based on Spring. With Impala, you can write Spring applications exactly as before, with one major difference. As your application grows, you can break it down into modules. Modules can be organised in a tree-based hierarchy. Apart from the root module, each module has a parent. Also, any module can have child modules.

In addition to giving your application additional layer with which to structure its parts statically, modules also have dynamic behaviour which can be of great use both in production and in particular in development. Modules can be loaded incrementally. In other words, you only need to start up your application with what it needs to begin with. Additional functionality can be added as you go along. Also, existing modules can be reloaded, for example to reflect changes in the module's internal implementation.

Impala is extremely lightweight, and is particularly well suited to test driven development. One of the most impressive things about Impala is how dramatically it speeds up development. You can think of it as offering Ruby on Rails-like productivity without having to depart from a standard Java Spring-based programming model. You can leverage the full benefits of IDE refactoring and debugging support while working at virtual breakneck speed. Impala has a shallow learning curve. It is simple for any Spring user to pick up. Also, migrating an existing Spring-based application to use Impala is a simple task.

Getting going with the sample application

The best way to understand Impala and appreciate it's benefits is through experiencing it for yourself as a developer working on an application. The application we'll be using in this article is the Spring Petclinic application. Petclinic is one of the reference applications that comes packaged with the Spring Framework. It is a simple web-based application with a Hibernate interface to a database backend. The Petclinic database stores information on pets, vets and visits, and has a simple Spring MVC-based interface for viewing and manipulating some of this data. A version of Petclinic is also used as a reference application for Impala.

The Impala Petclinic application is divided into a number of projects, as shown in the diagram below

Petclinic project

Because the Petclinic application is very simple and small, it only contains four modules:

You probably will have noticed a couple of other projects in the workspace beginning with the prefix petclinic. The projects petclinic-repository and petclinic-tests have a special purpose in the Impala project workspace:

Setting up the database

The Impala Petclinic by default uses a MySQL database, so if you don't have MySQL installed, the easiest way to get going is to download and install MySQL. Once you've done so, you will need to create and initialise the Petclinic database.

In the folder petclinic/db, you will find the necessary scripts to get started. First, as root, run createDB.txt, to create the database with the necessary access permissions:

mysql -uroot < createDB.txt
Next, create the tables by running initDB.txt as the petclinic user.
mysql -upetclinic -ppetclinic < initDB.txt
Finally, you will need to add some data, which is contained in populateDB.txt:
mysql -upetclinic -ppetclinic < initDB.txt
We now have the database set up with some data, and ready to go.

Running the application

We now have a basic understanding of how the project is structured at a high level. We now want to see the application in action.

In order to start the application, simply run the StartServer class as a main application. Then point your browser at http://localhost:8080/petclinic-web/. You should see the following screen.

Petclinic project

Congratulations. You're up and running with the Impala Petclinic sample! So what has happened? We've started our Impala application without having run any special build steps. How is this possible? The Petclinic application is a typical Java web app application, following all the conventions typically used in building Servlet-based web applications: Servlets defined in the web.xml file, and additional metadata in a WEB-INF directory. However, we're taking advantage of the ability of the popular Jetty web server to run embedded web applications, so no additional build steps are necessary.

We'll get a little more insight into what is going on when we take a look at the console output.

2008-05-26 14:21:25.919::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
2008-05-26 14:21:25.976::INFO:  jetty-6.1.1
2008-05-26 14:21:26.320:/petclinic-web:INFO:  Initializing Spring root WebApplicationContext
INFO : BaseImpalaContextLoader - Loading bootstrap context from locations [META-INF/impala-bootstrap.xml, 
	META-INF/impala-web-bootstrap.xml, 
	META-INF/impala-jmx-bootstrap.xml, 
	META-INF/impala-web-listener-bootstrap.xml]
INFO : ScheduledModuleChangeMonitor - Starting org.impalaframework.module.monitor.ScheduledModuleChangeMonitorBean 
	with fixed delay of 2 and interval of 10
INFO : LoadTransitionProcessor - Loading definition root-module
INFO : ScheduledModuleChangeMonitor - Monitoring for changes in module root-module: 
	[file [/localDisk/workspaces/impala/petclinic/bin]]
INFO : LoadTransitionProcessor - Loading definition petclinic-hibernate
INFO : ScheduledModuleChangeMonitor - Monitoring for changes in module petclinic-hibernate: 
	[file [/localDisk/workspaces/impala/petclinic-hibernate/bin]]
INFO : LoadTransitionProcessor - Loading definition petclinic-service
INFO : ScheduledModuleChangeMonitor - Monitoring for changes in module petclinic-service: 
	[file [/localDisk/workspaces/impala/petclinic-service/bin]]
INFO : LoadTransitionProcessor - Loading definition petclinic-web
INFO : ScheduledModuleChangeMonitor - Monitoring for changes in module petclinic-web: 
	[file [/localDisk/workspaces/impala/petclinic-web/bin]]
2008-05-26 14:21:28.563:/petclinic-web:INFO:  Initializing Spring FrameworkServlet 'petclinic-web'
INFO : ExternalModuleServlet - FrameworkServlet 'petclinic-web': initialization started
INFO : ExternalModuleServlet - FrameworkServlet 'petclinic-web': initialization completed in 11 ms
2008-05-26 14:21:28.587::INFO:  Started SelectChannelConnector @ 0.0.0.0:8080
DEBUG : ScheduledModuleChangeMonitor - Completed check for modified modules. No modified module contents found
DEBUG : ScheduledModuleChangeMonitor - Completed check for modified modules. No modified module contents found
DEBUG : ScheduledModuleChangeMonitor - Completed check for modified modules. No modified module contents found
DEBUG : ScheduledModuleChangeMonitor - Completed check for modified modules. No modified module contents found
DEBUG : ScheduledModuleChangeMonitor - Completed check for modified modules. No modified module contents found

One of the first things you will notice from this output is that on startup, a Spring application context is created from a number of Impala-related bean definition files (META-INF/impala-bootstrap.xml, etc.). Impala is configured simply as a collection of Spring beans.

Next, the modules are loaded. It's worth pointing out that the root module is always loaded first. The output showing the loading of the root module is below.

INFO : LoadTransitionProcessor - Loading definition root-module
INFO : ScheduledModuleChangeMonitor - Monitoring for changes in module root-module: 
	[file [/localDisk/workspaces/impala/petclinic/bin]]
If we follow the rest of the output, we notice that the rest of the modules are loaded in the following order: petclinic-hibernate, petclinic-service and petclinic-web.

The final thing to notice is that Impala has been set up for autoreloading of modules. If a change in one of the modules is detected, then the module, as well as all of its children, is automatically reloaded. For example, the output

INFO : ScheduledModuleChangeMonitor - Monitoring for changes in module petclinic-service: 
	[file [/localDisk/workspaces/impala/petclinic-service/bin]]
shows us that the petclinic-sevice/bin directory is being monitored for changes. We can verify that this detection mechanism is working correctly by simply modifiying one of the classes in the petclinic-service project. For example, if we make a trivial change in the file HibernateClinic.java (for example, adding some white space and saving the file), we see the following output:
INFO : ScheduledModuleChangeMonitor - Found modified modules: 
	[org.impalaframework.module.monitor.ModuleChangeInfo: petclinic-service]
INFO : WebModuleChangeListener - Processing modified module petclinic-service
INFO : UnloadTransitionProcessor - Unloading module petclinic-service
INFO : LoadTransitionProcessor - Loading definition petclinic-service
INFO : ModuleContributionPostProcessor - Contributing bean clinic from module petclinic-service
INFO : ScheduledModuleChangeMonitor - Monitoring for changes in module petclinic-service: 
	[file [/localDisk/workspaces/impala/petclinic-service/bin]]
It's quite clear from this output that this module has reloaded. Behind the scenes, the following happened:

It's this ability to apply application modifications on the fly in a robust way which greatly accelerates application development with Impala. We'll be taking advantage of this capability as we enhance the Impala Petclinic application in this article. (Of course, it's not mandatory to have autoreloading turned on - for most applications it will be more appropriate to turn off reloading, or to enable it through explicit interactions on the JMX console.)

Enhancing the Sample Application

So far, we've got started with the sample application, and we've seen a glimpse of Impala in action. We're now going to get stuck in and implement a new feature.

One of the pages you can link to from the home page of the Petclinic application is a page which displays a list of vets. This page is shown below.

Vets page

We're going to allow our vets to belong to different surgeries. The user interface will be enhanced to allow the user to change the surgery to which the vet belongs. This little feature will take us through a full vertical slice of the application, right through creating a new database table to adding a new form to the user interface. However, before we make any changes to the application, we need to update our database.

Updating the database

We get things started by adding a new surgeries table to our database, and creating a foreign key reference to this table in the vets table.

CREATE TABLE surgeries (
  id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(80),
  location VARCHAR(255),
  INDEX(name)
);

alter table vets add column surgery_id INT(4) UNSIGNED NOT NULL REFERENCES surgeries(id);

The data scripts also contains a script to delete data, run prior to the execution of database unit tests. This file is called emptyDB.txt, and requires an extra statement:

DELETE FROM surgeries;

We also need to modify our sample data script (populateDB.txt), so that it creates a few surgery entries assigns one of these to each of the vets.

INSERT INTO surgeries VALUES (1, 'Dogs Friend', 'Kings Cross');
INSERT INTO surgeries VALUES (2, 'Cats Comfort', 'Marylebone');
INSERT INTO surgeries VALUES (3, 'Animal Sanctuary', 'Richmond');

INSERT INTO vets VALUES (1, 'James', 'Carter', 1);
INSERT INTO vets VALUES (2, 'Helen', 'Leary', 1);
INSERT INTO vets VALUES (3, 'Linda', 'Douglas', 2);
INSERT INTO vets VALUES (4, 'Rafael', 'Ortega', 2);
INSERT INTO vets VALUES (5, 'Henry', 'Stevens', 3);
INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins', 3);

Okay, not very exciting, but we're now ready to start making changes to the application. Since we haven't made any change in our application, our tests will all still be running. We can verify this by running AllTests.

All tests

Introducing the Interactive Test Runner

When you are writing code in a typical Java project, the chances are you typically follow a well trodden develop/test cycle. You write the interface, or some skeleton code, and about the same time (in some cases before, in some cases after) you write some test code to test the class you are working on, for example using JUnit. Each time you update the code or tests, you rerun the test in your IDE. You look for the green bar in your IDE. If it's red, you fix the code and/or tests, rerun the tests and so on.

All of this works fine when you are working on unit tests without significant system dependencies. However, if you are writing integration tests, or tests for code which interacts with other parts of the system (such as Hibernate data access code), this does not work so well. The problem is that each time you run the test, you need to start a new JVM and load all the third party libraries used. If you are testing a class whose dependencies are Spring wired, an application context needs to be loaded, and this can take quite a while. If your changes between are typically small, as mine are, the overall drain on productivity becomes quickly apparent, and you can end up spending more time waiting for tests to run than writing code.

The cure to this problem is Impala's interactive test runner. Notice in the AllTests output above, the second test class which we ran as part of the JUnit suite. Well, there's another way to run this class. Open HibernateClinicTests, and you will see that the class also has a main method implementation, shown below.

public class HibernateClinicTest extends TestCase implements
		ModuleDefinitionSource {

	public static void main(String[] args) {
		System.setProperty(LocationConstants.ROOT_PROJECTS_PROPERTY, "petclinic");
		InteractiveTestRunner.run(HibernateClinicTest.class);
	}
	
	//test methods omitted
	
	public RootModuleDefinition getModuleDefinition() {

		return new SimpleModuleDefinitionSource("petclinic",
				new String[] { "parent-context.xml" }, new String[] {
						"petclinic-hibernate", "petclinic-service" })
				.getModuleDefinition();
	}
	
}
The main method fires up the InteractiveTestRunner. Note that the input of the InteractiveTestRunner.run() method is an implementation of ModuleDefinitionSource. ModuleDefinitionSource defines a method - getModuleDefinition() - which we can use to specify which modules should be included as part of the integration test. Notice that for our test, we have specified a context location for the root module (parent-context.xml), as well as an array containing the names of the modules to load. In this case, we require the modules petclinic-hibernate and petclinic-service, but because we are not testing any front end functionality in this test, petclinic-web is omitted. If we run HibernateClinicTest as a main method, we get the following output.
Test class set to tests.integration.HibernateClinicTest
INFO : LoadTransitionProcessor - Loading definition root-module
INFO : LoadTransitionProcessor - Loading definition petclinic-hibernate
INFO : ModuleContributionPostProcessor - Contributing bean dataSource from module petclinic-hibernate
INFO : ModuleContributionPostProcessor - Contributing bean sessionFactory from module petclinic-hibernate
INFO : ModuleContributionPostProcessor - Contributing bean sessionFactory from module petclinic-hibernate
INFO : ModuleContributionPostProcessor - Contributing bean transactionManager from module petclinic-hibernate
INFO : LoadTransitionProcessor - Loading definition petclinic-service
INFO : ModuleContributionPostProcessor - Contributing bean clinic from module petclinic-service
Starting inactivity checker with maximum inactivity of 600 seconds
--------------------

Please enter your command text
>
From the ModuleDefinitionSource, the interactive test runner figures out which modules to load, which it then proceeds to load. It's now ready to run some tests. Let's start by checking the list of available commands.
Please enter your command text
>usage
set-class (aliases: sc, class): Loads module definition using supplied test class
change-directory (aliases: cd): Changes working directory
reload (aliases: rel, module): Reloads root module and all child modules
test (aliases: t): Runs test
rerun-test (aliases: rt, rerun): Runs test
exit (aliases: e): Shuts down modules and exits application
usage (aliases: u): Shows usage of commands
The available commands are printed out, together with aliases. I can now run a test.
Please enter your command text
>test

More than one alternative was found.
Please choose option by entering digit corresponding with selection

1 testGetVets
2 testGetPetTypes
3 testFindOwners
4 testLoadOwner
5 testInsertOwner
6 testUpdateOwner
7 testLoadPet
8 testInsertPet
9 testUpdatePet
10 testInsertVisit
>1
Running test testGetVets
.Hibernate: select vet0_.id as id0_, vet0_.first_name as first2_0_, vet0_.last_name as last3_0_ from vets vet0_ order by vet0_.last_name, vet0_.first_name
...
Hibernate: select specialtie0_.vet_id as vet1_1_, specialtie0_.specialty_id as specialty2_1_, specialty1_.id as id2_0_, specialty1_.name as name2_0_ from vet_specialties specialtie0_ left outer join specialties specialty1_ on specialtie0_.specialty_id=specialty1_.id where specialtie0_.vet_id=?

Time: 0.903

OK (1 test)
The test runner figured out the list of available tests, which I chose by specifying a number. The first really feature which becomes really useful is I can modify and rerun the a test without having to restart the application. Suppose I make a trivial change to testGetVets(), by adding assertTrue(1 > 0) to the test method.
	public void testGetVets() {
		//existing method contents omitted
		assertTrue(1 > 0);
	}
If I rerun testGetVets, the test will fail:
Please enter your command text
>rerun
Running test testGetVets
...
F
Time: 0.504
There was 1 failure:
1) testGetVets(tests.integration.HibernateClinicTest)junit.framework.AssertionFailedError
	at tests.integration.HibernateClinicTest.testGetVets(HibernateClinicTest.java:129)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	...

FAILURES!!!
Tests run: 1,  Failures: 1,  Errors: 0

This is already a bit time saver: test changes are picked up automatically, which means I can incrementally enhance/modify my tests while the application is still running. We'll reverse this change, and in the next section we'll do something a bit more useful.

Taking off

We're now ready to change into a new gear and really get things moving. Let's start by updating adding our new Surgery entity:

package org.springframework.samples.petclinic;

public class Surgery extends NamedEntity {

	private String location;

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}
}

Because each of our vets belongs to a surgery, we also need to update the Vet entity.

	private Surgery surgery;

	public Surgery getSurgery() {
		return surgery;
	}

	public void setSurgery(Surgery surgery) {
		this.surgery = surgery;
	}

So we've updated our tables, and made the equivalent change to the domain classes. What remains now is to update the Hibernate mappings, as the modified bits in bold below show:

<class name="org.springframework.samples.petclinic.Vet" table="vets">
	<id name="id" column="id">
		<generator class="increment"/>

	</id>
	<property name="firstName" column="first_name"/>
	<property name="lastName" column="last_name"/>

	<set name="specialtiesInternal" table="vet_specialties">
		<key column="vet_id"/>
		<many-to-many column="specialty_id" class="org.springframework.samples.petclinic.Specialty"/>
	</set>
	<many-to-one name="surgery" column="surgery_id" class="org.springframework.samples.petclinic.Surgery"/>
</class>

<class name="org.springframework.samples.petclinic.Surgery" table="surgeries">
	<id name="id" column="id">
		<generator class="increment"/>
	</id>
	<property name="name" column="name"/>
	<property name="location" column="location"/>
</class>

Now remember that we already added some test data for the surgeries, so we should be able to expose a method to bring this data back as Surgery instances. In so doing, we will also validate our Hibernate mapping. As practitioner's of TDD, we start by adding a test method to HibernateClinicTests

public void testGetSurgeries() {
	Collection surgeries = this.clinic.getSurgeries();
	assertEquals(3, surgeries.size());
}

Of course, this gives us a compile error, which we can easily fix by allowing Eclipse to generate a method stub for us in Clinic.

Quickfix

Again, we'll still have a compile error until we add a stub implementation of this method in HibernateClinic, which Eclipse will also do in a similar way:

public Collection getSurgeries() {
	// TODO Auto-generated method stub
	return null;
}

Now let's see what happens when we rerun our test:

Please enter your command text
>test 

More than one alternative was found.
Please choose option by entering digit corresponding with selection

1 testGetVets
2 testGetPetTypes
...
10 testInsertVisit
11 testGetSurgeries
>11
Running test testGetSurgeries
.E
Time: 0.4
There was 1 error:
1) testGetSurgeries(tests.integration.HibernateClinicTest)java.lang.NoSuchMethodError: 
	org.springframework.samples.petclinic.Clinic.getSurgeries()Ljava/util/Collection;
	at tests.integration.HibernateClinicTest.testGetSurgeries(HibernateClinicTest.java:109)
	...
	at tests.integration.HibernateClinicTest.main(HibernateClinicTest.java:67)

The first thing to notice is that when we ran test our new test appeared in the list of tests to select. I ran testGetSurgeries by selecting 11. But notice the error: a NoSuchMethodError. This is because the Clinic class in the root module has changed. We can remedy this by running the reload command:

Please enter your command text
>reload
INFO : UnloadTransitionProcessor - Unloading module petclinic-hibernate
INFO : UnloadTransitionProcessor - Unloading module petclinic-service
INFO : UnloadTransitionProcessor - Unloading module root-module
INFO : LoadTransitionProcessor - Loading definition root-module
INFO : LoadTransitionProcessor - Loading definition petclinic-hibernate
INFO : LoadTransitionProcessor - Loading definition petclinic-service
Module 'root-module' loaded in 0.25 seconds
Used memory: 3.5MB
Max available memory: 63.6MB

Now if I rerun the test the test, I get the following:

Please enter your command text
>rerun
Running test testGetSurgeries
.E
Time: 0.422
There was 1 error:
1) testGetSurgeries(tests.integration.HibernateClinicTest)java.lang.NullPointerException
	at tests.integration.HibernateClinicTest.testGetSurgeries(HibernateClinicTest.java:110)
	...
	at tests.integration.HibernateClinicTest.main(HibernateClinicTest.java:67)

FAILURES!!!
Tests run: 1,  Failures: 0,  Errors: 1

We're still not there yet, but we're a big step forward. The NoSuchMethodError has gone away, but we're now getting a NullPointer. Of course, when we consider what our implementation of HibernateClinic, this is not surprising. Let's fix this by fleshing out our implementation of HibernateClinic.getSurgeries()

public Collection getSurgeries() throws DataAccessException {
	return getHibernateTemplate().find("from Surgery surgery");
}

Now HibernateClinic is in the petclinic-service module, so before our test will work, we need to reload this module

.
Please enter your command text
>reload service
INFO : UnloadTransitionProcessor - Unloading module petclinic-service
INFO : LoadTransitionProcessor - Loading definition petclinic-service
Module 'petclinic-service' loaded in 0.049 seconds
Used memory: 6.0MB
Max available memory: 63.6MB


Please enter your command text
>rerun
Running test testGetSurgeries
.Hibernate: select surgery0_.id as id17_, surgery0_.name as name17_, surgery0_.location as location17_ from surgeries surgery0_

Time: 0.385

OK (1 test)

Bingo! We how have our test working. So let's reflect on what we've done. At no point did we need to restart our JVM. When we made a change, all we needed to do was to reload the appropriate module. Note that to reload the root module was extremely quick: it only took a quarter of a second. It's this support for fast, dynamic reloading of modules which dramatically increases your productivity when working with Impala.

What's next

Impala also has great support for interactive development of web applications. In our next installment, we will talk about how to use Impala to painlessly develop web applications in a more interactive way, again using the trademark dynamic reloading capability. We'll do this by enhancing our Petclinic example application, adding a new screen which allows us to change the surgery to which our Petclinc vets belong. Stay tuned for this.

Getting the sample application

The best way to get the sample application is to download it directly from subversion. Simply check out all the projects in the http://impala.googlecode.com/svn/trunk/petclinic/ into a clean Eclipse workspace.

This is a working document. If there are any errors, or if you disagree with anything which I have said, or have any suggestions for improvements please email me at philzoio@realsolve.co.uk.