DATACMNS-1467 - Polishing.

Tweak docs.

Original Pull Request: #332
This commit is contained in:
Mark Paluch
2019-06-04 15:18:35 +02:00
committed by Christoph Strobl
parent dde9a651ba
commit 687222d1fa
4 changed files with 100 additions and 72 deletions

View File

@@ -3,23 +3,25 @@
The Spring Data infrastructure provides hooks for modifying an entity before and/or after certain methods are invoked.
Those so called ``EntityCallback``s provide a convenient way to check and potentially modify an entity in a callback fashioned style. +
An `EntityCallback` looks pretty much like a specialized `ApplicationListener`, with which you might be familiar.
Some Spring Data implementations publish store specific events, like a `BeforeSaveEvent` that allow to modify the given entity, which in some cases, eg. immutable types, can cause somme trouble.
Plus, the event publishing relies on the `ApplicationEventMulticaster` which can be configured with an asynchronous `TaskExecutor` leading to unpredictable outcome.
An `EntityCallback` looks pretty much like a specialized `ApplicationListener`, with which you might be already familiar.
Some Spring Data modules publish store specific events, like a `BeforeSaveEvent` that allow modifying the given entity which in some cases, eg. immutable types, can cause trouble.
Another aspects is that, event publishing relies on `ApplicationEventMulticaster` that can be configured with an asynchronous `TaskExecutor` leading to unpredictable outcome as event processing can be forked onto a `Thread`.
``EntityCallback``s offer integration points with both sync and reactive APIs guaranteeing an in order invocation at fixed checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type.
Entity callbacks provide integration points with both, synchronous and reactive, APIs guaranteeing an in-order execution at well-defined checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type.
Entity callbacks are typically separated by API type. This separation means that a synchronous API only considers synchronous entity callbacks and a reactive implementation considers only reactive entity callbacks.
[NOTE]
====
The entity callback API has been introduced with Spring Data Commons 2.2 and is the recommended way of dealing with entity modifications. +
Existing store specific `ApplicationEvents` are still be published *before* the potentially registered ``EntityCallback``s are called.
The Entity Callback API has been introduced with Spring Data Commons 2.2. It is the recommended way of applying entity modifications.
Existing store specific `ApplicationEvents` are still published *before* the invoking potentially registered ``EntityCallback``s.
====
[entity-callbacks.implement]
== Implementing Entity Callbacks
The `EntityCallback` is, via its generic type argument, directly associated with the domain type it is meant for.
Each store typically uses a set of predefined entity callbacks covering the lifecycle of an entity.
An `EntityCallback` is directly associated with its domain type through its generic type argument.
Each Spring Data module typically ships a set of predefined `EntityCallback` interfaces covering the entity lifecycle.
.Anatomy of an `EntityCallback`
====
@@ -38,7 +40,7 @@ public interface BeforeSaveCallback<T> extends EntityCallback<T> {
}
----
<1> `BeforeSaveCallback` specific method to be called before an entity is saved. Returns a potentially modifed instance.
<2> The entity right bevore presisted.
<2> The entity right before persisting.
<3> A number of store specific arguments like the _collection_ the entity is persisted to.
====
@@ -59,20 +61,22 @@ public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
}
----
<1> `BeforeSaveCallback` specific method to be called on subscription, before an entity is saved. Emits a potentially modifed instance.
<2> The entity right bevore presisted.
<2> The entity right before persisting.
<3> A number of store specific arguments like the _collection_ the entity is persisted to.
====
Implement the interface suiting your application needs like shown in the example below.
NOTE: Optional entity callback parameters are defined by the implementing Spring Data module and inferred from call site of `EntityCallback.callback()`.
Implement the interface suiting your application needs like shown in the example below:
.Example `BeforeSaveCallback`
====
[source,java]
----
public class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered <2> {
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered <2> {
@Override
public Object onBeforeSave(Person entity, String collection) { <1>
public Object onBeforeSave(Person entity, String collection) { <1>
if(collection == "user) {
return // ...
@@ -83,74 +87,77 @@ public class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ord
@Override
public int getOrder() {
return 100; <2>
return 100; <2>
}
}
----
<1> Implement logic according to application requirements.
<1> Callback implementation according to your requirements.
<2> Potentially order the entity callback if multiple ones for the same domain type exist. Ordering follows lowest precedence.
====
[entity-callbacks.register]
== Registering Entity Callbacks
``EntityCallback``s get picked up by the store specific implementations in case they are provided with an `ApplicationContext`.
Most template APIs already implement `ApplicationContextAware` an therefore have a context at hand if registered as a Bean.
``EntityCallback`` beans are picked up by the store specific implementations in case they are registered in the `ApplicationContext`.
Most template APIs already implement `ApplicationContextAware` and therefore have access to the `ApplicationContext`
The following example provides a collection of valid entity callback registrations.
The following example explains a collection of valid entity callback registrations:
.Example `EntityCallback` Bean registration
====
[source,java]
----
@Order(1) <1>
@Component
class First implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeSave(Person person) {
return // ...
}
}
@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
Ordered <2> {
@Override
public Object onBeforeSave(Person entity, String collection) {
// ...
}
@Override
public int getOrder() {
return 100; <2>
}
}
@Configuration
public class EntityCallbackConfiguration {
@Bean
BeforeSaveCallback<Person> annotationOrderedCallback() { <1>
return new First();
}
@Bean
BeforeSaveCallback<Person> interfaceOrderedCallback() { <2>
return new DefaultingEntityCallback();
}
@Bean
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() { <3>
return (BeforeSaveCallback<Person>) it -> // ...
}
}
@Bean
UserCallbacks multipleCallbacksInOneImplementationClass() { <4>
return new UserCallbacks();
}
@Component
class UserCallbacks implements BeforeConvertCallback<User>,
BeforeSaveCallback<User> { <4>
@Order(1) <1>
static class First implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeConvert(User user) {
return // ...
}
@Override
public Person onBeforeSave(Person person) {
return // ...
}
}
static class UserCallbacks implements BeforeConvertCallback<User>, BeforeSaveCallback<User> { <4>
@Override
public Person onBeforeConvert(User user) {
return // ...
}
@Override
public Person onBeforeSave(User user) {
return // ...
}
}
@Override
public Person onBeforeSave(User user) {
return // ...
}
}
----
<1> `BeforeSaveCallback` receiving its order from the `@Order` annotation.
<2> `BeforeSaveCallback` receiving its order via the `Ordered` interface implementation.
<3> `BeforeSaveCallback` using a lambda expression. Unordered by default and invoked last.
<4> Combine multiple entity callback interfaces in one implementation class.
<4> Combine multiple entity callback interfaces in a single implementation class.
====

View File

@@ -16,34 +16,47 @@
package org.springframework.data.mapping.callback;
/**
* Marker interface for entity callbacks to be implemented in specific callback subtypes intended for internal usage
* within store specific implementations. <br />
* Marker interface for entity callbacks to be implemented in specific callback subtypes. Intended for internal usage
* within store specific implementations.
* <h3>Ordering {@link EntityCallback}</h3>
* <p/>
* Multiple entity callbacks are invoked sequentially with the result of the previous callback. Callbacks are unordered
* by default. It is possible to define the order in which listeners for a certain domain type are to be invoked. To do
* so, add Spring's common {@link org.springframework.core.annotation.Order @Order} annotation or implement
* {@link org.springframework.core.Ordered}.
* <h3>Exception Handling</h3>
* <p />
* Multiple entity callbacks are invoked sequentially with the result of the previous callback. Those callbacks do by
* default not follow an explicit order of invocation. It is strongly recommended to enforce ordering for callbacks of
* the same by implementing {@link org.springframework.core.Ordered} or following the annotation driven approach using
* {@link org.springframework.core.annotation.Order}.
* While it is possible for a {@link EntityCallback} to declare that it throws arbitrary exception types, any checked
* exceptions thrown from a {@link EntityCallback} are wrapped in an
* {@link java.lang.reflect.UndeclaredThrowableException UndeclaredThrowableException} since the callback mechanism can
* only handle runtime exceptions. Entity callback processing is stopped on the {@link EntityCallback} that raised an
* exception and the caused exception is propagated to the caller.
* <h3>Domain Type Binding</h3>
* <p />
* Entity callbacks are invoked after publishing {@link org.springframework.context.ApplicationEvent events}.
* An {@link EntityCallback} can generically declare the domain type that it is able to process by specifying the
* generic type parameter {@code <T>}. When registered with a Spring
* {@link org.springframework.context.ApplicationContext}, callbacks are filtered accordingly, with the callback getting
* invoked for assignable domain objects only.
* <p/>
* Typically, entity callbacks are invoked after publishing {@link org.springframework.context.ApplicationEvent events}.
* <p/>
* <h3>Defining {@link EntityCallback} Interfaces</h3>
* <p />
* A store specific {@link EntityCallback} needs to define a callback method accepting an object of the parameterized
* type as its first argument followed by additional <i>optional</i> arguments.
*
* <pre>
* <code>
* A {@link EntityCallback} interface needs to define a callback method accepting an object of the parameterized type as
* its first argument followed by additional <i>optional</i> arguments.
*
* <pre class="code">
*
* public interface BeforeSaveCallback&lt;T&gt; extends EntityCallback&lt;T&gt; {
*
* T onBeforeSave(T entity, String collection);
* T onBeforeSave(T entity, String collection);
* }
* </code>
* </pre>
*
* The
*
* @author Mark Paluch
* @author Christoph Strobl
* @param <T> Entity type. Used to detect {@link EntityCallback callbacks} to invoke via their generic type signature.
* @param <T> Entity type used to detect {@link EntityCallback callbacks} to invoke via their generic type signature.
* @since 2.2
* @see org.springframework.core.Ordered
* @see org.springframework.core.annotation.Order
*/

View File

@@ -19,8 +19,12 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.util.Assert;
/**
* Interface to be implemented by objects that can manage a number of {@link EntityCallback} objects and invoke these
* with a specific entity.
*
* @author Christoph Strobl
* @since 2.2
* @see EntityCallback
*/
public interface EntityCallbacks {
@@ -39,7 +43,7 @@ public interface EntityCallbacks {
* @param callbackType must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param args optional arguments.
* @param <T>
* @param <T> Entity type.
* @return never {@literal null}.
* @throws IllegalArgumentException if a required argument is {@literal null}.
*/

View File

@@ -21,8 +21,12 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.util.Assert;
/**
* Interface to be implemented by objects that can manage a number of {@link EntityCallback} objects and invoke these
* with a specific entity.
*
* @author Christoph Strobl
* @since 2.2
* @see EntityCallback
*/
public interface ReactiveEntityCallbacks {
@@ -42,7 +46,7 @@ public interface ReactiveEntityCallbacks {
* @param callbackType must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param args optional arguments.
* @param <T>
* @param <T> Entity type.
* @return a {@link Mono} emitting the result after invoking the callbacks.
* @throws IllegalArgumentException if a required argument is {@literal null}.
*/