DTO plugin for Grails

  • Tags : rpc, dto, dozer
  • Latest : 0.2.4
  • Last Updated: 16 June 2010
  • Grails version : 1.1 > *
  • Authors : Marc Palmer
5 votes
Dependency :
compile ":dto:0.2.4"

Documentation

Summary

A Grails plugin for generating DTO classes and converting domain instances to those DTOs.

Installation

The usual:
grails install-plugin dto

Description

Manually creating and managing DTO objects for your domain classes is labour intensive and error-prone. This plugin aims to simplify DTOs by automatically generating them and providing a mechanism to easily map domain class instances to DTO instances.

Check out what's changed in the plugin's release notes.

When you install this plugin, you get two features. The first is a new command "generate-dto".

The generate-dto command

grails generate-dto [--all] [--non-recursive] [domain class ...]

The basic behaviour of this command is to generate DTO classes in the src/java directory that have properties matching the corresponding domain classes. For example, if you have a domain class

package org.example

class Post { String content int priority }

the command will create a file src/java/org/example/PostDTO.java that looks like this:
package org.example

public class PostDTO implements grails.plugins.dto.DTO { private static final long serialVersionUID = 1L;

private String content; private int priority;

public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; } }

If you don't provide any parameters to the command, it will prompt you for the name of a domain class. Once you provide that, it generates the corresponding DTO. You can also pass the names of multiple domain classes as separate arguments to the command, like so:
grails generate-dto org.example.Post org.example.security.User org.example.Other
Note than when you provide the name of a domain class, you should include the package. If you don't include it, the command assumes the domain class is in the default package. It makes for more typing than you might expect, but that's the way it works for now.

So what about those two optional arguments?

  • --all : The command will generate DTOs for all your domain classes.
  • --non-recursive : The command will not generate DTOs for domain class relations/associations.
The second option disables the default behaviour, which is for generate-dto to not only create DTOs for the specified domain classes, but also for any related domain classes and any relations of those related domain classes. And so on. For example, let's say you have the following two domain classes:
class Post {
    User user
    String content
}

class User { String username String passwordHash

static hasMany = [ users: User ] }

Running the command
grails generate-dto Post
will result in both a PostDTO.java file and a UserDTO.java one. But if you specify the --non-recursive option, then only PostDTO.java will be created.

As of version 0.2 of the plugin, you can control which packages the DTO classes are created in. For example, say you want all DTOs to go into the same package, org.example.dto . Just pass the target package as an argument to --pkg :

grails generate-dto --all --pkg=org.example.dto
If you have a lot of DTOs, though, you end up flooding that single package. What if you just want to substitute the root of the package, but keep the sub-packages that help differentiate the domain classes? For example, you may have domain classes in org.example.attributes , org.example.posts , and org.example.security , but want the DTOs in org.another.dto.client.attributes , org.another.dto.client.posts , and org.another.dto.client.security . In other words, you want to swap the two root parts of the package with another root. Easily done:
grails generate-dto --all --oldpkg=org.example --newpkg=org.another.dto.client
Voila! Package substitution. This is a particularly important feature for GWT users, because they typically have to put classes used by the client into a "client" sub-package.

Of course, typing such a long command line every time you want to generate some DTOs is a pain, so you can also specify your package transformations in your application's grails-app/conf/BuildConfig.groovy file:

dto.package.transforms = [ "org.example": "org.another.dto.client" ]
You can specify as many transformations as you like. If the generator comes across a class that doesn't fit one of the "from" packages, it falls back to the default behaviour of putting the DTO in the same package as the domain class. If you would rather have a different fallback package, you can use the wildcard:
dto.package.transforms = [ "*": "org.example.dto", "org.example": "org.another.dto.client" ]

That's it for the DTO generation. The next stage involves creating DTO instances from their corresponding domain instances.

Converting domain instances to DTOs

Once you have a domain instance, you can create the corresponding DTOs by one of two methods:

  • domainObj as DTO
  • domainObj.toDTO()
Both will return the root DTO instance. Note that in the first option, the class is grails.plugins.dto.DTO .

These methods work well if the DTO classes have the same package as their corresponding domain classes, but if you have used package substitution during the DTO generation, you'll run into problems. In such cases, you can use a variation of the second method:

import ....UserDTO
    …
    def dto = user.toDTO(UserDTO)
The assumption in the above example is that UserDTO is in a different package than the User domain class, hence the import. So the User instance will explicitly be converted to a UserDTO instance.

Occasionally you may have a collection of domain classes that you want to convert. For example, when you execute a findAllBy query. You could convert each domain instance in the collection one by one, but the DTO plugin gives you a shortcut:

def users = User.findAllByAgeGreaterThan(20)
    def dtos = users.toDTO(UserDTO)
It's important to realise that, in this case, the argument to toDTO() is the class of objects you want in the resulting list, not the class of the list (or other collection). The method is hard-coded to create a TreeSet if the source collection is a SortedSet , a HashSet for a Set , and an ArrayList for all other collection types.

The basic mapping of fields from domain classes to DTOs isn't always appropriate. For example, what if you want to store a relationship's ID in the DTO rather than the relationship itself? In that case, you need to manually modify the generated DTO class and add a Dozer mapping file. The mapping file can be located anywhere and called anything because you specify its (or their) location(s) via this configuration setting:

dto.mapping.files = [ "classpath:dozer-mapping.xml" ]
See how we use a "classpath:" prefix? Any valid Spring resource path can be used. Also take care to put the above setting in Config.groovy not BuildConfig.groovy .