Skip to content
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

Add Query Watching #312

Merged
merged 8 commits into from
Mar 13, 2017
Merged

Add Query Watching #312

merged 8 commits into from
Mar 13, 2017

Conversation

BenSchwab
Copy link
Contributor

@BenSchwab BenSchwab commented Mar 10, 2017

closes #304

This is a WIP query watching PR. There will be many more tests, naming will be reconsidered, TODOs fixed etc...

But wanted to get early thoughts on general design.

I played around with couple ways, but settled on have an the flow ApolloCall.toWatcher() -> ApolloWatcher

The reason is that in order to watch a query you need to execute the query at least once to generate the dependentKeys. So a user would first need to configure the "root query" with whatever cache control they want, and then transform it into a watcher.

Then they call enqueueAndWatch which performs the initial fetch. Upon successful fetch, the watcher then subscribes to change keys events on the cache.

Importantly, refetch cachePolicy is separate than the initial fetch policy.

One thing to take a look at: whenever a query is refetched, we actually unsubscribe from cache changed keys event, until the query is returned with new dependentKeys. This is because when there is an outgoing request, the current dependentKeys are "stale". When we move to more sophisticated threading model this will be important.

}

@Nonnull public WatcherSubscription enqueueAndWatch(@Nullable final ApolloCall.Callback<T> callback) {
this.callback = callback;
Copy link
Contributor

@sav007 sav007 Mar 10, 2017

Choose a reason for hiding this comment

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

Should we add safe guard if we already watching? I mean we don't support multiple subscriptions right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

}

private void fetch() {
activeCall.enqueue(callbackProxy(callback, activeCall));
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be careful, active call can't be run multiple times, should it be activeCall.clone()?

Copy link
Contributor Author

@BenSchwab BenSchwab Mar 10, 2017

Choose a reason for hiding this comment

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

enqueueAndWatch should only be called once -- I can add some safeguards for that. But enqueueAndWatch just calls the ApolloCall that this was transformed from, so that should never have been executed before.


@Nonnull public RealApolloWatcher<T> refetch() {
activeCall.cancel(); //Todo: is this necessary / good? We don't want people to chain refetch().refetch()
activeCall = activeCall.clone().cacheControl(refetchCacheControl);
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this line should be moved to fetch function, it will be easier to understand that we actually doing activeCall.clone() before enqueue

Copy link
Contributor Author

@BenSchwab BenSchwab Mar 10, 2017

Choose a reason for hiding this comment

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

So we actually aren't cloning before enqueueAndWatch only from refetch. Per other comments, it is only possible to transform a ApolloCall to an ApolloWatcher if it has not been executed. However, calls to refetch will clone the ApolloCall because in this case it has been used. I haven't convinced myself that refetch as a lot of value -- but added it because it is part of the iOS watcher interface.

@@ -74,7 +76,7 @@ private RealApolloCall(Operation operation, HttpUrl serverUrl, Call.Factory http
return executeNetworkRequest();
}

@Nonnull @Override public ApolloCall<T> enqueue(@Nullable final Callback<T> callback) {
@Nonnull @Override public void enqueue(@Nullable final Callback<T> callback) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what the reason to remove return value?

Copy link
Contributor

Choose a reason for hiding this comment

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

And can we have like this: apollocall.enqueue(callback).watch() ?

Copy link
Contributor Author

@BenSchwab BenSchwab Mar 10, 2017

Choose a reason for hiding this comment

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

We need to guarantee that the a Call has been executed when we watch. Otherwise we have no dependentKeys to monitor. So I don't like relying on have the user chain apollocall.enqueue().watch(). Instead I have it has apolloCall.toWatcher().enqueueAndWatch().

Is there a value in having enqueue return an ApolloCall? OkHttp has void return null. It just seems dangerous. Once you call enqueue many functions on that ApolloCall now explode, and returning it seems to imply that the object is still safe to use.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a value in having enqueue return an ApolloCall

no just was curious, thx makes sense!!

@@ -9,22 +15,35 @@
public final class RealCache implements Cache {
private final CacheStore cacheStore;
private final CacheKeyResolver cacheKeyResolver;
private Map<RecordChangeSubscriber, Set<String>> subscribers;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we make RecordChangeSubscriber as weak ref? So WeakHashMap?
Then it will be user responsibility to keep around subscribtion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Good Idea!

@sav007
Copy link
Contributor

sav007 commented Mar 10, 2017

One more question do we have a vision how query watching will utilize RxJava?

@digitalbuddha
Copy link
Contributor

It's going to use rxjava fromEmitter/create api which can wrap any async call into rx. I have good idea how to get that piece in.

@digitalbuddha
Copy link
Contributor

Here was original idea for rx support. I don't see why it won't work for this too. https://gist.github.com/digitalbuddha/3c1dbccf059f0279e46e540b2c5fd89e

@BenSchwab BenSchwab force-pushed the bschwab/query-watch branch from 902a8aa to a93c1f0 Compare March 11, 2017 07:20
@BenSchwab BenSchwab changed the title [WIP] Add Query Watching Add Query Watching Mar 11, 2017
@BenSchwab BenSchwab requested a review from sav007 March 11, 2017 07:30
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

final class RealApolloWatcher<T extends Operation.Data> implements ApolloWatcher<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to have cancel or dispose method, to cancel watching via RealApolloWatcher as well not only via WatcherSubscription

return this;
}

@Nonnull public RealApolloWatcher<T> refetch() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we can merge refetchCacheControlwith this method, so to have 2 API calls:
public RealApolloWatcher<T> refetch(@Nonnull CacheControl cacheControl)
public RealApolloWatcher<T> refetch()

Copy link
Contributor Author

@BenSchwab BenSchwab Mar 11, 2017

Choose a reason for hiding this comment

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

Overloading seems fair -- but we need to keep refetchCacheControl because it plays a roll in enqueueAndWatch because when dependenKeys change refetchCacheControl is used to make the new request. So call.toWatcher().refetchCacheControl(CACHE_ONLY).enqueueAndWatch(..)

Copy link
Contributor

Choose a reason for hiding this comment

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

My intention was to make it as much immutable as possible, after we called enqueueAndWatch what if we change the cache control refetchCacheControl? Should we add safe guard to prevent user from changing cache control after he called enqueueAndWatch ?

}

@Nonnull @Override public ApolloCall<T> httpCacheControl(@Nonnull HttpCacheControl httpCacheControl) {
@Nonnull @Override public RealApolloWatcher<T> toWatcher() {
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if I call this multiple times for the same RealApolloCall

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With clone, this would be acceptable. I don't see a real harm in allowing it, but it does go against the explicit single-use immutable style of the other methods.

}

@Nonnull @Override public ApolloCall<T> httpCacheControl(@Nonnull HttpCacheControl httpCacheControl) {
@Nonnull @Override public RealApolloWatcher<T> toWatcher() {
return new RealApolloWatcher<>(this, cache);
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we call new RealApolloWatcher<>(clone(), cache)?

@@ -12,33 +20,41 @@
private final CacheStore cacheStore;
private final CacheKeyResolver cacheKeyResolver;
private final ReadWriteLock lock;
private Map<RecordChangeSubscriber, Set<String>> subscribers;
Copy link
Contributor

Choose a reason for hiding this comment

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

final

this.cache = cache;
}

public void enqueueAndWatch(@Nullable final ApolloCall.Callback<T> callback) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: @OverRide

activeCall.enqueue(callbackProxy(this.callback, activeCall));
}

@Nonnull public RealApolloWatcher<T> refetchCacheControl(@Nonnull CacheControl cacheControl) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: @OverRide

@Nonnull ApolloCall<T> enqueue(@Nullable Callback<T> callback);
void enqueue(@Nullable Callback<T> callback);

@Nonnull ApolloWatcher<T> toWatcher();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe simple watcher() ?

Copy link
Contributor

@sav007 sav007 left a comment

Choose a reason for hiding this comment

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

Small comments, but lgtm. Great work

@BenSchwab
Copy link
Contributor Author

Thanks for the reviews!

@BenSchwab BenSchwab merged commit 5200ea3 into master Mar 13, 2017
@BenSchwab BenSchwab deleted the bschwab/query-watch branch March 13, 2017 02:39
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.

Add QueryWatching
3 participants