Monday, March 10, 2014

How to set CreatedBy, CreatedDate, UpdatedBy, UpdatedDate and Version Audit entities for MongoDB documents?

It is a common programming practice to have audit fields associated with each document which helps in determine the basic information like when document was last created or modified and who did it. This information proves to be very crucial piece of information available while troubleshooting a problem.
Stamping each document with these information in different collections becomes tedious and repetitive. This topic covers how can we use Spring framework to auto populate these fields and developers just need to focus on the other important fields within a document.

A little background... 

We will use Spring Data MongoDB in the example to integrate with MongoDB. MongoDB is a scalable, high-performance, Document Oriented NoSQL database. 
A model can be made Auditable by one of the following ways:
1. Using @CreatedBy, @CreatedDate@LastModifiedBy, @LastModifiedDate and @Version annotations defined in package org.springframework.data.annotation.
2. Model implementing org.springframework.data.domain.Auditable interface. Auditable is an interface that defines all required properties for auditing an Entity, it is inherited from Presistable interface.
In this article we will focus on using Annotations for populating the audit fields.


Figure: Class diagram for Audit Entity

Setup

Setup Maven dependencies to latest Spring Data MongoDB. We would also need to add dependency of joda-time to classpath.
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>1.3.3.RELEASE</version>
</dependency>
<dependency>
   <groupId>joda-time</groupId>
   <artifactId>joda-time</artifactId>
   <version>2.3</version>
</dependency>

Get Started
Step 1 : Enable Auditing in Spring configuration

Add <mongo:auditing/> to your spring configuration file. That's all is needed to enable the auditing for MongoDB entities.

Step 2 : Create AuditEntity  as an abstract class 

Refer the class diagram above. Create an abstract class which will contain all the audit fields annotated with their respective annotation. This abstract class will be extended by all the entities and Spring Data will take care of populating these auditing fields for all the entities extending AuditEntity class.
package com.javabydefault.entity;

import java.util.Date;

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.Version;

public abstract class AuditEntity {
 
 @Id
 private String id;
 @CreatedDate
 private Date createdDate;
 @CreatedBy
 private String createdBy;
 @LastModifiedDate
 private Date updatedDate;
 @LastModifiedBy
 private String updatedBy;
 @Version
 private Long version;
 
 /**
  * @return the id
  */
 public String getId() {
  return id;
 }
 /**
  * @param id the id to set
  */
 public void setId(String id) {
  this.id = id;
 }
 /**
  * @return the createdDate
  */
 public Date getCreatedDate() {
  return createdDate;
 }
 /**
  * @param createdDate the createdDate to set
  */
 public void setCreatedDate(Date createdDate) {
  this.createdDate = createdDate;
 }
 /**
  * @return the createdBy
  */
 public String getCreatedBy() {
  return createdBy;
 }
 /**
  * @param createdBy the createdBy to set
  */
 public void setCreatedBy(String createdBy) {
  this.createdBy = createdBy;
 }
 /**
  * @return the updatedDate
  */
 public Date getUpdatedDate() {
  return updatedDate;
 }
 /**
  * @param updatedDate the updatedDate to set
  */
 public void setUpdatedDate(Date updatedDate) {
  this.updatedDate = updatedDate;
 }
 /**
  * @return the updatedBy
  */
 public String getUpdatedBy() {
  return updatedBy;
 }
 /**
  * @param updatedBy the updatedBy to set
  */
 public void setUpdatedBy(String updatedBy) {
  this.updatedBy = updatedBy;
 }
 /**
  * @return the version
  */
 public Long getVersion() {
  return version;
 }
 /**
  * @param version the version to set
  */
 public void setVersion(Long version) {
  this.version = version;
 }
}

Step 3 : Injecting Data and User information for audit fields

For getting the audit time, implementation of interface org.springframework.data.auditing.DateTimeProvider is required. Spring provides a default implementation for current time which is org.springframework.data.auditing.CurrentDateTimeProvider. Usually this will serve the purpose unless there is a requirement for custom datetime provider. If you want to go with the default DateTime implementation, then nothing much is required to be done for Date audit fields.

For getting the User information, we need to implement org.springframework.data.domain.AuditorAware interface. The implementation class must be able to provide current user name may be from Session or Spring SecurityContext or any other way as per your application.

package com.javabydefault.entity;

import org.springframework.data.domain.AuditorAware;

public class UserNameAuditor implements AuditorAware<String> {

 @Override
 public String getCurrentAuditor() {
  return "JavaByDefault"; //The user name needs to be retrieved from your application's SecurityContext
 }

}

Step 4 : Make spring aware about AuditorAware class 

Modify <mongo:auditing> configuration to make spring aware about the AuditorAware implementation class.
<mongo:auditing auditor-aware-ref="entityAuditor"/>
<bean id="entityAuditor" class="com.javabydefault.entity.UserNameAuditor"/>

Step 5 : Write other Entity classes extending AuditEntity 

Now any entity class you write, it must extend AuditEntity abstract class. Your entity class does not need to worry about any of the audit entity fields set in abstract class. When you save any document using MongoTemplate or MongoRepository, all annotated fields gets set automatically.

Final round up...
Spring Data makes it quite convenient to have all the audit fields set automatically leaving entity developers just to focus on the core fields. When a mongo document is saved using MongoTemplate or MongoRepository, the audit fields are set in save and insert operations which is being called when whole entity is being saved. This suffice most of the scenarios. 
MongoTemplate fires BeforeConvert event during save and insert operations which is where Spring sets the audit fields. Besides save and insert operations, it provides many other methods like update*(**) and findAndModify(**) which will update the model, but since BeforeConvert event is not fired audit fields will not be saved in these cases. This scenario can be handled by writing some Aspects that inject the audit fields before invoking other update methods of MongoTemplate. I will cover this in more details in a separate post.

Till then have fun playing with Audit fields. Please drop your comments/suggestions below.

1 comment:

  1. hey can you give solution for methods like update*(**) and findAndModify(**) in which we will update the model i am currently working on some project and i have face same problem as you mention in it. and by the way nice blog. very helpful

    Thank you

    ReplyDelete