Skip to content

DATACMNS-1190 - Added section on how to enforce nullability constraints on repositories #253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATACMNS-1190-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down
76 changes: 76 additions & 0 deletions src/main/asciidoc/repositories.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,82 @@ In this first step you defined a common base interface for all your domain repos
NOTE: Note, that the intermediate repository interface is annotated with `@NoRepositoryBean`. Make sure you add that annotation to all repository interfaces that Spring Data should not create instances for at runtime.


[[repositories.nullability]]
=== Null handling of repository methods

As of Spring Data 2.0, repository CRUD methods that return an individual aggregate instance use Java 8's `Optional` to indicate the potential absence of a value.
Besides that, Spring Data supports to return other wrapper types on query methods:

* `com.google.common.base.Optional`
* `scala.Option`
* `io.vavr.control.Option`
* `javaslang.control.Option` (deprecated as Javaslang is deprecated)

Alternatively query methods can choose not to use a wrapper type at all.
The absence of a query result will then be indicated by returning `null`.
Repository methods returning collection like values will never return null but an empty value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about Repository methods returning collections, wrappers, and streams are guaranteed to never return null but rather the corresponding empty representation.?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should link to the appendix table listing all supported return types as they also list the alternative collection types we support that might be handy to know about but don't need any discussion here.


[[repositories.nullability.annotations]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section duplicates the section on Null-safety that is currently in place. It would make sense to move core.nullability-validation in here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After revisiting the other version of the docs, I'd actually like to keep this one here and augment it with the following aspects currently present in the other version:

  • Nullability annotations should list the fully-qualified name at least once, so that it's clear what to import. I think we can (should?) leave out the remark with JSR-305 needed for meta-annotation usage. That's taking it a bit too far I think.
  • The kotlin-reflect library needed to get the runtime checks enabled.

==== Nullability annotations

To properly validate nullability constraints on a repository method at runtime, annotations can be defined to define whether query methods are supposed to return `null`.
To enable this, non-nullability needs to be activated on the package level, e.g. using Spring's `@NonNullApi` in `package-info.java`:

.Declaring non-nullablity in `package-info.java`
====
[source, java]
----
@NonNullApi
package com.acme;
----
====

Once that is in place, repository query method will get runtime execution validation of the nullability constraints and exceptions will be thrown in case a query execution result violates the defined constrained, i.e. the method would return `null` for some reason but is declared as non-nullable (the default with the annotation defined on the package the repository resides in).
If you want to opt-in to nullable results again, e.g. `@Nullable` can be used on a method selectively.
Using the aforementioned result wrapper types will continue to work as expected, i.e. an empty result will be translated into the value representing absence.

.Using different nullability constraints
====
[source, java]
----
package com.acme;

interface UserRepository extends Repository<User, Long> {

User getByEmailAddress(EmailAddress emailAddress); <1>

@Nullable
User findByEmailAddess(@Nullable EmailAddress emailAdress); <2>

Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); <3>
}
----
<1> Will throw an `EmptyResultDataAccessException` in case the query executed does not produce a result. Will throw an `IllegalArgumentException` in case the `emailAddress` handed to the method is `null`.
<2> Will return `null` in case the query executed does not produce a result. Also accepts `null` as value for `emailAddress`.
<3> Will return `Optional.empty()` in case the query executed does not produce a result. Will throw an `IllegalArgumentException` in case the `emailAddress` handed to the method is `null`.
====

[[repositories.nullability.kotlin]]
==== Nullability in Kotlin-based repositories

Kotlin has the definition of nullability constraints baked into the language.
Spring Data repositories using the language mechanism to define those constraints will automatically get the same runtime checks applied:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a good place to mention the need to have kotlin-reflect on the classpath.


.Using nullability constraints on Kotlin repositories
====
[source, kotlin]
----
interface UserRepository : Repository<User, String> {

fun findByUsername(username: String): User <1>

fun findByFirstname(firstname: String?): User? <2>
}
----
<1> The method defines both the parameter as non-nullable (the Kotlin default) as well as the result. The Kotlin compiler will already reject method invocations trying to hand `null` into the method. In case the query execution yields an empty result, an `EmptyResultDataAccessException` will be thrown.
<2> This method accepts `null` as parameter for `firstname` and return `null` in case the query execution does not produce a result.
====

[[repositories.multiple-modules]]
=== Using Repositories with multiple Spring Data modules

Expand Down