Skip to content
This repository has been archived by the owner on Sep 5, 2019. It is now read-only.
/ greendonut Public archive

Green Donut is a port of facebook's DataLoader utility, written in C# for .NET Core and .NET Framework

License

Notifications You must be signed in to change notification settings

ChilliCream/greendonut

Repository files navigation

As of know Green Donut is part of the Hot Chocolate GraphQL server repository and therefore maintained there for easiness. GreenDonat remains as an independant product. The codebase can be found here.


GreenDonut

GitHub release NuGet Package License Build Tests Coverage Status Quality BCH compliance Join the chat at https://gitter.im/ChilliCream/greendonut


Green Donut is a port of facebook's DataLoader utility, written in C# for .NET Core and .NET Framework.

DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a consistent API over various backends and reduce requests to those backends via batching and caching. -- facebook

DataLoader are perfect in various client-side and server-side scenarios. Although, they are usually know for solving the N+1 problem in GraphQL APIs. DataLoader decouple any kind of request in a simplified way to a backend resource like a database or a web service to reduce the overall traffic to those resources by using two common techniques in computer science namely batching and caching. With batching we decrease the amount of requests to a backend resource by grouping single requests into one batch request. Whereas with caching we avoid requesting a backend resource at all.

Getting Started

First things first, install the package via NuGet.

For .NET Core we use the dotnet CLI, which is perhaps the preferred way doing this.

dotnet add package GreenDonut

And for .NET Framework we still use the following line.

Install-Package GreenDonut

People who prefer a UI to install packages might wanne use the NuGet Package Manager, which is provided by Visual Studio.

After we have installed the package, we should probably start using it, right. We really tried to keep the API of DataLoader congruent to the original facebook implementation which is written in JavaScript, but without making the experience for us .NET developers weird.

Example

The simplest way to get started is to create an instance of the default DataLoader implementation, which might be the right choice if you need just one type of DataLoader. However, if you need a bunch of individual DataLoader and/or using DI, which is an abbreviation for Dependency Injection, you might wanne also take a look at the Custom DataLoader section.

Create a new instance

Creating a new instance is easy as you will see in the following example. The tricky part here is to implement our data fetching logic - here shown as FetchUsers - which depends on our backend resource. Once we have done that, we just pass our fetch function into the DataLoader constructor. That's actually everything so far.

var userLoader = new DataLoader<string, User>(FetchUsers);

In order to change the default behavior of a DataLoader, we have to create a new instance of DataLoaderOptions and pass it right into the DataLoader constructor. Lets see how that looks like.

var options = new DataLoaderOptions<string>
{
    SlidingExpiration = TimeSpan.FromHours(1)
};
var userLoader = new DataLoader<string, User>(keys => FetchUsers(keys), options);

So, what we see here is that we have changed the SlidingExpiration from its default value, which is 0 to 1 hour. 0 means the cache entries will live forever in the cache as long as the maximum cache size does not exceed. Whereas 1 hour means a single cache entry will stay in the cache as long as the entry gets touched within one hour. This is an additional feature that does not exist in the original facebook implementation.

Fetching data

Fetching data consists of two parts. First part is declaring your need in one or more data items by providing one or more keys.

await userLoader.LoadAsync("Foo", "Bar", "Baz");

The second part is dispatching our requested data items. There are two options. First option is manual dispatching the default behavior as of version 2.0.0. As the name says, manual dispatching means we have to trigger the dispatching process manually; otherwise no data is being fetched. This is actually an important difference to facebook's original implementation, which is written in JavaScript. Facebook's implementation is using a trick in NodeJs to dispatch automatically. If you're interested how that works, click here to learn more about that. But now lets see how we trigger the dispatching process manually.

await userLoader.DispatchAsync();

The second option is, we enable auto dispatching which dispatches permanently in the background. This process starts immediately after creating a new instance of the DataLoader. Lets see how that looks like.

var options = new DataLoaderOptions<string>
{
    AutoDispatching = true
};
var userLoader = new DataLoader<string, User>(FetchUsers, options);

In this case we wouldn't need to call DispatchAsync at all.

Custom DataLoader

A custom DataLoader is especially useful in DI scenarios.

public interface IUserDataLoader
    : IDataLoader<string, User>
{ }

Although the extra interface IUserDataLoader isn't necessarily required, we strongly recommend to create an extra interface in this particular case because of several reasons. One reason is you might have a handful of DataLoader which implemanting a completely different data fetching logic, but from the outside they look identic due to their identic type parameter list. That's why we should always create a separate interface for each DataLoader. We just mentioned one reason here because the explanation would go beyond the scope of custom DataLoader.

public class UserDataLoader
    : DataLoaderBase<string, User>
    , IUserDataLoader
{
    protected override Task<IReadOnlyList<Result<User>>> Fetch(IReadOnlyList<string> keys)
    {
        // Here goes our data fetching logic
    }
}

API

The API shown here is simplified. Means we have omitted some information for brevity purpose like type information, method overloads, return values and so on. If you're interested in those kind of information - and we bet you're - then click here for being transferred to our documentation.

Events

Name Description
RequestBuffered Raises when an incoming data request is added to the buffer. Will never be raised if batching is disabled.

Properties

Name Description
BufferedRequests Gets the current count of buffered data requests waiting for being dispatched as batches. Will always return 0 if batching is disabled.
CachedValues Gets the current count of cached values. Will always return 0 if caching is disabled.

Methods

Name Description
Clear() Empties the complete cache.
DispatchAsync() Dispatches one or more batch requests. In case of auto dispatching we just trigger an implicit dispatch which could mean to interrupt a wait delay. Whereas in a manual dispatch scenario it could mean to dispatch explicitly.
LoadAsync(key) Loads a single value by key. This call may return a cached value or enqueues this single request for bacthing if enabled.
LoadAsync(keys) Loads multiple values by keys. This call may return cached values and enqueues requests which were not cached for bacthing if enabled.
Remove(key) Removes a single entry from the cache.
Set(key, value) Adds a new entry to the cache if not already exists.

Best Practise

  • Consider using a DataLoader instance per request if the results may differ due to user privileges for instance.

Documentation

Click here for more documentation.