Skip to content

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

Closed
wants to merge 17 commits into from

Conversation

mp911de
Copy link
Member

@mp911de mp911de commented Sep 21, 2016

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 and ReactiveWrapperConverters to expose metadata about reactive types and their value multiplicity.


Related ticket: DATACMNS-836

Open issues:

  • Revisit changes
  • Remove touched classes without functional changes

@mp911de
Copy link
Member Author

mp911de commented Oct 28, 2016

QueryExecutionConverters uses ReactiveAdapterRegistry which requires Project Reactor to be available on the classpath. This breaks applications not having Project Reactor on their class path.

/**
* Deletes all entities managed by the repository.
*/
Single<Void> deleteAll();
Copy link
Member Author

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);
Copy link
Member Author

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.
mp911de and others added 5 commits November 8, 2016 10:50
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.
- 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.
@mp911de mp911de force-pushed the issue/DATACMNS-836 branch from 1c006fb to 8155b28 Compare November 8, 2016 09:51
Copy link
Member

@odrotbohm odrotbohm left a 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) {
Copy link
Member

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?

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member

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);
Copy link
Member

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?

Copy link
Member Author

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.
@odrotbohm
Copy link
Member

I pushed the changes suggested above in abac726. The use of a ConversionService still seems to be a bit of overkill in WrapperConversionMatch as I guess we could just determine the same information from ReactiveWrappers directly.

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.
…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.
@odrotbohm odrotbohm closed this Nov 14, 2016
@odrotbohm odrotbohm deleted the issue/DATACMNS-836 branch November 14, 2016 18:53
Aloren pushed a commit to Aloren/spring-data-commons that referenced this pull request Jun 20, 2019
Fixes spring-projectsgh-175 (more of a workaround really and spring-projectsgh-177 is still open
to track further changes).
Aloren pushed a commit to Aloren/spring-data-commons that referenced this pull request Jun 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants