dtonator is a code generator that generates DTOs and mapping code for sending your domain objects over the wire.
It's differentiating feature (from alternatives like dozer) is that it doesn't use any runtime reflection, and instead generates code at build-time to do all of the mappings. This is actually less for performance (which is usually the goal for avoiding reflection, at least historically) and primarily for simplicity. You can look/debug through the generated code to see exactly what's happening in the mapping logic. It's all very straightforward.
It was built for using with GWT-RPC, but it's not coupled to GWT itself.
dtonator artifacts are available at the repo.joist.ws Maven repository, with org com.bizo
and module dtonator
.
dtonator uses a dtonator.yaml
file to configure what it generates.
A sample configuration file looks like:
config:
dtoPackage: com.bizo.dtonator.dtos
domainPackage: com.bizo.dtonator.domain
mapperPackage: com.bizo.dtonator.mapper
EmployeeDto:
domain: Employee
Given this dtonator will generate an EmployeeDto
with all of the primitive properties of Employee
(discovered via reflection) and a Mapper
class that gets/sets the properties. You would use the result like:
// Mapper is generated
Mapper mapper = new Mapper(...);
// dto -> domain
// EmployeeDto is generated
EmployeeDto dto = new EmployeeDto(1, "ee1");
// sets the DTO values back into Employee
Employee ee = mapper.fromDto(dto);
// domain -> dto
EmployeeDto dto = mapper.toDto(ee);
Besides simple mappings where the names and types match, dtonator supports a number of cases that come up when mapping DTOs.
-
Mapping all basic (non-entity/non-list) properties is the default behavior:
FooDto: domain: Foo
-
Map all properties except one (skip
a
, include the rest*
):FooDto: domain: Foo properties: -a, *
-
Map only certain properties (
a
andb
):FooDto: domain: Foo properties: a, b
-
Map extra properties that aren't on the domain object
FooDto: domain: Foo properties: a, newProperty String
For dtonator to get/set the value of this unknown
newProperty
, it generates an interface,FooDtoMapper
, which you must implement to provide thenewProperty
semantics:public interface FooDtoMapper { String getNewProperty(Foo foo); void setNewProperty(Foo foo, String newProperty); }
-
Include a list of child objects:
EmployerDto: domain: Employer properties: employees EmployeeDto: domain: Employee
The usage would look like:
EmployerDto erDto = new EmployerDto( 1l, newArrayList(new EmployeeDto(1l))
By default dtonator will use the
DomainObjectLookup
to look up eachEmployee
object and callemployer.setEmployees(theEmployees)
. -
Aliases properties to different names
EmployeeDto: domain: Employee properties: shortName(longNameOnDomainObject)
-
Generate getters/setters methods:
EmployeeDto: properties: id Long, name String beanMethods: true
The generated
EmployeeDto
will havegetName
andsetName
methods.Bean methods can also be configured globally for all DTOs.
-
With getters/setters, DTOs can also implement interfaces, e.g.:
interface HasName { String getName(); }
EmployeeDto: properties: id Long, name String interfaces: com.foo.HasName beanMethods: true
The generated
EmployeeDto
will implementHasName
.
-
Output directory (defaults to
target/gen-java-src
)config: outputDirectory: target/java
-
Indentation of the generated code (defaults to four space)
config: indent: two-space | tab | four-space
-
Interfaces to be added to all DTOs/enums:
config: commonInterface: java.io.Serializable
dtonator is a pre-compilation code generation, e.g. you should invoke it in your build process before calling javac
.
It can also be setup in IDEs to run "on save".
- In ant, use an "exec" task with
com.bizo.dtonator.Dtonator
as the main class, and the dtonator jar on the classpath - In Eclipse, you can setup an External Tool Builder to invoke the
java
system command withcom.bizo.dtonator.Dtonator
as the main class, and a-cp
argument of the dtonator jar + your config file. You can set it up to run automatically with the "Build Options" tab when configuring the builder. For example, something like:- Location:
${system_path:java}
- Working Directory:
${workspace_loc:/fooproject-web}
- Arguments:
-cp "../fooproject-domain/target/classes:src/main/webapp/WEB-INF/classes:lib/eclipse/*" com.bizo.dtonator.Dtonator
- Location:
- In IntelliJ, you can probably use something like the File Watchers plugin to watch for changes to the input
.class
files or thedtonator.yaml
config file, similar to the Eclipse External Tool Builder setup. - In gradle or Maven, you should be able to translate the ant "exec" task into a respective pre-compilation task/goal/etc.
Here is a gradle snippet that should work:
task dtonator(type: JavaExec) {
classpath sourceSets.main.compileClasspath
main = 'com.bizo.dtonator.Dtonator'
}
compileJava.dependsOn(dtonator)
Because dtonator is a pre-compilation code generator, any information it gains via reflection (e.g. automatically inferring the types of properties on mapped objects) must already be available/compiled in .class
files. This means if you want to generate DTOs for your domain objects, you would need a build flow like:
- Compile your domain objects into
.class
files - Run dtonator with
dtonator.yaml
+ domain object.class
files on the classpath
- This creates various
Mapper.java
/etc. output files
- Compile your webapp/API code + generated
Mapper.java
/etc. together
Depending on your project setup, this might best be achieved by having your domain objects be a separate project (so a separate Maven/gradle/etc.) build than your webapp/API layer.
For Eclipse:
- Install IvyDE
- Ensure Preferences / Ivy / Classpath Container / Resolve dependencies in workspace is checked
- Import the
dtonator/.project
andfeatures/.project
- Run
dtonator-features.launch
to update the output for thedtonator-features
project for testing
For command line:
- A list of objects, but as the id/name
- children:
_.name
for anArrayList<String>
- children:
- Better syntax for read-only properties (
~id
is kind of dumb) - datamapper-style chaining
properties: parentId -> parent.id
, read/write forid
, otherwise read, e.g.parentName
properties: parentIds
, uselookup
to manage
- Move
DomainObjectLookup
to a separatedtonator-runtime
jar