graph-gophers/dataloader

repository·main·Indexed 23 days ago

https://github.com/graph-gophers/dataloader

A Golang implementation of Facebook's DataLoader pattern designed to batch and cache requests to reduce calls to data sources such as databases or APIs. It provides a generic Interface for loading data via unique keys, supporting custom cache implementations, batch capacity configuration, and thunk-based resolution for single or multiple keys.

Tokens
4.8K
Snippets
17
Records
29
Agent score
79%

What's inside graph-gophers-dataloader

  1. Configure or disable the DataLoader cache

    main

    The default dataloader.NewBatchedLoader uses a basic in-memory cache. This cache is intended for short-lived DataLoaders (e.g., scoped to a single HTTP request).

    If you want to use your own caching logic or disable caching entirely, you can provide a custom implementation. To disable caching, use the NoCache type, which implements the cache interface with no-op methods.

  2. Upgrade from v3 to v4

    main

    In v4, the key type was changed from string to interface{}, allowing for more flexible key types in both the dataloader.Interface and the Cache interface.

    // dataloader.Interface as now allows interace{} as key rather than string
    - loader.Load(context.Context, key string) Thunk
    + loader.Load(ctx context.Context, key interface{}) Thunk
    - loader.LoadMany(context.Context, key []string) ThunkMany
    + loader.LoadMany(ctx context.Context, keys []interface{}) ThunkMany
    - loader.Prime(context.Context, key string, value interface{}) Interface
    + loader.Prime(ctx context.Context, key interface{}, value interface{}) Interface
    - loader.Clear(context.Context, key string) Interface
    + loader.Clear(ctx context.Context, key interface{}) Interface
    
    // cache interface now allows interface{} as key instead of string
    type Cache interface {
    - 	Get(context.Context, string) (Thunk, bool)
    + 	Get(context.Context, interface{}) (Thunk, bool)
    - 	Set(context.Context, string, Thunk)
    + 	Set(context.Context, interface{}, Thunk)
    - 	Delete(context.Context, string) bool
    + 	Delete(context.Context, interface{}) bool
    	Clear()
    }
  3. Upgrade from v1 to v2

    main

    The primary change in v2 is the introduction of context.Context to the API. You must update your Load, LoadMany, and BatchFunc implementations to accept and pass a context.

    - loader.Load(key string) Thunk
    + loader.Load(ctx context.Context, key string) Thunk
    - loader.LoadMany(keys []string) ThunkMany
    + loader.LoadMany(ctx context.Context, keys []string) ThunkMany
    
    - type BatchFunc func([]string) []*Result
    + type BatchFunc func(context.Context, []string) []*Result
  4. How to use DataLoader

    main

    To use DataLoader, you must define a batchFn that accepts a context.Context and a slice of keys, returning a slice of *dataloader.Result pointers. You then create a loader using dataloader.NewBatchedLoader(batchFn).

    To fetch data, call loader.Load(ctx, key). This returns a 'thunk'—a function that, when called, blocks until the batch is processed and the value is resolved. The first context.Context passed to Load is the one provided to your batchFn.

    // setup batch function - the first Context passed to the Loader's Load
    // function will be provided when the batch function is called.
    // this function is registered with the Loader, and the key and value are fixed using generics.
    batchFn := func(ctx context.Context, keys []int) []*dataloader.Result[*User] {
      var results []*dataloader.Result[*User]
      // do some async work to get data for specified keys
      // append to this list resolved values
      return results
    }
    
    // create Loader with an in-memory cache
    loader := dataloader.NewBatchedLoader(batchFn)
    
    /**
     * Use loader
     *
     * A thunk is a function returned from a function that is a
     * closure over a value (in this case an interface value and error).
     * When called, it will block until the value is resolved.
     *
     * loader.Load() may be called multiple times for a given batch window.
     * The first context passed to Load is the object that will be passed
     * to the batch function.
     */
    thunk := loader.Load(context.TODO(), 5)
    result, err := thunk()
    if err != nil {
      // handle data error
    }
    
    log.Printf("value: %#v", result)
  5. Upgrade from v4 to v5

    main

    In v5, the interface{} key type was replaced with specific Key and Keys types in both the dataloader.Interface and the Cache interface.

    // dataloader.Interface as now allows interace{} as key rather than string
    - loader.Load(context.Context, key interface{}) Thunk
    + loader.Load(ctx context.Context, key Key) Thunk
    - loader.LoadMany(context.Context, key []interface{}) ThunkMany
    + loader.LoadMany(ctx context.Context, keys Keys) ThunkMany
    - loader.Prime(context.Context, key interface{}, value interface{}) Interface
    + loader.Prime(ctx context.Context, key Key, value interface{}) Interface
    - loader.Clear(context.Context, key interface{}) Interface
    + loader.Clear(ctx context.Context, key Key) Interface
    
    // cache interface now allows interface{} as key instead of string
    type Cache interface {
    - 	Get(context.Context, interface{}) (Thunk, bool)
    + 	Get(context.Context, Key) (Thunk, bool)
    - 	Set(context.Context, interface{}, Thunk)
    + 	Set(context.Context, Key, Thunk)
    - 	Delete(context.Context, interface{}) bool
    + 	Delete(context.Context, Key) bool
    	Clear()
    }
  6. Upgrade from v2 to v3

    main

    In v3, context.Context was added to the Prime, Clear, and Cache interface methods.

    // dataloader.Interface as added context.Context to methods
    - loader.Prime(key string, value interface{}) Interface
    + loader.Prime(ctx context.Context, key string, value interface{}) Interface
    - loader.Clear(key string) Interface
    + loader.Clear(ctx context.Context, key string) Interface
    
    // cache interface as added context.Context to methods
    type Cache interface {
    - 	Get(string) (Thunk, bool)
    + 	Get(context.Context, string) (Thunk, bool)
    - 	Set(string, Thunk)
    + 	Set(context.Context, string, Thunk)
    - 	Delete(string) bool
    + 	Delete(context.Context, string) bool
    	Clear()
    }
  7. Use NoopTracer as a default tracer

    main

    If you do not require tracing, or if you want to provide a default implementation that does nothing, use NoopTracer[K, V]. It implements the Tracer interface with empty functions that do not affect the context or the execution flow.

    // NoopTracer is the default (noop) tracer
    type NoopTracer[K comparable, V any] struct{}
  8. Understand Result and ResultMany types

    main

    The Result and ResultMany types are the containers for data and errors returned by the batching process.

    • Result[V]: Contains Data V and Error error.
    • ResultMany[V]: Contains Data []V and Error []error. The lengths of these slices match the input keys.
    type Result[V any] struct {
    	Data  V
    	Error error
    }
    
    type ResultMany[V any] struct {
    	Data  []V
    	Error []error
    }