The scope: Write functional tests that could prove that the methods save and delete on an entity work. I'll use an entity called Traveller for this example.
The technology: I chose to use the following frameworks: Spring 3.0.5, JPA 2.0, Hibernate 3.5.3, AspectJ 1.6.9, JUnit 4.8.2, Maven 2.2.1, Eclipse Helios and MySQL 5.x
I'll be omitting things that are not too important. For all the details, please have a look at the whole source code at:
https://github.com/sandromancuso/cqrs-activerecord
Let's start with the test class:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( locations={ "file:src/test/resources/applicationContext-test.xml", "file:src/main/resources/applicationContext-services.xml" }) @TransactionConfiguration(transactionManager = "myTransactionManager", defaultRollback = true) @Transactional public class TravellerActiveRecordIntegrationTest extends BaseTravellerIntegration { @Test public void testTravellerSelfCreation() { assertThereAreNoTravellers(named("John"), from("England")); Traveller traveller = aTraveller().named("John").from("England").build(); traveller.save(); assertThereIsASingleTraveller(named("John"), from("England")); } @Test public void testTravellerEdition() { Traveller traveller = aTraveller().named("John").from("England").build(); traveller.save(); traveller.setName("Sandro"); traveller.setCountry("Brazil"); traveller.save(); assertThereAreNoTravellers(named("John"), from("England")); assertThereIsASingleTraveller(named("Sandro"), from("Brazil")); } @Test public void testDeleteTraveller() { Traveller traveller = aTraveller().named("John").from("England").build(); traveller.save(); traveller.delete(); assertThereAreNoTravellers(named("John"), from("England")); } }
A few things to notice about this test class:
- As this test is meant to insert and delete from the database, I set it to always rollback the transaction after each test, meaning that nothing will be committed to the database permanently. This is important in order to execute the tests multiple times and get the same results.
- The test class extends a base class, that has the methods assertThereAreNoTravellers and assertThereIsASingleTraveller. The advantage of doing that is that you separate what you want to test from how you want to test, making the test class clean and focused on the intention.
- Note that I also used the Builder pattern to build the traveller instance instead of using the setter methods. This is a nice approach in order to make your tests more readable.
The Traveller entity implementation
So now, let's have a look at the Traveller entity implementation.
@Entity @Table(name="traveller") @EqualsAndHashCode(callSuper=false) @Configurable public @Data class Traveller extends BaseEntity { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String name; private String country; }
The Traveller class is a normal JPA entity as you can see by the @Entity, @Table, @Id and @GenereratedValue annotations. To reduce the boiler place code like getters, setters, toString() and hashCode(), I'm using Lombok, that is a small framework that generate all that for us. Just add a @Data annotation.
The real deal here is Traveller's super class BaseEntity (for a lack of inspiration to find better name). Let's have a look:
@Configurable public abstract class BaseEntity { @PersistenceContext protected EntityManager em; public abstract long getId(); public void save() { if (getId() == 0) { this.em.persist(this); } else { this.em.merge(this); } this.em.flush(); } public void delete() { this.em.remove(this); this.em.flush(); } public void setEntityManager(EntityManager em) { this.em = em; } }
The BaseEntity class has quite a few things that can be discussed.
The save() method: I've chosen to have a single method that can either insert or update an entity, based on its id. The problem with this approach is that, firstly, the method has more than one responsibility, making it a bit confusing to understand what it really does. Secondly it relies on the entities id, that needs to be a long, making it a very specific and weak implementation. The advantage is that from the outside (client code), you don't need to worry about the details. Just simple call save() and you are done. If you prefer a more generic and more cohesive implementation, make the getId() method return a generic type and split the save() method into a create() and update() methods. My idea here was just to make it simple to use.
EntityManager dependency: Here is where the biggest problem lies. For it to work well, every time a new instance of a entity is created, either by using new EntityXYZ() by hand or when it is created by a framework (e.g. as a result of a JPA / Hibernate query), we want the entity manager to be injected automatically. The only way I found to make it work is using aspects with AspectJ and Spring.
Configuring AspectJ and Spring
My idea here is not to give a full explanation about the whole AspectJ and Spring integration, mainly because I don't know it very well myself. I'll just give the basic steps to make this example work.
First add @Configurable to the Entity. This will tell that the entity will be managed by Spring. However, Spring is not aware of instances of the entity, in this case, Traveller, being created. This is why we need AspectJ. The only thing we need to do is to add the following line to our Spring context xml file.
<context:load-time-weaver />
This makes AspectJ intercept the creation of beans annotated with @Configurable and tells Spring to inject the dependencies. In order to the load-time weaver (LTW) work, we need to override JVM's class loading so that the first time our entity classes are loaded, AspectJ can kick in, the @Configurable annotation is discovered and all the dependencies are injected. For that we need to pass the following parameter to the JVM:
-javaagent:<path-to-your-maven-repository>/.m2/repository/org/springframework/spring-instrument/3.0.5.RELEASE/spring-instrument-3.0.5.RELEASE.jar
The snippet above is what we must use if using Spring 3.0.x. It works fine inside Eclipse but apparently it has some conflicts with Maven 2.2.1. If you run into any problems, you can use the old version below.
-javaagent:<path-to-your-maven-repository>/.m2/repository/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar
Another thing that is a good idea is to add the aop.xml file to your project, limiting the classes that will be affected by AspectJ. Add the aop.xml to src/main/resources/META-INF folder.
<include within="com.lscc.ddddemo.model.entity.*" /> <include within="com.lscc.ddddemo.model.entity.builder.*" /> <exclude within="*..*CGLIB*" />
NOTE: The exclude clause is important to avoid some conflicts during the integration test. AspectJ sometimes tries to do some magic with the test classes as well causing a few problems and the exclude clause avoids that.
Making the integration test work
I'll be using MySQL, so I'll need a database with the following table there:
CREATE TABLE `traveller` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, `country` varchar(45) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB
As I'm using JPA 2.0, we need a persistence.xml that must be located at src/main/resources/META-INF
com.lscc.ddddemo.model.entity.Traveller <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" /> <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/lscc-ddd" /> <property name="hibernate.connection.username" value="root" /> <property name="hibernate.c3p0.min_size" value="5" /> <property name="hibernate.c3p0.max_size" value="20" /> <property name="hibernate.c3p0.timeout" value="300" /> <property name="hibernate.c3p0.max_statements" value="50" /> <property name="hibernate.c3p0.idle_test_period" value="3000" />
In our Spring configuration, we also need to tell Spring how to create an EntityManager, providing the EntityManager Factory:
<property name="persistenceUnitName" value="testPU" /> <tx:annotation-driven transaction-manager="myTransactionManager" /><property name="entityManagerFactory" ref="entityManagerFactory" />
In order to separate unit tests and integration tests in my Maven project, I've added a different profile for the integration tests in my pom.xml:
with-integration-tests org.apache.maven.plugins maven-surefire-plugin always -javaagent:/Users/sandro.mancuso/.m2/repository/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar 2.5 true integration-tests integration-test test **/common/* **/*IntegrationTest.java
So now, if you want to run the integration tests from the command line, just type:
mvn clean install -Pwith-integration-tests
If running the tests from inside Eclipse, don't forget to add the -javaagent paramenter to the VM.
Conclusion
So now, from anywhere in your application you can do some thing like:
Traveller traveller = new Traveller(); traveller.setName("John"); traveller.setCountry("England"); traveller.save();
The advantage of using the ActiveRecord:
- Code becomes much simpler;
- There is almost no reason for a DAO layer any more;
- Code is more explicit in its intent.
The disadvantages:
- Entities will need to inherit from a base class;
- Entities would have more than one responsibility (against the Single Responsibility Principle);
- Infrastructure layer (EntityManager) would be bleeding into our domain objects.
In my view, I like the simplicity of the ActiveRecord pattern. In the past 10 years we've been designing Java web applications where our entities are anaemic, having state (getters and setters) and no behaviour. So at the end, they are pure data structures. I feel that entities must be empowered and with techniques like that, we can do it, abstracting the persistence layer.
I'm still not convinced that from now on I'll be using ActiveRecord instead of DAOs and POJOs but I'm glad that now there is a viable option. I'll need to try it in a real project, alongside with Command Query Responsibility Segregation (CQRS) pattern to see how I will feel about it. I'm really getting sick of the standard Action/Service/DAO way to develop web applications in Java instead of having a proper domain model.
By the way, to make the whole thing work, I had loads of problems to find the right combination of libraries. Please have a look at my pom.xml file for details. I'll be evolving this code base to try different things so when you look at it, it may not be exactly as it is described here.
https://github.com/sandromancuso/cqrs-activerecord
Interesting links with more technical details:
http://nurkiewicz.blogspot.com/2009/10/ddd-in-spring-made-easy-with-aspectj.html
http://blog.m1key.me/2010/06/integration-testing-your-spring-3-jpa.html
3 comments:
Does Grails solve some of this since you wrote it?
How does Grails manage the bleed-over?
Yes, things moved on since I wrote this post two years ago. I'm not too familiar with Grails but, yes, it does solve this problem. There is another small framework you can use, in case you don't want to use grails called ActiveJdbc. Worth having a look if you want pure java code. http://code.google.com/p/activejdbc/
Post a Comment