May 14 2009

Simple Object Assembler release update

rob

There have been a couple of releases of the Simple Object Assembler in quick succession over the last week. These changes are all in aid of simplifying it’s use and can dramatically reduce the number of converters required in some projects due to enhancements to the auto-mapping capabilities.  One project i’m using it on saw the converters drop from 34 to 14!

A summary of the release notes from the last week are below:

0.4.1

This is a small api change release that simplifies a couple of commonly used methods. It will require changes to any existing converters. Please see upgrade notes below.

Upgrade notes:

  1. Change all converters that implement the convert(...) method to return void instead of the destination object.
  2. Change all converters that implement the alwaysIgnoreProperties() method to return an IgnoreSet.

0.4.0


Mar 28 2009

Simple Object Assembler

rob

Going back a few years, it took a little while for me to realise the benefits of providing a clean separation of the core business domain from any consuming client, be it a web ui or remote service. I guess I always saw it as a lot of work (which it can be) and the simpler apps I was writing didn’t seem to need it – or at least I couldn’t really see the benefit at the time. While exposing dtos to the outside world is not necessarily a better option in all cases (many modern app frameworks offer rapid development because they don’t do this), i’ve found that erring on the side of caution in most of my development has set me up for a better experience as an application grows, allowing me to develop my domain without worry about how it might be used externally. This approach also has a nice side effect of allowing me to develop from the outside in, giving me exactly what I want in a web client and exactly what I want in my domain… often these are a little different.

I’ve used a few different techniques for converting domain objects to dtos and back again and it’s always turned out to be an ugly part of the application. I gave Dozer a go for a while and while it’s very capable, I found that once you have nested object graphs that need selective conversion for different scenarios, it became more and more difficult to follow, with more chance of things happening that you don’t want. Also having the mapping rules defined in xml, there were too many places to check for any different scenario. It’s been a while since I used Dozer and it’s quite possible that any statements I make about the way it works may be wrong so i’ll just say that it had some things a while back that lead me away from it and I haven’t gone back for another look since. So after trying Dozer out and not being entirely happy, I went back to having a pair of domain / dto assemblers that had dedicated methods for converting one object to another.

For example:

public class DomainAssembler {

public User assembleNewUser(NewUserRequest newUserRequest) {
//… manually convert objects here.
}

}

As you can imagine, this class will get large very quickly so you’ll want to break it down. Depending on the application, it might not be clear where or how much and what the deciding factor is. If you’re using course grained services, maybe separating assemblers on a per-service basis is the way to go. The thing with that is that you end up with lots of inter-dependencies between service based assemblers that is also hard to manage. The worst thing is that you also end up with lots of special methods for converting different parts of a graph. Clear naming is something I always find difficult, probably because I place a lot of importance on it.

So for example, this assembler has a three different methods for returning a different view of a user:

public class DtoAssembler {

public UserDto assemblerBasicUser(User user) {
//… manually convert objects here.
}

public UserDto assemblerUserWithProjects(User user) {
//… manually convert objects here.
}

public UserDto assemblerUserWithProjectsAndTasks(User user) {
//… manually convert objects here.
}

}

Depending on your application, this may or may not be a problem but you can see where it’s going. Anyway, this all bothered me a bit so I started writing something that has helped to simplify things for me a little on a couple of apps. There were a few basic requirements that I wanted to be able to satisfy and some assumptions about the environment that it would exist within. These included:

  • Runs in a Spring 2.5.x based application and it’s ok to have a dependency on this but try to avoid if possible.
  • Likely to be a Hibernate / JPA based application but again don’t tie to the Hibernate API if at all possible.
  • Utilise Convention Over Configuration where possible but don’t impose it.
    • Fields of same names are mapped automatically
    • Fields of same name but different types can be mapped by registering converters for the combination. It’s recursive.
    • Fields of different names can be mapped by explicit configuration
    • Fields to be ignored can be configured
    • Any details about how much of an object graph should be populated must be configurable at runtime
  • Can be completely overridden if necessary or at different stages in the conversion cycle.
  • Configuration information is not defined in xml and is as close to the code that uses it as possible. This being a differentiator from Dozer.
  • Should be able to detect problems in configuration such as mistyped property names.
  • Destination objects should be able to be constructed in any way required but if not defined should be done reflectively.
  • Should be capable of handling conversion of objects proxied by hibernate or JPA.
  • Can handle mapping of collections, using existing objects within the destination collection when it makes sense for integrity or performance reasons.
  • Can handle circular relationships
  • Should use direct field access to negate the need for getters and setters simply for object conversion

Object Assembly / Conversion

The object assembler is the entry point to object conversion. It maintains a registry of ObjectConverters that are written by the application developer to convert one object to another. The registry enables a single converter to exist for one type to another.

You generally call the object assembler like this:

DestinationObject destinationObject objectAssembler.assemble(sourceObject, DestinationClass.class);

This will look up it’s registry for a converter with the same source / destination combination. A converter in it’s simplest form might look something like this:

public class SourceToDestinationTestObjectConverter extends    AbstractObjectConverter<SourceObject, DestinationObject> {

public Class<DestinationObject> getDestinationObjectClass() {
return DestinationObject.class;
}

public Class<SourceObject> getSourceObjectClass() {
return SourceObject.class;
}

}

You’ll notice that this converter extends the AbstractObjectConverter which has most of the conversion logic. The end result of calling this converter via the assembler as shown above is that all properties of the same name will be converted from the source to destination object. If two properties with the same name but different types are encountered, the registry will be consulted and the assembler will recursively call itself until all properties are converted.


Selective Conversion

If you want to exclude any part of the object graph from automatically being converted, you can do so in three ways.

  1. By overriding the ‘disableAutoMapping()’ method on your converter implementation and returning ‘true’
  2. By overriding the ‘alwaysIgnoreProperties()’ method on your converter and returning a Set of field names to never convert
  3. By passing an array or var-args of property names to ignore into the assembler

The first two approaches are useful when you want this behaviour for every invocation of the converter but if you want to be able to specify what to convert upon invocation, the third approach is much more flexible.

Example:

DestinationObject destinationObject objectAssembler.assemble(sourceObject, DestinationClass.class, “ignoreFieldA”, “ignoreFieldB”);

The ignore field paths are aware of nested object graphs so it’s possible to ignore any part of the graph, no matter how deep.

DestinationObject destinationObject objectAssembler.assemble(sourceObject, DestinationClass.class, “ignoreCollection.fieldA”, “ignoreFieldB.fieldValue”);

This approach is very similar to web field binding except that collections are not indexed. This means that while you can refer to a field of an object within a collection, it will always apply to all objects in the collection, not a single one. For example you cannot specify ‘ignoreCollection[0].fieldName’ to ignore only the ‘fieldName’ property of the first element in the ignoreCollection. I can’t think of a situation where this would be that useful. Having said that, adding this capability would be reasonably straight forward.

Wildcard Exclusions

There are times when you want to convert an object that is a property of another but not actually convert any of it’s properties. Say for example, you want to look up a staff member’s manager from the database and populate the manager field in the staff member but you don’t want to actually map any of the ManagerDto fields onto the Manager object from the database. In this case you can use a wildcard in your ignorePath.

StaffMember staffMember objectAssembler.assemble(staffMemberDto, StaffMember.class, “manager.*”);

Mapping fields with different names

Sometimes your source and destination objects don’t have the same names. In this case, it’s possible to define pairs of ’source > destination’ names for mapping. This is done at the converter level by overriding the ‘customConverterFieldMappings()’ method. This method should return a Set of ConverterFieldMappings.

Configuration Validation

It’s easy to mistype a property name so you’ll want some checking to make sure your property names actually exist. Due to the way converters are registered, this can’t be done at startup time yet (hopefully it will soon). It can be done on first execution though so it’s easy to pick this up as part of your test suite. Any fields marked to ignore or fields mapped that don’t exist will result in an exception with a clear indication as to the source of the problem.

Constructing your own Destination Object

The default behaviour is for the converter to attempt to construct a destination object based on it’s destination type. This requires your destination object to have a default no-arg constructor. If you want to be able to construct your own object you can do this by overriding the createDestinationObject(SourceObject sourceObject) method. Because the sourceObject is passed into this method it’s entirely possible to manually do some conversion in here but it’s not the intended place and it’s possible that your values will be overridden during the actual conversion which happens next so it’s not advised. There is a method for this which i’ll come to. This is however a good place to do things like retrieve an object from the database based on the id of the source object. This is certainly the most common case.

Example of a Converter with the createDestinationObject method being used.

@Component
public class UserDtoToUserConverter extends AbstractObjectConverter<UserDto, User> {

private UserDao userDao;

@Autowired
public UserDtoToUserConverter(UserDao userDao) {
super();
this.userDao = userDao;
}

@Override
public User createDestinationObject(UserDto userDto) {
if (userDto.getId() == null) {
return new User();
} else {
return userDao.findById(userDto.getId());
}
}

public Class<UserDto> getSourceObjectClass() {
return UserDto.class;
}

public Class<User> getDestinationObjectClass() {
return User.class;
}

}


Custom Conversion Logic

There are cases where you need to perform some custom conversion logic because it’s not simple enough to just map one field to another. In this case you can override the convert(SourceObject sourceObject, DestinationObject destinationObject) method. This is called after all auto conversion has been performed and enables any custom conversion to be done. If you wanted to perform a full custom conversion for a converter you simply override disableAutoMapping() to return true and write your custom logic here.

Conclusion

This is not a perfect solution and it certainly won’t make sense in all situations. Some people, justifiably will take issue with the idea of passing strings of property fields to ignore / exclude into the assembler. I find this a bit awkward myself however i’m not sure that it’s really all that different to putting it outside in an xml file. I feel this line is starting to be crossed with some implementations of annotations vs their xml configuration approaches. Sure, it’s compiled code, but it’s also a lot more visible at the point at which it matters so there are some clarity benefits there. It would also be possible to externalise this configuration as an option but the more that’s done, the closer we get to Dozer which undeniably has a much deeper feature set and is almost certainly much ’smarter’ under the hood with the benefit of years in the wild. The intention for this was a simple solution to a simple mapping problem, sitting halfway between dozer and a fully handwritten approach. I came across an interesting post the other day by Gavin King, talking about the new typesafe Criteria API for JPA 2.0. I’ve been pondering whether one of the features they are using from the Java 6 compiler plugin architecture might be useful here although introducing yet another annotation could become a bit of a bore!

Another potential problem comes about when new properties are introduced to objects that are already mapped. It’s possible that an introduced relationship with another large part of the object graph could go unnoticed and result in conversion of much larger sets of objects than originally intended. I guess this is a trade off of taking blacklist vs whitelist approach for property mapping.

Get the Code

I’ve published the code on google code. At this point it’s not packaged up so you’ll have to check it out and build it from the source.  It’s a maven project though so this will be trivial as long as you’re familiar with maven.