Git Product home page Git Product logo

Comments (18)

smkhalsa avatar smkhalsa commented on May 17, 2024 1

@awaik

I've added a basic Offline Mutation Plugin in #67.

You can see how to instantiate it here

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024 1

The Client is simply a composition of other TypedLinks, so you should be able to just copy / paste, adding the OfflineMutaionTypedLink into the chain. Keep in mind it'll need to be after the RequestControllerTypedLink and before the terminating link (i.e. FetchPolicyTypedLink).

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

Offline mutations are still a work in progress. This is a non-trivial problem to solve. Apollo Client still hasn't implemented this feature, despite it being a major pain point for years.

That said, the Ferry architecture should allow for them. I just haven't gotten around to implementing the feature yet.

If you are interested in contributing to this feature, I'd be happy to help you get oriented with the relevant parts.

from ferry.

awaik avatar awaik commented on May 17, 2024

Wow, didn't know that.
Yes, I would like to try and happy to take part in your nice plugin.
Give me one week please, I will look through the code and try to understand the structure and principles.

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

@awaik sounds good. Let me know if there's something you have trouble understanding.

from ferry.

awaik avatar awaik commented on May 17, 2024

lib/src/client/client.dart

  /// Keeps track of network connection status. For offline mutations to work,
  /// you must update this value when the network status changes.
  // TODO: implement offline mutation cache
  final isConnected = BehaviorSubject<bool>.seeded(true);

Do I understand correctly that you suggest isConnected to be managed from the app and when status changed we implement the mutation updating?
Or it should be managed in the package itself?

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

@awaik We will need to create an offline mutation queue to persist mutations when offline, along with a way to deserialize them into the original mutation type. I'd recommend using Hive for persistence since we already have a Hive data store implementation.

Mutations should only be run once each time they are called. This means that if a mutation is added to the queryController then the client immediately goes offline before the network response is received, the mutation should NOT be stored and rerun when the client comes online.

Therefore I was thinking of the following:

  1. The client should be aware of network connection status
  2. If the client is offline when the mutation is run, it gets added to the offline mutation queue and doesn't get added to the queryController. Once the client comes back online, mutations get added back to the queryController.
  3. The user could implement a RetryLink to catch cases where the client goes offline immediately AFTER the mutation is added to queryController.

I'm sure there are a lot of edge cases that aren't addressed by the above.

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

I think we should also provide hooks to allow custom logic to be run throughout the mutation execution lifecycle. At a minimum, these hooks should include:

  1. onEnqueue - run when a mutation is added to the offline queue (i.e. when the client is offline)
  2. onConnect - run when the client re-connects

By default, onConnect would just add the mutation back to the queryController, but this could be overridden with a custom callback.

from ferry.

jifalops avatar jifalops commented on May 17, 2024

@smkhalsa I think there's a problem with this design because you often won't know the device lost its connection until the network fails after the query is sent. There's also very slow connections or the server going down.

I have written a client and offline-capable cache out of necessity for my job. I've just come across Ferry and it seems to have everything I am looking for except the offline part. I'd like to bring the offline part to this repo but I'm not sure what I'd need to learn. I've done everything manually without normalization etc, and have only recently began to understand how all the pieces fit together and why you created the various gql packages.

In a nutshell, the cache I made is basically a hashmap of Query+Variables => Response stored as JSON data in files. The app that uses it makes some minor efforts to repeat queries/variables to maximize cache hits. This has actually worked really well. Does the HiveStore store data normalized?

The offline part is that any mutation that fails due to a network error is put into a queue. Duplicate mutations (query+variables) are not queued. Each offline-capable mutation must be able to make (and undo) changes to the full data graph in memory. The optimistic response might only include a created entity for example, and not reflect the side effect of associating that entity with another.

I can try and give a more precise description if you think this is doable.

from ferry.

jifalops avatar jifalops commented on May 17, 2024

After some thought I think the normalized cache would work as long as the queryId offered the same query+variables uniqueness. Mutations don't necessarily need to be undoable, and modifications to the in-memory graph outside of the optimistic result can be handled via a callback/hook since it's more business logic anyway.

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

@jifalops Thanks for the feedback.

I think there's a problem with this design because you often won't know the device lost its connection until the network fails after the query is sent. There's also very slow connections or the server going down.

Yes, I've thought about this, and we'd definitely need to address errors on inflight mutations.

The offline part is that any mutation that fails due to a network error is put into a queue. Duplicate mutations (query+variables) are not queued.

Enqueuing every mutation that fails due to a network error wouldn't work for every use case. For example, in some cases, it might be important for a mutation to get run exactly once, and the server may have successfully executed the original query, but the client never received the response (due to going offline immediately after sending the mutation, for example). In this case, the mutation would get enqueued and run a second time when the client comes back online.

We also have to consider that network retries are traditionally handled by Links, so our design should play nice with a RetryLink, etc.

In a nutshell, the cache I made is basically a hashmap of Query+Variables => Response stored as JSON data in files. The app that uses it makes some minor efforts to repeat queries/variables to maximize cache hits. This has actually worked really well.

So what's missing for you that is causing you to consider Ferry?

Does the HiveStore store data normalized?

In Ferry, Stores are responsible only for CRUD operations. Normalization / Denormalization happens in the Cache layer (which defers the actual normalization to the normalize package).

Each offline-capable mutation must be able to make (and undo) changes to the full data graph in memory. The optimistic response might only include a created entity for example, and not reflect the side effect of associating that entity with another.

The Cache layer in Ferry also handles optimistic updates. The Cache maintains a map of optimistic patches which are layered on top of the Store to derive the optimistic state. Once the mutation response is received, the patch for that mutation is removed. The user can also pass UpdateCacheHandlers to the client for a particular Mutation which will be run as part of the optimistic patch, then again after receiving the mutation response.

from ferry.

jifalops avatar jifalops commented on May 17, 2024

So what's missing for you that is causing you to consider Ferry?

It's not clear how my approach will scale, and it was largely created because of a lack of existing tools at the time. I like the idea of streamed responses and a normalized cache. In fact I've been moving towards a normalized in-memory cache naturally from the denormalized file cache, and it's sort of glaring at times that this should be done by a package.

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

Rather than providing a single canonical solution for offline mutation support, I'm considering implementing a plugin architecture that would allow third-party plugins to intercept and arbitrarily process OperationRequests and OperationResponses.

Since Ferry is essentially just a series of Stream transformations, the system would allow plugins to run custom transformations on the stream, both before and after the request is resolved.

Using this system, a ferry_offline_mutation_plugin might implement the following:

  1. intercept all mutations and add them to a mutation queue (which would use some offline-capable persistence such as hive)
  2. once a mutation is resolved successfully on the network, remove the mutation from the queue
  3. any time the client comes online, rerun any failed mutations

Obviously, this architecture could also enable virtually endless additional features to be added to the client.

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

Actually, much of the existing core ferry functionality could be reimplemented as plugins, including:

  1. updateCacheHandlers
  2. updateResult
  3. addTypename

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

#67 is a refactor that implements the plugin architecture described above.

from ferry.

akinsho avatar akinsho commented on May 17, 2024

@smkhalsa thanks for you work on this plugin, I've just moved over from graphql-flutter and am looking to try out the offline mutation plugin, but have just realised I don't think it's been released yet? I tried running it using a git url instead just to test it out and given the monorepo structure that doesn't work. I might be missing something 🤔 I'm running 0.9.2 and none of the plugin code seems to be available yet in that version. Any chance of a release soon 🙏🏿

from ferry.

smkhalsa avatar smkhalsa commented on May 17, 2024

@akinsho Ferry now uses a TypedLink-based architecture, so this plugin has been migrated to a TypedLink.

To use it, you'll need to implement your own custom TypedLink chain.

from ferry.

akinsho avatar akinsho commented on May 17, 2024

Thanks for the quick response @smkhalsa , I just had a read of the doc on typed links and a look at the test for the offline mutation typed link . I'm wondering what the easiest way to extend the client is to add this link in? It seems the default client is a chain of links and I'd like to maintain that as I assume it includes functionality that i'd need/like to keep rather than re-invent a completely custom chain.

EDIT: The test seems to only create a requestController and terminatingLink so I'm not sure if this means that it doesn't include any of the other functionality that the Client does or if your custom link replaces the client for any mutation you want to make offline

from ferry.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.