How to build a Builder

July 11, 2012 | Comments
Tags: Tips Design HowTo Principles


Whenever I start to write out some domain object I always try to remind myself of two important principles:

  1. Avoid anaemic domain objects
  2. Build a Builder

The first principle may come as a surprise to some. After all, there are plenty of examples of JPA or JAXB annotated Java classes that simply consist of a bunch of getters and setters leaving all the complex business logic to Data Access Objects (DAOs).

These are really just Data Transfer Objects (DTOs) rather than domain objects. A proper domain object contains more than just getters and setters, it has meaningful method names tied to appropriate business logic. Sure, it’ll have some annotations to assist with persistence, but on the whole it’ll be a fluent representation of the business domain in which it resides.

The second principle can seem like overkill to many since a domain object is such a trivial thing to create. However as Bloch states in Effective Java:

“…the Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters, especially if most of those parameters are optional.”

So what makes a good Builder?

I have found that applying the following approach leads me to create a clean and consistent Builder interface:

Applying the above gives rise to building code that looks like this:

final User admin = UserBuilder
  .newInstance()
  .withUUID("trent123")
  .withUsername("trent")
  .withPassword("trent1")
  .withContactMethod(ContactMethod.FIRST_NAME, "Trent")
  .withContactMethod(ContactMethod.EMAIL, "admin@example.org")
  .withRole(adminRole)
  .build();

Of course, there is a temptation to use the Builder to create highly complex default starting configurations, such as .withAdministratorRolesAndAuthorities(). I have found that it is best to keep the focus tightly on the underlying domain object, and to use other dedicated Builders for the additional objects.

Show me the code!

In my MultiBit Merchant project, I have a User entity. Here is its Builder. I put it in here in its entirety so that you can simply copy-paste-modify your own versions from this template. It’s quite long…

public class UserBuilder {
  // Store specification state here
  private String openId;
  private String uuid = UUID.randomUUID().toString();
  private String secretKey;
  private String username;
  private String password;
  private Customer customer;

  // Keep a collection of visitors
  private List<AddContactMethod> addContactMethods = Lists.newArrayList();
  private List<AddRole> addRoles = Lists.newArrayList();

  // Prevent repeat builds
  private boolean isBuilt = false;

  public static UserBuilder newInstance() {
    return new UserBuilder();
  }

  public User build() {
    validateState();

    // User is a DTO and so requires a default constructor
    User user = new User();

    user.setOpenId(openId);

    if (uuid == null) {
      throw new IllegalStateException("UUID cannot be null");
    }
    user.setUUID(uuid);

    user.setSecretKey(secretKey);
    user.setUsername(username);

    if (password != null) {
      // Digest the plain password
      String encryptedPassword = new StrongPasswordEncryptor().encryptPassword(password);
      user.setPassword(encryptedPassword);
    }

    // Bi-directional relationship
    if (customer != null) {
      user.setCustomer(customer);
      customer.setUser(user);
    }

    for (AddRole addRole : addRoles) {
      addRole.applyTo(user);
    }

    for (AddContactMethod addContactMethod : addContactMethods) {
      addContactMethod.applyTo(user);
    }

    isBuilt = true;

    return user;
  }

  private void validateState() {
    if (isBuilt) {
      throw new IllegalStateException("The entity has been built");
    }
  }

  public UserBuilder withOpenId(String openId) {
    this.openId = openId;
    return this;
  }

  public UserBuilder withUUID(String uuid) {
    this.uuid = uuid;
    return this;
  }

  public UserBuilder withSecretKey(String secretKey) {
    this.secretKey = secretKey;
    return this;
  }

  public UserBuilder withContactMethod(ContactMethod contactMethod, String detail) {

    addContactMethods.add(new AddContactMethod(contactMethod, detail));

    return this;
  }

  public UserBuilder withRole(Role role) {

    addRoles.add(new AddRole(role));
    return this;
  }

  public UserBuilder withRoles(List<Role> roles) {

    for (Role role : roles) {
      addRoles.add(new AddRole(role));
    }

    return this;
  }

  public UserBuilder withUsername(String username) {
    this.username = username;
    return this;
  }

  public UserBuilder withPassword(String password) {
    this.password = password;
    return this;
  }

  public UserBuilder withCustomer(Customer customer) {
    this.customer = customer;
    return this;
  }

  private class AddContactMethod {
    private final ContactMethod contactMethod;
    private final String detail;

    private AddContactMethod(ContactMethod contactMethod, String detail) {
      this.contactMethod = contactMethod;
      this.detail = detail;
    }

    void applyTo(User user) {
      ContactMethodDetail contactMethodDetail = new ContactMethodDetail();
      contactMethodDetail.setPrimaryDetail(detail);

      user.setContactMethodDetail(contactMethod, contactMethodDetail);

    }
  }

  private class AddRole {
    private final Role role;

    private AddRole(Role role) {
      this.role = role;
    }

    void applyTo(User user) {

      UserRole userRole = new UserRole();

      UserRole.UserRolePk userRolePk = new UserRole.UserRolePk();
      userRolePk.setUser(user);
      userRolePk.setRole(role);

      userRole.setPrimaryKey(userRolePk);

      user.getUserRoles().add(userRole);

    }
  }
}

What’s with the inner classes?

The inner classes (AddContactMethod and AddRole) are part of a Visitor pattern that is used to store and then apply state. This provides a simple, but powerful, technique to configure collections.

Of course, the above is only a snapshot of the current state of the project – it is certain to evolve – but the principles behind it will remain.

Tweet

Related Posts

Useful? Consider a small donation: 1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH

Got an opinion? Comments are welcome!

Want more? Take a look at the following:

How to be agile when all about you are not - part 1
7 refactorings I couldn't live without
MultiBit Merchant: Cheaper in bitcoins
Deprecate with Prejudice
Are your zombie memes killing you?
Default vs Impl - A rant
How to implement a RuntimeExceptionMapper for Dropwizard
Do you need a Code Librarian?
Stored procedures vs Hibernate
Creating agile functional tests with Selenium
How to be agile when all about you are not - part 2
How to deploy dynamic sites with git
How to deploy static sites with git
How to recover your bitcoins from a failed hard drive
Simple Scaffolding
An nginx configuration file for Dropwizard with static content
A .gitignore file for Intellij and Eclipse with Maven
How to recover lost bitcoins from an Android wallet
9 top tips to understand new code
Avoiding duplicate test results with Hudson
How to create a deterministic JAR
Preventing Dependency Chain Attacks in Maven
Dropwizard with Ember Data demo
Dropwizard with OpenID
How to include Markdown with Freemarker in Dropwizard
MultiBit Merchant: Implementing HMAC authentication in Dropwizard
How to deploy a Dropwizard project to Heroku
How to accept bitcoins on your blog with no code

comments powered by Disqus