Closed emmanuelbernard closed 5 years ago
@cescoffier @FroMage here are my first batch of feedback. I don't know how readable my wild proposals are, so feel free to ask questions.
I hate packages with the name api.
That's temporary. Actually, I will extract this to a new repo / project.
UniSubscriber and UniSubscription do not receive nor return Uni in their signature. Can we therefore have generic Subscriber / Subscription? We can have a prefix to differentiate them from others like Rx, I'm arguying for a unified Uni and Multi Subscription types. Are they used by normal people? If no, maybe put them in a sub package.
MultiSubscription
should be a Reactive Streams Subscription
. Also, MultiSubscriber
should be a ReactiveStreams Subscriber
, so no new class required for these. Here we need a specific Subscription because the request
method (that matter for multi-item streams), is not used. That said, I agree that these classes are rather internal / advanced users and most people won't use these classes directly. So I'm in favor in moving them (we can even remove UniSubscription
)
Disposable does not seem to be used from a user PoV (from the tests I am seeing), do we really need it? In the main package?
Definitely in implementation details. Should not be there.
Methods https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/UniEmitter.java#L28 Is it common enough to call success() to warrant an extra method exposed?
I was wondering about this one actually. And I'm still puzzled. I believe that null
should be an explicit decision and so this method (that pass null
) should be dropped. However, I wanted to discuss it with @FroMage.
I personally find asFoo() more natural to read than toFoo(). I don't know if there is a strong reason to use to.
I've been using to
to avoid confusion with the as
is a Kotlin keyword(https://kotlinlang.org/docs/reference/typecasts.html#unsafe-cast-operator). But I don't have a strong opinion about it.
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L271 I'd rename CompletableFuture
subscribeToCompletionStage(); to CompletableFuture subscribeAndConvertToCompletableFuture(); While toFoo() might be readable (though I find asFoo more readable), subscribeTo changes the meaning.
Agreed, it should be renamed.
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L163 I'm not super clear what it defers to from the javadoc, nor when I would want to use it.
Yes, I need to improve the Javadoc. It's actually quite useful, as it let you postponed the creation at subscription time. So you would create the resources only if required (and not at instantiation time).
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L350 I don't understand what that one does.
Again, the Javadoc is not great... Imagine 2 async actions running in parallel, you want to be notified when both completed but without requiring their results. That being said, this is close to the zip
operator (but the results are not discarded). So maybe we can remove it.
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L377 When do you plan to use this one? Having to pass a function instance seems annoying at first value compared to a static Converter.class of sort.
Agreed, we can remove it. It was before adding the adapters.
Wild proposals Replace fromPublisher(Publisher
) and fromPublisher(PublisherBuilder) with: either 1. Uni
from(Class ).foo(Foo ) used like Uni Uni.from(PublisherConverter.class).publisher(publisher); where publisher(P...)is a specific method driven directly or indirectly by thePublisherConverter`. Examples of APIs using this pattern is https://github.com/beanvalidation/beanvalidation-api/blob/master/src/main/java/javax/validation/Validation.java#L63 https://github.com/beanvalidation/beanvalidation-api/blob/master/src/main/java/javax/validation/Validation.java#L152 https://github.com/beanvalidation/beanvalidation-api/blob/master/src/main/java/javax/validation/spi/ValidationProvider.java#L26 (I think it can be simplified for our use case though) or 2. Uni from().publisher(Publisher) Uni from().publisher(PublisherBuilder) where these methods are "hardcoded" in the return type of from() This makes for an easier usage for the known type to convert from. We can have a from(Class ) (see option 1) for types "unknown" like Rx1, Rx2, Reactor etc or 3. (variant of 2.) fromPublisher().with(Publisher ), fromPublisher().with(PublisherBuilder ), fromConverter(Class ) The idea is to reduce the methods displayed by the IDE and be extensible for each of these >alternatives Same for fromOptional
I would prefer 2. Right now, we don't have the Converter class, it's managed using:
from(I instance)
and this triggers a lookup for the right UniAdapter
instance (SPI). For instance:
Mono<String> mono = ...
Uni<String> uni = Uni.from(mono);
It also has the opposite variant (using to
so might we as
):
Uni<String> uni = ...
Mono<String> mono = Uni.to(Mono.class)`
So basically it hides the Converter/Adapter
class.
Replace failed(Throwable) and failed(Supplier) with failing().with(Throwable) and failing().with(Supplier) The idea is to reduce the methods displayed by the IDE
I need to try to be sure it has the "expected reduction of methods displayed in your IDEin the sense that this should be done for the complete API (
failing,
on,
collect,
from,
block`...). It worths a try!
Replace subscribe(UniSubscriber) UniSubscription subscribe(Consumer, Consumer) and > CompletableFuture
subscribeToCompletionStage() with subscribe().withSubscriber, subscribe().withCallbacks, subscribe().andReturnCompletableFuture() Replace block* with block().indefinitely(), block().withDuration(), block().andReturnOptional() Note that all this design does not requires several implementations, DefaultUni can implement all >these types returned by subscribe(), from() etc and this is returned.
Definitely worths a try.
TODO (Emmanuel):
Look at and discuss to. Consider doing the method aggregation pattern for operators like or().else(), retry, >on().error().return() etc
Wasn't return a keyword ;-)
Also offer a generic operator extension model based on the uni from(Converter) proposal above >(Bean Validator pattern).
So, that was proposed as from
and to
using:
static <T, I> Uni<T> from(I instance);
<O> O to(Class<O> clazz);
Now the question is whether we want to make the converter/adapter
explicit.
Implementation detail It's useful to have UniFailed UniOf etc, vs a generic Uni impl and passing the behavior as lambdas in > the constructor?
Why not.
My feedback:
from
and fromOptional
I'm not sure they need to have separate names.failed
has a variant with Supplier
but from
's similar variant is defer
. I understand this is standard terminology but perhaps we should harmonise it?Tuple/Pair
types?ignore
be ignoreElement
?I don’t think the as keyword from Kotlin will be a problem. It’s a keyword and therefore is not a (chainable) method
The idea is to reduce the methods displayed by the IDE
Quite often, especially in APIs like this (java.util.stream
, RxJava, etc.) I let my IDE show me all possible methods and I look at their signatures (especially return types) to see if there's something that fits what I need to do. Please don't take that away.
I don’t think the as keyword from Kotlin will be a problem. It’s a keyword and therefore is not a (chainable) method
If there's a method called as
on a Java class, you can't call it from Kotlin like obj.as(...)
, because as
in Kotlin is a non-contextual keyword (https://kotlinlang.org/docs/reference/keyword-reference.html). You have to do obj.`as`(...)
.
Closing as Uni has been moved to its own repo.
Emmanuel is a noob in reactive things. Feel free to ignore the proposals that are not possible for fundamental reasons.
Types
I hate packages with the name
api
.UniSubscriber
andUniSubscription
do not receive nor returnUni
in their signature. Can we therefore have genericSubscriber
/Subscription
? We can have a prefix to differentiate them from others like Rx, I'm arguying for a unified Uni and Multi Subscription types. Are they used by normal people? If no, maybe put them in a sub package.Disposable
does not seem to be used from a user PoV (from the tests I am seeing), do we really need it? In the main package?Methods
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/UniEmitter.java#L28 Is it common enough to call
success()
to warrant an extra method exposed?I personally find
asFoo()
more natural to read thantoFoo()
. I don't know if there is a strong reason to useto
.https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L271 I'd rename
CompletableFuture<T> subscribeToCompletionStage();
toCompletableFuture<T> subscribeAndConvertToCompletableFuture();
WhiletoFoo()
might be readable (though I findasFoo
more readable),subscribeTo
changes the meaning.https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L163 I'm not super clear what it defers to from the javdoc, nor when I would want to use it.
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L350 I don't understand what that one does.
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L377 When do you plan to use this one? Having to pass a function instance seems annoying at first value compared to a static
Converter.class
of sort.Wild proposals
Replace
fromPublisher(Publisher<T>)
andfromPublisher(PublisherBuilder)
with:Uni<T> from(Class<T extends Converter>).foo(Foo<T>)
used likeUni<String> Uni.from(PublisherConverter.class).publisher(publisher); where
publisher(P...)is a specific method driven directly or indirectly by the
PublisherConverter`.Uni<T> from().publisher(Publisher)
Uni<T> from().publisher(PublisherBuilder)
where these methods are "hardcoded" in the return type offrom()
from(Class<T extends Converter>)
(see option 1) for types "unknown" like Rx1, Rx2, Reactor etcfromPublisher().with(Publisher<T>)
,fromPublisher().with(PublisherBuilder<T>)
,fromConverter(Class<T extends Converter>)
Same for
fromOptional
Replace
failed(Throwable)
andfailed(Supplier)
withfailing().with(Throwable)
andfailing().with(Supplier)
Replace
subscribe(UniSubscriber)
UniSubscription subscribe(Consumer, Consumer)
andCompletableFuture<T> subscribeToCompletionStage()
withsubscribe().withSubscriber
,subscribe().withCallbacks
,subscribe().andReturnCompletableFuture()
Replace
block*
withblock().indefinitely()
,block().withDuration()
,block().andReturnOptional()
Note that all this design does not requires several implementations,
DefaultUni
can implement all these types returned bysubscribe()
,from()
etc andthis
is returned.TODO (Emmanuel):
to
.or().else()
,retry
,on().error().return()
etcDetails
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/UniEmitter.java#L6 => s/single signals/single signal/
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L37 => s/being subscribed to the specified value if/being subscribed to with the specified value if/
https://github.com/smallrye/smallrye-reactive-streams-operators/blob/features/POC-Uni/implementation/src/main/java/io/smallrye/reactive/streams/api/Uni.java#L84 => s/If it's not the case the subscriber's callbacks/If it's not the case, the subscriber's callbacks/
Implementation detail
It's useful to have
UniFailed
UniOf
etc, vs a generic Uni impl and passing the behavior as lambdas in the constructor?