Pre-made Immutables styles made by Treatwell for you!
@Style
s!Usage of immutable data structures for simple objects is generally not a controversial topic. Unfortunately, implementation of this idea in real life code
often proves to be quite difficult to properly manage, especially considering the tradeoff between boilerplate, maintainability and usability.
This is why, at Treatwell, we have become huge fans of the Immutables library over the years.
This brought us to building our own custom @Style
extensions on top of it, so that it would feel just right to blend it into our our existing
codebase with as little effort as possible while still covering a broad amount of use cases. This has been a great success so far, and while
not everything is perfect yet, we decided we would share these with a wider audience to make it easier for other to benefit from the lessons we
learned on our way to generalizing its usage in-house.
Hopefully you find as much enjoyment to using these as we did and still do!
There are many combinations of non-standard configurations that we do use for better usage and QoL. Here’s a few to give you an idea:
get*
and is*
accessor property names (instead of only get*
by default) when you need themYou will find all of the styles that we came up with over the years here.
But we understand that this would be a lot to take in randomly, so here are the two most important (and recommended for general use) ones to start with:
@ValueObjectStyle
AbstractXyz -> Xyz
public
, which allows for keeping the abstract one package-private@NonStrictValueObjectStyle
)Optional
and null
: Setting null
as value for an Optional
field will map it to Optional#empty
Optional#ofNullable
) does, as while
@Immutable
@ValueObjectStyle
/* package-private */ abstract class AbstractPerson {
@Parameter
public abstract String getName();
@Parameter
public abstract Instant creationTime();
}
@RestConstroller
public class PersonController {
private final PersonDao personDao;
@PostMapping
public Person createPersonWithName(@RequestParameter("name") String name) {
Person newPerson = Person.of(name, Instant.now());
// or Person.builder().name(name).creationTime(Instant.now()).build();
personDao.savePerson(newPerson);
return newPerson; // automatically serialized by Jackson
}
}
@DefaultStyle
Xyz -> ImmutableXyz
Optional
and null
: Setting null
as value for an Optional
field will map it to Optional#empty
Optional#ofNullable
) does, as while
@Immutable
@DefaultStyle
@JsonSerialize(as = ImmutableCount.class) // because Jackson will only see the abstract type instead of the generated one,
@JsonDeserialize(as = ImmutableCount.class) // it needs a little bit of extra help when handling the abstract type directly
public interface Count { // N.B.: If you always use only the generated type, this is unnecessary, but then
// @ValueObjectStyle seems more appropriate
@Parameter
int getCount();
@Parameter
Instant getLastIncrementTime();
}
@RestController
public class MyCountService {
private final AtomicReference<Count> currentCount = new AtomicReference<>(ImmutableCount.of(0, LocalDateTime.now()));
@PostMapping
public void incrementCount(Count count) {
Count oldCount = currentCount.get();
currentCount.set(ImmutableCount.of(count.getCount() + oldCount.getCount(), count.getLastIncrementTime()));
}
@GetMapping
public Count getCurrent() {
return currentCount.get();
}
}
This is a design choice to be made so there’s no silver bullet answer to begin with, but a couple of things will often motivate
the choice to fall upon one or the other:
The main difference between these two relates to whether you want to manipulate:
@ValueObjectStyle
) and be mostly blind to the abstract (annotated) one, @DefaultStyle
).Now the choice between these two approaches also relates to your concerns balance between serialization, visibility and inheritance
If serialization is the major concern, @ValueObjectStyle
will be the most convenient:
AbstractXyz -> Xyz
) makes it much cleaner to use the generated class@DefaultStyle
requires adding @Json{S, Des}erialize(as = ...)
to itIf on the other hand, you have a deep hierarchy, it is much easier to manage it with @DefaultStyle
and its
interface-based usage (not to mention using interfaces is always a pleasant advantage as composition is far easier to manage than inheritance):
Xyz -> ImmutableXyz
) is unwieldy, but does not matter a lot as we will mostly beA last point is that @ValueObjectStyle
allows keeping the abstract class package-private which could be useful to
make sure to avoid involuntary usage/inheritance of the abstract one.
To wrap this up, @ValueObjectStyle
should be mostly sufficient in almost all cases that do not require support for
complex class hierarchies.