Recently I’ve been working on a project using Java and a technology called Gigaspaces. Being back in the Java world has been interesting, but thankfully many of the tools and approaches are similar.
One aspect of our application (Ok, many, but we’ll pick one) is responsible for retrieving and storing information to a database. To make it clearer, let’s say that we are working with user information. Our service is responsible for:
- Finding a user based on their username
- Finding a user based on their unique identifier
- Finding a related user based on a different user
- Creating a user
- Update a user
With the Gigaspaces technology, we are basically using a big Enterprise Service Bus. So, for one of our other services to find a user, they create a UserRequest and populate it with a user object and an action – Create, Request, Update, or FindRelated. This gets mapped to our UserService, which then handles the request and puts the UserRequest back on the Bus with a state of complete. This request then gets passed back to the originator.
So, for us, that basically means we have a UserRequestProcessor which has to expose a method to handle incoming requests. It returns a UserRequest object, and Gigaspaces knows how to map that. In other words, we can write our code to basically ignore Gigaspaces from a developer test perspective.
The basic architecture that we came up with was to have the UserRequestProcessor delegate calls to a UserDAO which would then in turn do the data access via Hibernate mappings. This lets us write both interaction-based tests like:
public void testProcessorCallsRequestMethodOfDaoForRequestAction() {
User user = new User(VALID_USERNAME, VALID_CONTEXT);
UserRequest request = new UserRequest(user, UserAction.REQUEST);
MockUserRequestProcessor processor = new MockUserRequestProcessor();
UserRequest response = processor.processUserRequest(request);
assertTrue(((MockUserRequestDAO)processor.getDataAccess()).requestIdentityCalled);
}
and more standard tests like:
public void testProcessorUpdatesRequestWithReturnedUserIdAndReturnsItForRequestActionWhenFound() {
User user = new User(VALID_USERNAME, VALID_CONTEXT);
UserRequest request = new UserRequest(user, UserAction.REQUEST);
MockUserRequestProcessor processor = new MockUserRequestProcessor();
UserRequest response = processor.processUserRequest(request);
assertEquals(VALID_USER_ID, response.getUser().getUserId());
}
The magic is the getDataAccess() method of the UserRequestProcessor, a getSessionFactory() method of the UserDAO all coupled with Hibernate’s ability for us to override properties.
Let’s look at a simple method. In the UserDAO, there is a method called requestUser which takes in a User object, and returns the User object populate with the correct Username and Context for the given User ID. Thanks to Hibernate, it looks something like:
public User requestUser(User user) {
Session session = getSessionFactory().openSession();
User returnUser = new NullUser();
try {
returnUser = (User)session.get(User.class, user.getUserId());
} finally {
session.close();
}
return returnUser;
}
Now, this is a real live DAO, which wants a real live connection to a database. It is able to do its magic because of a real, live Hibernate mapping file. But I want to be able to drive the development and design of it using tests like:
public void testUserWithValidIdLoadsUsernameAndContext() {
}
How do I do that in a way that doesn’t take 45 seconds for the test suite to run, and allows me to reset the environment? The test actually looks like:
public void testUserWithValidIdLoadsUsernameAndContext() {
UserDAO dao = new InMemoryUserDAO();
User user = new User();
user.setUserId(VALID_USER_ID);
User result = dao.requestUser(user);
assertEquals(VALID_USERNAME, result.getUserName());
assertEquals(VALID_CONTEXT, result.getContext());
}
The magic is in the InMemoryUserDAO class. It is an inner class of my UserDAOTest class and overrides the getSessionFactory to return a connection to an in-memory database. But it does something else important as well, as we’ll see in a second:
protected class InMemoryIdentityDAO extends IdentityDAO {
private SessionFactory factory = null;
protected SessionFactory getSessionFactory() {
if(factory == null) {
factory = new Configuration()
.configure() //pull defaults from hibernate.cfg.xml
.setProperty("hibernate.connection.url", "jdbc:"hsqldb:mem:test_db");
//override other db properties like driver and dialect
.buildSessionFactory();
}
return factory;
}
}
The first thing we do is load the same hibernate.cfg.xml file that our production classes use. This is important because it makes sure that any class mappings or properties we’ve set in there will be the exact same for our test classes. In other solutions I had seen something similar, but they either defined a special configuration file for testing, or used Hibernate’s methods to specify the class mappings in the test classes. Using this method, the only thing we override is the connection to the database, leaving everything else intact.
So, now in my test setup, I can spin up an in-memory database, populate it with the data, and make sure that I can use TDD to build my data access classes. And thanks to Gigaspaces for designing a product where I can build my classes and business logic without having to shove bunches of your code and objects in it.
Good post Cory!
Yes, one of the things we constantly do in GigaSpaces is think about the developer and the development experience. This is usually easier said than done… With the ability to plug in different implementations and by using our cluster aware client side API, you get a real “write onece, scale anywhere” system design and user experience.
-Guy
I thought you were gonna get to dedicate to Ruby now?