Re: Records — Dealing with nullable values

Tagir Valeev amaembo at gmail.com
Sun Dec 15 02:20:40 UTC 2019


>  But if you really want to treat "no
> gender" as a valid point in the domain, then just make a record with an
> Optional<Gender> component, and push the `Optional.ofNullable()` to the
> caller (or, provide an overloaded constructor that does it for them.)
>

A better solution, to my opinion, is to encode an unspecified gender as one
of the values within the Gender type and make it non-nullable. E.g. if
Gender is an enum, just add a new UNSPECIFIED constant. This is much better
for all clients. E. g. you may safely do person.gender().toString() to
display the gender in the UI without ugly things like
person.gender().map(Gender::toString).orElse(Messages.UNSPECIFIED_GENDER).

With best regards,
Tagir Valeev.


But I would still use this pattern sparingly; the majority of uses I see
> of Optional in domain models are gratuitous.
>
> On 12/14/2019 5:31 PM, Arash Shahkar wrote:
> > In my example, gender can be left undefined, but not name. Dropping
> > requireNonNull(gender) from the compact constructor works, but leaves
> > the accessor for gender returning null (possibly).
> >
> > What I’d like to achieve is to have the accessor for gender return
> > Optional<Gender>, to clearly communicate that it might be null. I have
> > found this pretty common, and using this pattern has helped a lot.
> >
> > I was wondering if my “workaround” to be able to use records and drop
> > Lombok for these cases is a big no-no.
> > On Dec 14, 2019, 5:16 PM -0500, Brian Goetz <brian.goetz at oracle.com>,
> > wrote:
> >> No, that would not be a good idea.  If you find yourself using Optional
> >> for a method parameter, or a field type, or a record component type,
> >> you've almost certainly gone Optional-crazy.  It's a common, but
> >> curable, disease.
> >>
> >> What you want to do is this:
> >>
> >>     record Foo(String name, Gender gender) {
> >>         Foo {
> >>             requireNonNull(name);
> >>             requireNonNull(gender);
> >>         }
> >>     }
> >>
> >> The `Foo { ... }` is a _compact constructor_, which is a constructor
> >> whose signature is the same as that of the record, and which
> >> automatically handles the `this.x = x` boilerplate for you. This is
> >> where you put argument validation / normalization.
> >>
> >>
> >> On 12/14/2019 5:01 PM, Arash Shahkar wrote:
> >>> Hi,
> >>>
> >>> I’ve been using Lombok for quite a while now to automatically
> >>> generate boilerplate code for "data carrier" classes. However, a
> >>> pattern I really like and have found to work very well is to verify
> >>> non-nullity of all the fields that must not be null, and return an
> >>> Optional<Field> from the accessor of a field if it’s allowed to have
> >>> a null value. Consider:
> >>>
> >>> @lombok.Value
> >>> class Person {
> >>>
> >>>     String name;
> >>>     Gender gender;
> >>>
> >>>     Person(String name, Gender gender) { … } // customize the
> >>> constructor, verifying that name is defined and valid
> >>>
> >>>     Optional<Gender> getGender() { // customize the accessor for
> >>> gender, to make sure calling code deals with the possibility of
> >>> gender being null
> >>>         return ofNullable(gender);
> >>>     }
> >>> }
> >>>
> >>> Doing a similar thing with records is not possible, due to the fact
> >>> that the accessor for gender has to exactly return a Gender. With
> >>> the current specification for records, the only option is to do:
> >>>
> >>> record Person(String name, Optional<Gender> gender) {
> >>>     …
> >>> }
> >>>
> >>> Is this considered good practice? Do you suggest any alternatives?
> >>
>
>


More information about the amber-dev mailing list