Errai: The browser as a platform

Friday, March 16, 2012

Errai Marshalling and the Builder Pattern

My previous post was about the Errai Marshalling framework and its facilities for working with immutable types. In that post, I laid out how to use Errai's @MapsTo annotation to create entity instances via a public constructor or—as is often advantageous—a public static factory method.

But as an entity class grows to accommodate a real-world-use-case, a third approach to creating instances of immutable types becomes more and more appealing: the builder pattern.

If you use the builder pattern exactly as laid out by Joshua Bloch, Errai's marshalling system will be unable to infer a property mapping for the entity's constructor:

import java.util.Date;
import org.jboss.errai.common.client.api.annotations.Portable;

@Portable
public final class OrientationSensorReading {

  private final Date observationTime;
  private final double x;
  private final double y;
  private final double z;

  // ERROR: Errai Marshalling does not know what to do with this constructor
  private OrientationSensorReading(Builder builder) {
    this.observationTime = new Date(builder.observationTime.getTime());
    this.x = builder.x;
    this.y = builder.y;
    this.z = builder.z;
  }

  public Date getObservationTime() { return new Date(observationTime.getTime()); }
  public double getX() { return x; }
  public double getY() { return y; }
  public double getZ() { return z; }

  public static class Builder {
    private Date observationTime = new Date();
    private double x;
    private double y;
    private double z;

    public Builder time(Date time) { this.observationTime = time; return this; }
    public Builder x(double x) { this.x = x; return this; }
    public Builder y(double y) { this.y = y; return this; }
    public Builder z(double z) { this.z = z; return this; }

    public OrientationSensorReading build() {
      return new OrientationSensorReading(this);
    }
  }
}

You could work around this issue by implementing a custom marshaller (these are explained in in the Errai reference guide). But that's a burden: it's extra, external code you have to revisit every time you add or remove a property on your entity type.

A better approach is to adopt a modified version of Bloch's builder pattern. Instead of giving the class a constructor that takes the Builder as its sole parameter, create one big constructor that takes every single property, each of which annotated with a @MapsTo annotation. This constructor does triple duty as the target of Builder.build(), the target of Errai's de-marshaller, and a place to hang directions for Errai's marshaller generator. Now you have the builder pattern, and you haven't given up the convenience of Errai's automatic marshaller generation:

import java.util.Date;
import org.jboss.errai.common.client.api.annotations.Portable;
import org.jboss.errai.marshalling.client.api.annotations.MapsTo;

@Portable
public final class OrientationSensorReading {

  private final Date observationTime;
  private final double x;
  private final double y;
  private final double z;

  public OrientationSensorReading(
      @MapsTo("observationTime") Date observationTime,
      @MapsTo("x") double x,
      @MapsTo("y") double y,
      @MapsTo("z") double z) {
    this.observationTime = new Date(observationTime.getTime());
    this.x = x;
    this.y = y;
    this.z = z;
  }

  public Date getObservationTime() { return new Date(observationTime.getTime()); }
  public double getX() { return x; }
  public double getY() { return y; }
  public double getZ() { return z; }

  public static class Builder {
    private Date observationTime = new Date();
    private double x;
    private double y;
    private double z;

    public Builder time(Date time) { this.observationTime = time; return this; }
    public Builder x(double x) { this.x = x; return this; }
    public Builder y(double y) { this.y = y; return this; }
    public Builder z(double z) { this.z = z; return this; }

    public OrientationSensorReading build() {
      return new OrientationSensorReading(observationTime, x, y, z);
    }
  }
}

Yes, it's a little bit more verbose than Bloch's approach. But the client code using the builder is identical, and the integrity of the immutable type is in no way compromised. And it's still all in the same file, and far less verbose than a handwritten custom marshaller.

Future Work

There's always room for improvement. In particular, I'd like the option of making the entity constructor (the one with the @MapsTo annotations) package private. This would enforce usage of the builder, which is less error prone than the constructor. That's why you went to the trouble of creating a builder in the first place, right? This is in our issue tracker as ERRAI-234.

Additionally, the contract of Errai's @Portable annotation is that all nested classes of a @Portable class are also portable. This means Errai will generate marshallers for your Builder subclasses. Although this is harmless in the above examples, it becomes a problem when your builders have mandatory fields expressed as constructor parameters. We're tracking this issue as ERRAI-233. The workaround for now is to include a public no-args constructor in any such Builders.

1 comment:

  1. Good work! Have this approach working a treat in my app

    - Tombee

    ReplyDelete