Closed mickroll closed 3 years ago
this is a good idea, I think. In general, I would actually dream that Java would be a language in which tools like Immutables would not be needed. Even with new records we still probably need builders and some other stuff generated even if we delegate basic fields/hashCode/equals to the record implementation. The other thing is that considering record syntax is more compact than equivalent interface definition, some would even prefer having just record + generated builder akin to following:
record Abc(int a, double b, String c) {
@RecordBuilder// or something on Abc - doesn't matter
class Builder extends ImmutableAbc.Builder {}
}
var abc = new Abc.Builder().a(1).b(0.2).c("C").build();
In order to get to this we need some time + experimenting. I would call for help here, but because of experimental nature of it, the path to merged PR might not be straightforward, yet we're open to consider proposed designs/implementations
Since java records I think of Immutables in its current state as a bridging technology. Many libs depend on bean-style getters, some expect them to be annotated. Java records do not provide this (afaik). On the other hand, newer libs might expect/allow data objects to be java records. This is where immutables could step in:
@JsonProperty
)Backing immutable implementations with java records would enable a seamless interoperation between 'old-style' bean-expecting libs and newer ones that already allow 'record-style' getters (do they have a special name?)
Can you provide some code snippets as an illustration? interfaces, signatures, and/or usage pseudocode
I thought of something along the lines of:
Data object definition:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import javax.validation.constraints.NotEmpty;
import org.immutables.value.Value;
@Value.Immutable(record = true)
@Value.Style(get = { "get*", "is*" }, init = "set*", allParameters = true)
@JsonDeserialize(builder = ImmutableLoginAttemptUser.Builder.class)
public interface LoginAttemptUser {
@NotEmpty
String getUsername();
@NotEmpty
String getPassword();
}
Record-style implementation, generated by immutables:
@javax.annotation.processing.Generated("org.immutables.processor.ProxyProcessor")
public record ImmutableLoginAttemptUser(String username, String password)
implements LoginAttemptUser {
@JsonProperty("username")
@Override
public String getUsername() {
return username;
}
@JsonProperty("password")
@Override
public String getPassword() {
return password;
}
public static ImmutableLoginAttemptUser.Builder builder() {
return new ImmutableLoginAttemptUser.Builder();
}
@Generated(from = "LoginAttemptUser", generator = "Immutables")
public static final class Builder {
[... same as before]
}
}
Usage, bean-style (these getters need to be generated by immutables to fulfill interface declarations):
var user = ImmutableLoginAttemptUser.builder().setUsername("foo").setPassword("bar").build();
String x = user.getUsername() + ":" + user.getPassword();
The jackson json deserializer would use the builder in a similar way. The jackson json serializer would use the bean-style getters.
Usage, record-style (these 'getters' are generated by compiler):
var user = ImmutableLoginAttemptUser.builder().setUsername("foo").setPassword("bar").build();
String x = user.username() + ":" + user.password();
I think the overall behaviour of the generated records are the same in comparison to created immutable classes.
Note the restrictions of records (https://openjdk.java.net/jeps/395#Rules-for-record-classes), that is why i would recommend to enable usage of records on an opt-in basis via @Value.Immutable(record = true)
.
Interesting. So the most compelling use case for generating records would allow to use records while still implementing older get*
accessors from interfaces? I thinks it's by design that it will contain both .getVal()
and .val()
accessors for compatibility. I think Jackson piece would be negligible in future as Jackson can (if not already) easily provide a module to serialize/deserialize records.
Well, maybe what I really need is a builder for records. And thats already possible using @Builder.Constructor
(https://immutables.github.io/factory.html)
Thanks for the discussion!
How about generating the immutable implementation of a given interface as a java 15
record
(JEP 359)?This would allow all the benefits of
records
to be combined with the greatness and ecosystem integration of immutables (in my case: mainly compatibility with mapstruct and openapi).WDYT?