Avoid Inconsistent Builder With Lomboks @Builder
Author
Marcus HeldIn a project I worked on I saw that nearly every entity and value object was created with Lomboks @Builder . Their reason is that it makes it easier to construct these objects - especially for tests. But it comes with a cost. The problems that these builders create can’t be detected by the compiler and are especially dangerous in every CI environment.
Let’s look at an example. This is our object that uses the builder for construction:
@Entity
@Builder
public class UserEntity {
@Id
private long id;
private String name;
@LastModifiedDate
private Date lastModification;
private DeviceData deviceData;
}
The @Builder
annotation generates an api that can be used like this:
UserEntity.builder()
.id(UUID.randomUUID())
.name("Marcus Held")
.deviceData(new DeviceData())
.lastModification(new Date())
.build();
So, let’s have a look in which pitfalls we can fall.
Pitfall #1 Adding Fields
Imagine we add a field to the entity like:
@Entity
@Builder
public class UserEntity {
@Id
private long id;
private String name;
@LastModifiedDate
private Date lastModification;
private DeviceData deviceData;
private Date registrationDate;
}
As soon as we introduce this the compiler… - does nothing. Of course not, because we don’t construct this entity by a constructor, we rely on the generated builder and the API is not forcing us to fill in the registrationDate
. The developer needs to make sure to look for all creations in the code and check if the property needs to be filled or not. I discussed a possible solution in Distinguish Between Optional and Mandatory Parameters in the Builder Pattern
.
Pitfall #2 Managed Properties
You might have noticed that our example uses a couple of different annotations. For example @LastModifiedDate private Date lastModification;
. The @LastModifiedDate
is a Spring Data annotation which fills in the date automatically. So actually we don’t want to have this in our builder. With the Builder annotation we can’t exclude this field, and even when there would be an option it wouldn’t be compile safe.
note “Tip” If you need to modify this field in a test, then consider mocking the class and test against the API instead of the property itself.
Pitfall #3 Invariants
According to the definitions of Domain Driven Design by Eric Evans the builder is a factory and its purpose is to ensure the invariant of the objects it constructs. Our builder can’t do this. For example, how should the builder make sure that the same id
is not used twice? Or that the name
is not breaking any bad word filter? The builder can’t do this without domain knowledge.
Conclusion
My takeaway from these thoughts is that I don’t use the @Builder
annotation anymore, because its pitfalls are too severe in my opinion. What I especially dislike is that these issues are hard to find, because all of them are not detectable by the compiler and can easily slip through in a CI environment.