-
Notifications
You must be signed in to change notification settings - Fork 687
DATACMNS-836 - Add components to support reactive repositories. #177
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
Conversation
8cd82a8
to
3f01027
Compare
3f01027
to
186e9e9
Compare
9f89c8c
to
ecb0fe6
Compare
|
/** | ||
* Deletes all entities managed by the repository. | ||
*/ | ||
Single<Void> deleteAll(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider Completable
instead of Single<Void>
* @return the entity with the given id or {@literal null} if none found | ||
* @throws IllegalArgumentException if {@code id} is {@literal null} | ||
*/ | ||
Single<T> findOne(Single<ID> id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider Observable<T>
instead of Single<T>
as the result may be absent.
Upgraded to Spring Data Build parent 2.0 snapshots. Removed obsolete distribution key property. Removed obsolete template.mf.
Added explicit generics invocations in places that caused an ambiguous invocation on Java 8.
ecb0fe6
to
33750a6
Compare
We now expose reactive interfaces to facilitate reactive repository support in store-specific modules. Spring Data modules are free to implement their reactive support using either RxJava 1 or Project Reactor (Reactive Streams). We expose a set of base interfaces: * `ReactiveCrudRepository` * `ReactiveSortingRepository` * `RxJava1CrudRepository` * `RxJava1SortingRepository` Reactive repositories provide a similar feature coverage to blocking repositories. Reactive paging support is limited to a `Mono<Page>`/`Single<Page>`. Data is fetched in a deferred way to provide a paging experience similar to blocking paging. A store module can choose either Project Reactor or RxJava 1 to implement reactive repository support. Project Reactor and RxJava types are converted in both directions allowing repositories to be composed of Project Reactor and RxJava 1 query methods. Reactive wrapper type conversion handles wrapper type conversion at repository level. Query/implementation method selection uses multi-pass candidate selection to invoke the most appropriate method (exact arguments, convertible wrappers, assignable arguments). We also provide ReactiveWrappers to expose metadata about reactive types and their value multiplicity.
…a 2 type conversion.
- Rename RxJava...Repository to RxJava1...Repository - Use Completable and Observable instead of Single for results without values/optional values - Remove reactive paging for now as it does not really fit reactive data streaming - Expose ReactiveWrappers.isAvailable(ReactiveLibrary) method to query library availability
…verters. Move reactive wrapper conversion to ReactiveWrapperConverters to minimize touching points with reactive APIs. ReactiveWrapperConverters is used only from reactive repository components and does not initialize with blocking API use. This removes the need of having Project Reactor on the class path for non-reactive use.
1c006fb
to
8155b28
Compare
…blisherAdapter keep state between multiple subscribers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of comments regarding the use of a ConversionService
for distinguishing between reactive and non-reactive repositories. I'll push a commit with some suggested changes into the PR.
@@ -293,16 +294,14 @@ protected boolean isStrictRepositoryCandidate(Class<?> repositoryInterface) { | |||
* @param loader must not be {@literal null}. | |||
* @return the repository interface or {@literal null} if it can't be loaded. | |||
*/ | |||
private Class<?> loadRepositoryInterface(RepositoryConfiguration<?> configuration, ResourceLoader loader) { | |||
protected Class<?> loadRepositoryInterface(RepositoryConfiguration<?> configuration, ResourceLoader loader) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason we need to make this protected
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module-specific implementations need to load the interface for type usage introspection (see MongoRepositoryConfigurationExtension.getRepositoryConfigurations(…)
/ReactiveMongoRepositoryConfigurationExtension.getRepositoryConfigurations(…)
). The requirement derives from distinguishing between blocking/reactive repository interfaces.
repositoryInformation = new DefaultRepositoryInformation(metadata, repositoryBaseClass, | ||
customImplementationClass); | ||
} else { | ||
// TODO: Not sure this is the best idea but at some point we need to distinguish between |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we introduced a method on ReactiveWrappers
that indicates whether a method uses a reactive wrapper type (either parameter or return type) or even a class is using one (by inspecting all methods exposed). That in place we could call that very method here. That way, we could at least keep the creation of the ConversionService
inside that class and move away from that rather indirect decision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a second thought… we could even expose ….isReactiveRepository()
on RepositoryMetadata
backed by the same logic.
* @return the saved entities | ||
* @throws IllegalArgumentException in case the given {@code Publisher} is {@literal null}. | ||
*/ | ||
<S extends T> Flux<S> save(Publisher<S> entityStream); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we rather use a Flux
as parameter here, too. If not, should findOne(…)
use an Observable
? Otherwise there's an asymmetry between the parameter types, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The root cause for asymmetry are design differences between RxJava and Project Reactor. There's no common base type for RxJava types whereas Reactor uses Publisher
. Publisher
is a general purpose type where multiplicity does not matter. RxJava enforces a specific type. Accepting Publisher
is the recommended way to maximize interop between reactive libraries.
We can reduce asymmetry by reducing methods. findOne
can be invoked also with findAll(mono)
or findAll(single.toObservable())
which allows us to reconsider our API surface.
RepositoryMetadata now exposes an ….isReactiveRepository() that's implemented by inspecting the backing repository interface for any reactive wrapper type appearing in method signatures. That allows us to move down the use of a ConversionService down to ReactiveRepositoryInformation.WrapperConversionMatch. Made a couple of methods static that could be. Simplified some logic using streams. A bit better wording in assertions. Some formatting when using streams.
abac726
to
7ea8c9d
Compare
I pushed the changes suggested above in abac726. The use of a |
RepositoryConfigurationExtensionSupport now exposes a ….useRepositoryConfiguration(RepositoryMetadata) so that extensions can opt in or out of an interface taking part in configuration more easily. Turned loadRepositoryInterface(…) private again as extensions should be able to just inspect the RepositoryMetadata now. Some parameter rename in ReactiveWrapperConverters to avoid confusion.
Introduced ReactiveRepositoryFactorySupport so that validation for the presence of all required converters (e.g. RxJava 1) does not have to be repeated in individual stores.
5e35e3a
to
2d17ae4
Compare
…rConverters. Reactive type conversion now happens fully inside of ReactiveWrapperConverters and does not require an external ConversionService. This change allows changes to reactive type conversion without changing the API since all details are encapsulated by ReactiveWrapperConverters.
2d17ae4
to
c8ea943
Compare
Fixes spring-projectsgh-175 (more of a workaround really and spring-projectsgh-177 is still open to track further changes).
We now expose reactive interfaces to facilitate reactive repository support in store-specific modules. Spring Data modules are free to implement their reactive support using either RxJava1 or Project Reactor (Reactive Streams). We expose a set of base interfaces:
ReactiveCrudRepository
ReactiveSortingRepository
RxJava1CrudRepository
RxJava1SortingRepository
Reactive repositories provide a similar feature coverage to blocking repositories. Reactive paging is not supported.
A store module can choose either Project Reactor or RxJava 1 to implement reactive repository support. Project Reactor and RxJava types are converted in both directions allowing repositories to be composed of Project Reactor and RxJava query methods. Reactive wrapper type conversion handles wrapper type conversion at repository level. Query/implementation method selection uses multi-pass candidate selection to invoke the most appropriate method (exact arguments, convertible wrappers, assignable arguments).
We also provide
ReactiveWrappers
andReactiveWrapperConverters
to expose metadata about reactive types and their value multiplicity.Related ticket: DATACMNS-836
Open issues: