Audit information containing, at least, logged-in user id and timestamp can be very useful information for each persisted object. Customer’s specifications do not usually mention it besides some cases when an entity requires some kind of sophisticated changes history tracking. From my experience having such an information quite often pays off.
Our solution should fulfill several conditions:
- It must not add any additional work to the application developer. So explicit method calls or setting of attributes on business objects is out of question. From the developer’s perspective everything must be completely transparent.
- Audit info must contain the author’s user id. This could be for example a user currently logged in into a portal application, but in case when same logic is called from the shell script can be a persisted unix account login name. Keeping such level of flexibility disqualifies the developing of stored procedures on the database server. Actually I did not find any simple and universal solution, if you know something then I’m open to learn something new.
So what are our possibilities? If our project is developed in java and the ORM solution is Hibernate then one of the possible ways would be implementing the interface
org.hibernate.Interceptor (javadoc). I’ve been using it on the last two projects and it works just fine. Anyway this solution has, from my perspective, several disadvantages.
The first one is binding our service implementations to the Hibernate. The fact is that an ORM tool is usually not changed during the project, but we may be using a different one like the iBatis. Also re usability of once written code must not be forgotten and specially audit is a piece of code which may be used almost everywhere. Already written code can be simply reused.
The second disadvantage is perhaps very specific, but I already faced this problem. Each Session can have configured only one Interceptor and if we need more interceptors to implement logging, security or anything else, then we have to create something similar to the Chained interceptor. It means a container where all your interceptors can be configured and particular events are delegated to them. The described solution introduces another level of complexity and due to a different call order could sometimes miss our needs.
So for a prototype of a project based on Spring, JPA (for now just the Hibernate implementation) and GWT I decided to use a solution via Spring AOP. It’s been used for declarative transactions anyway and also complies with the conditions mentioned above.
So let’s start. We have got a class acting as a super class for our business objects called
. It contains also the following attributes:
and also a super-interface defining our DAOs similar to the following:
save() contains typically code managing object persistence. It means that we need to pass our business object – successor of the
– has attributes
insertedBy set already before the method
save() is called. As already mentioned, we don’t want to set those attributes directly because such code should definitely not go to the DAO layer. Also obtaining current user’s info and setting it to an already created object is not a general solution and may lead us to have soon pretty unreadable code. Luckily we’ve got AOP and its support in Spring. So let’s define an aspect:
An aspect is defied as an usual java class annotated by @AspectJ annotations . If you want to learn more about the Spring AOP implementation, then a good start would be here. To make it short, we can say that Spring AOP uses by default a dynamic proxy to create a proxy object around our implementation – in the example above around the
. The first method called in our example would then be the method
save() of the proxy class which calls – according to the aspect declaration –
save() method of our implementation.
onSaveInDaoLayer() is annotated by a
@Pointcut and defines a place in the code where the advice is going to be executed. Predicate in our example defines the
save() method execution within the package containing all DAO services implementations.
addAudit() is annotated by
@Before, defining executing an advice code execution before join point call (in our example it plays the role of a join point of the method
save() ). Said in other words: method
addAudit() – setting the missing audit information – is executed always before
Our aspect also contains two setters necessary for the dependency injections – services providing the current timestamp and the current user ID based on the context.
Last but not least, we have to add our aspect into the spring configuration together with an @AspectJ autoproxying activation ( requires the aop schema in the configuration file).
And that’s it. Every persisted object now contains the information about an user and the time of creation.