"With" for records and the default constructor
Øyvind Kvien
oyvind at kvien.no
Tue Nov 26 10:13:43 UTC 2024
I see there're no plans of including the default constructor with withers
for record creation, and a manually created static builder has previously
been suggested instead with default values in the constructor. In my
opinion ommiting the default constructor to be used with withers would be a
big mistake.
A very common scenario in smaller services is to use simple data structures
to pass data to the api of other services and message brokers. In these
cases the data contains mostly primitive types and (these are not domain
models). Setting default values manually in the default constructor, as
suggested would be very tedious and lots of typing. Also many times a
default value is not even possible since it could mean setting the object
in a wrong initial state. Forcing the use of default values I think is a
bad approach.
It would therefore be immensely useful if the default record constructor
could be used with withers. The compiler needs of course to enforce that
all parameters in the default constructor are set.
Below is a real world example of how building a request body to an internal
service in my organization, used for getting access tokens for test
purposes, would look like using withers and the default constructor. The
following records describes the simple data structure for the request body
to the api of the service (which gets serialized to json).
record RequestBody(
String audience,
boolean withoutDefaultClientClaims,
boolean withoutDefaultUserClaims,
boolean createDPoPTokenWithDPoPProof,
ExpirationParameters expirationParameters,
ClientClaimsParameters clientClaimsParameters,
@Nullable UserClaimsParameters userClaimsParameters,
@Nullable DPoPProofParameters dPoPProofParameters) {
}
record ExpirationParameters(
int expirationTimeInSeconds) {
}
record ClientClaimsParameters(
List<String> scope,
String orgnrParent,
String clientId,
String clientName,
String jti) {
}
record UserClaimsParameters(
String pid,
String hprNumber,
String name,
String givenName,
String middleName,
String familyName,
String securityLevel,
String assuranceLevel,
String amr) {
}
record DPoPProofParameters(
String htuClaimValue,
String htmClaimValue) {
}
Using withers with the default constructor would look as suggested in the
method below. It's very easy to visualise the json structure that the
RequestBody record get serialized into when the parameter names are present.
Also there's no need to use default values which is a big win, and the
compiler enforces that all parameters are set. This way the record is never
instantiated in a wrong state.
Note that no parenthesis are used after 'new RequestBody' for the default
constructor as a syntax suggestion.
public static RequestBody createRequestBodyUserToken(
@Nullable String clientId,
@Nullable String userNin
) {
return new RequestBody with { // No parenthesis after 'new
RequestBody' for the default constructor.
audience = "audience";
withoutDefaultClientClaims = true;
withoutDefaultUserClaims = true;
createDPoPTokenWithDPoPProof = true;
expirationParameters = new ExpirationParameters with {
expirationTimeInSeconds = 300;
};
clientClaimsParameters = new ClientClaimsParameters with {
scope = List.of("openid", "scope");
orgnrParent = "12345";
clientId = clientId != null ? clientId :
"13edc8d1-3fa2-425a-9b53-c346df79e589";
clientName = "SmokeTest";
jti = UUID.randomUUID().toString();
};
userClaimsParameters = new UserClaimsParameters with {
pid = userNin != null ? userNin : "12345678912";
hprNumber = "987654";
name = "Half Badger";
givenName = "Half";
middleName = "";
familyName = "Badger";
securityLevel = "4";
assuranceLevel = "high";
amr = "pwd";
};
dPoPProofParameters = new DPoPProofParameters with {
htuClaimValue = "GET";
htmClaimValue = "https://myservice/test";
}
};
}
The alternative, as of today, is to first instantiate each record
separately and then lastly instantiate the RequestBody and return it. It's
harder to read as the parameter names get lost.
private static RequestBody createRequestBodyUserToken(
@Nullable String clientId,
@Nullable String userNin
) {
var expirationParameters = new ExpirationParameters(
300
);
var clientClaimsParameters = new ClientClaimsParameters(
List.of("openid","scope"),
"12345",
clientId != null ? clientId :
"13edc8d1-3fa2-425a-9b53-c346df79e589",
"SmokeTest",
UUID.randomUUID().toString()
);
var userClaimsParameters = new UserClaimsParameters(
userNin != null ? userNin : "12345678912",
"987654",
"Half Badger",
"Half",
"",
"Badger",
"4",
"high",
"pwd"
);
var dPoPProofParameters = new DPoPProofParameters(
"GET",
"https://myservice/test"
);
return new RequestBody(
"audience",
true,
true,
true,
expirationParameters,
clientClaimsParameters,
userClaimsParameters,
dPoPProofParameters
);
}
I therefore hope that the default record constructor can be included with
withers! It would be very useful when working with any type of api.
Regards
Øyvind Kvien
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20241126/8a983b94/attachment-0001.htm>
More information about the amber-dev
mailing list