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

Prepare for query re-fetching #491

Merged
merged 7 commits into from
May 20, 2017

Conversation

sav007
Copy link
Contributor

@sav007 sav007 commented May 13, 2017

Introduce separate interfaces for query and mutation calls.
Generate OperationName type for operations to be used as identifier for refetching.

Part of #452

Introduce separate interfaces for query and mutation calls.
Generate OperationName type for operations to be used as identifier for refetching.
*/
@Nonnull ApolloMutationCall<T> refetchQueries(@Nonnull OperationName... operationNames);

@Nonnull @Override ApolloMutationCall<T> cacheControl(@Nonnull CacheControl cacheControl);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@BenSchwab do you think cacheControl makes sense for mutation as it defines fetch strategies and makes sense for queries only?

Though cacheHeaders makes sense cos it defines if response for this mutation should be cached or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

how does caching a mutation work? Like what happens if you try same mutation again will you then get cached result instead of it hitting server?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We shouldn't not hit the when we run mutations, but we should store the results to cache. So it's should WRITE ONLY. That's why I guess we need to remove cacheControl and just have cacheHeaders as they tells to save to cache or not

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, cacheControl does not make sense for mutations. It will always be NETWORK_ONLY. If we write to the cache can be controlled by cacheHeaders. It's generally important to write mutations results to the cache, as this is what triggers updates to apolloWatchers.

@@ -62,7 +60,7 @@ public void testQueryWatcherUpdated_SameQuery_DifferentResults() throws IOExcept
EpisodeHeroName query = EpisodeHeroName.builder().episode(Episode.EMPIRE).build();
server.enqueue(mockResponse("EpisodeHeroNameResponseWithId.json"));

ApolloWatcher<EpisodeHeroName.Data> watcher = apolloClient.newCall(query).watcher();
ApolloQueryWatcher<EpisodeHeroName.Data> watcher = apolloClient.query(query).watcher();
Copy link
Contributor

Choose a reason for hiding this comment

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

can you watch anything other than a query?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not now, we watch only Queries

private <D extends Operation.Data, T, V extends Operation.Variables> RealApolloCall<T> newCall(
@Nonnull Operation<D, T, V> operation) {
ResponseFieldMapper responseFieldMapper = responseFieldMapper(operation);
return new RealApolloCall<T>(operation, serverUrl, httpCallFactory, httpCache, defaultHttpCachePolicy, moshi,
Copy link
Contributor

Choose a reason for hiding this comment

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

this constructor is wild! is there a way we can encapsulate somehow. Of the top of my head I see 2 options: builder or organization of params into a few objects.

ResponseFieldMapper responseFieldMapper;
synchronized (responseFieldMapperPool) {
responseFieldMapper = responseFieldMapperPool.get(operation.getClass());
if (responseFieldMapper == null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry if this is out of scope for this pr but is there a way way can do a null object pattern or some other way to show absence without using null. also missing nullability annotations

*
* @param query the operation which needs to be performed
* @return prepared {@link ApolloQueryCall} call to be executed at some point in the future
*/
Copy link
Contributor

@VisheshVadhera VisheshVadhera May 15, 2017

Choose a reason for hiding this comment

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

We need to change the docs to:

/**
     * Creates and prepares a new {@link ApolloMutationCall} call.
     *
     * @param mutation the mutation which needs to be performed
     * @return prepared {@link ApolloMutationCall} call to be executed at some point in the future
     */

@@ -34,14 +34,14 @@ private Rx2Apollo() {
}

/**
* Converts an {@link ApolloWatcher} to an asynchronous Observable.
* Converts an {@link ApolloQueryWatcher} to an asynchronous Observable.
*
* @param watcher the ApolloWatcher to convert.
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to change it to:

@param watcher the ApolloQueryWatcher to convert.

@@ -35,27 +35,27 @@ private RxApollo() {
}

/**
* Converts an {@link ApolloWatcher} into an Observable. Honors the back pressure from downstream with the back
* Converts an {@link ApolloQueryWatcher} into an Observable. Honors the back pressure from downstream with the back
* pressure strategy {@link rx.Emitter.BackpressureMode#LATEST}.
*
* @param watcher the ApolloWatcher to convert
Copy link
Contributor

@VisheshVadhera VisheshVadhera May 15, 2017

Choose a reason for hiding this comment

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

@param watcher the ApolloQueryWatcher to convert.

return from(watcher, Emitter.BackpressureMode.LATEST);
}

/**
* Converts an {@link ApolloWatcher} into an Observable.
* Converts an {@link ApolloQueryWatcher} into an Observable.
*
* @param watcher the ApolloWatcher to convert
Copy link
Contributor

Choose a reason for hiding this comment

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

@param watcher the ApolloQueryWatcher to convert.

@sav007
Copy link
Contributor Author

sav007 commented May 17, 2017

@BenSchwab @digitalbuddha @marwanad any comments?

Copy link
Contributor

@BenSchwab BenSchwab left a comment

Choose a reason for hiding this comment

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

Is OperationName sufficiently unique?
I forget if we allow two querries to have the same name so long as they are in different packages?

@sav007
Copy link
Contributor Author

sav007 commented May 18, 2017

@BenSchwab just checked we don;t allow to have the same names for queries even in separate packages:

There can be only one operation named "CheckoutByIdQuery".
There can be only one operation named "CheckoutByIdQuery".

But it even if we allow to have the same names, for refetch we are going to use OperationName object itself not string name:

public final class TestQuery implements Query<TestQuery.Data, Optional<TestQuery.Data>, Operation.Variables> {
...

  private static final OperationName OPERATION_NAME = new OperationName() {
    @Override
    public String name() {
      return "TestQuery";
    }
  };

  @Override
  public OperationName name() {
    return OPERATION_NAME;
  }

So it will be unique for every query

public interface ApolloMutationCall<T> extends ApolloCall<T> {

/**
* <p>Sets a list of GraphQL query names to be re-fetched once this mutation completed.</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this comment correct? Why do you need to obtain a ApolloQueryWatcher first?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We will use ApolloQueryWatcher to be notified when this mutation operation completed. So in order to listen for refetch user should have watcher first right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, we might want to refetch a query you aren't watching. Like if you have a post feed page, and a child page that creates a post, we might want to refetch the post feed query to update the normalized cache, even if we aren't watching

Copy link
Contributor

Choose a reason for hiding this comment

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

Then if you happen to be watching the post page, and something did change, the watcher will be triggered when the refetched query is written to the normalized cache

Copy link
Contributor

Choose a reason for hiding this comment

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

I was imagining you would just pass the actual queries to be refetched here instead of an identifier.

Also, what would happen if I was listening to the same query that was executed twice with different variables. Seems like the tags would collide.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, makes sense. Tags can easily collide. So we just provide list of Query objects to be refetched, so the only open question how to handle errors, just ignore? Mutation finished with success, but one of the refetched query failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the same time even if more than one watcher with the same name registered we will trigger them both

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think we would just have to ignore errors. Log them to our ApolloLogger of course.

Good point that we could just refetch all watchers with same tag. It's possible that we would be fetching more than the user actually wants, but if we batch the refetch, it doesn't seem like a big deal.

Copy link
Contributor

@BenSchwab BenSchwab left a comment

Choose a reason for hiding this comment

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

Changes to the interfaces LGTM -- in terms of the refetching API, I would still lean towards being able to register Query objects vs tags just for the fact that it's unclear to me how watcher's will be used in practice, and being more generic seems like a plus. But if you want to land this, we can revisit when we implement the refetching behavior.

@sav007 sav007 merged commit e035c02 into apollographql:master May 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants