Skip to content

Commit 6cbaae0

Browse files
Merge pull request #28 from NeedleInAJayStack/docs/readme-update
docs: Updates readme to use Async
2 parents 15ff327 + fab5315 commit 6cbaae0

1 file changed

Lines changed: 60 additions & 105 deletions

File tree

README.md

Lines changed: 60 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,25 @@ DataLoader is a generic utility to be used as part of your application's data fe
88

99
This is a Swift version of the Facebook [DataLoader](https://github.com/facebook/dataloader).
1010

11-
## Gettings started 🚀
11+
## Getting started 🚀
1212

1313
Include this repo in your `Package.swift` file.
1414

1515
```swift
16-
.Package(url: "https://github.com/GraphQLSwift/DataLoader.git", .upToNextMajor(from: "1.1.0"))
16+
.package(url: "https://github.com/GraphQLSwift/DataLoader.git", from: "2.0.0")
1717
```
1818

19-
To get started, create a DataLoader. Each DataLoader instance represents a unique cache. Typically instances are created per request when used
20-
within a web-server if different users can see different things.
19+
The `AsyncDataLoader` library is preferred. The `DataLoader` uses NIO for concurrency and is provided for backwards compatibility.
20+
21+
To get started, create a DataLoader. Each DataLoader instance represents a unique cache. Typically instances are created per request when used within a web-server if different users can see different things.
2122

2223
## Batching 🍪
23-
Batching is not an advanced feature, it's DataLoader's primary feature.
24+
Batching is not an advanced feature, it's DataLoader's primary feature. Create a DataLoader by providing a batch loading function:
2425

25-
Create a DataLoader by providing a batch loading function:
2626
```swift
27-
let userLoader = Dataloader<Int, User>(batchLoadFunction: { keys in
27+
import AsyncDataLoader
28+
29+
let userLoader = DataLoader<Int, User>(batchLoadFunction: { keys in
2830
try User.query(on: req).filter(\User.id ~~ keys).all().map { users in
2931
keys.map { key in
3032
DataLoaderFutureValue.success(users.filter{ $0.id == key })
@@ -33,86 +35,65 @@ let userLoader = Dataloader<Int, User>(batchLoadFunction: { keys in
3335
})
3436
```
3537

36-
The order of the returned DataLoaderFutureValues must match the order of the keys.
38+
The order of the returned DataLoaderFutureValues must match the order of the input keys.
3739

3840
### Load individual keys
3941
```swift
40-
let future1 = try userLoader.load(key: 1, on: eventLoopGroup)
41-
let future2 = try userLoader.load(key: 2, on: eventLoopGroup)
42-
let future3 = try userLoader.load(key: 1, on: eventLoopGroup)
42+
async let result1 = userLoader.load(key: 1)
43+
async let result2 = userLoader.load(key: 2)
44+
async let result3 = userLoader.load(key: 1)
4345
```
4446

4547
The example above will only fetch two users, because the user with key `1` is present twice in the list.
4648

4749
### Load multiple keys
4850
There is also a method to load multiple keys at once
4951
```swift
50-
try userLoader.loadMany(keys: [1, 2, 3], on: eventLoopGroup)
52+
try await userLoader.loadMany(keys: [1, 2, 3])
5153
```
5254

5355
### Execution
54-
By default, a DataLoader will wait for a short time from the moment `load` is called to collect keys prior
55-
to running the `batchLoadFunction` and completing the `load` futures. This is to let keys accumulate and
56-
batch into a smaller number of total requests. This amount of time is configurable using the `executionPeriod`
57-
option:
56+
By default, a DataLoader will wait for a short time from the moment `load` is called to collect keys prior to running the `batchLoadFunction` and completing the `load` results. This allows keys to accumulate and batch into a smaller number of total requests. This amount of time is configurable using the `executionPeriod` option:
5857

5958
```swift
60-
let myLoader = DataLoader<String, String>(
59+
let myLoader = DataLoader<String, String>(
6160
options: DataLoaderOptions(executionPeriod: .milliseconds(50)),
6261
batchLoadFunction: { keys in
6362
self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) }
6463
}
6564
)
6665
```
6766

68-
Longer execution periods reduce the number of total data requests, but also reduce the responsiveness of the
69-
`load` futures.
67+
Longer execution periods reduce the number of total data requests, but also reduce the responsiveness of the `load` futures.
7068

71-
If desired, you can manually execute the `batchLoadFunction` and complete the futures at any time, using the
72-
`.execute()` method.
69+
If desired, you can manually execute the `batchLoadFunction` and complete the futures at any time, using the `.execute()` method.
7370

74-
Scheduled execution can be disabled by setting `executionPeriod` to `nil`, but be careful - you *must* call `.execute()`
75-
manually in this case. Otherwise, the futures will never complete!
71+
Scheduled execution can be disabled by setting `executionPeriod` to `nil`, but be careful - you *must* call `.execute()` manually in this case. Otherwise, the futures will never complete!
7672

7773
### Disable batching
78-
It is possible to disable batching by setting the `batchingEnabled` option to `false`.
79-
In this case, the `batchLoadFunction` will be invoked immediately when a key is loaded.
80-
74+
It is possible to disable batching by setting `batchingEnabled` to `false`. In this case, the `batchLoadFunction` will be invoked immediately when a key is loaded.
8175

8276
## Caching 💰
83-
DataLoader provides a memoization cache. After `.load()` is called with a key, the resulting value is cached
84-
for the lifetime of the DataLoader object. This eliminates redundant loads.
77+
DataLoader provides a memoization cache. After `.load()` is called with a key, the resulting value is cached for the lifetime of the DataLoader object. This eliminates redundant loads.
8578

86-
In addition to relieving pressure on your data storage, caching results also creates fewer objects which may
87-
relieve memory pressure on your application:
79+
In addition to relieving pressure on your data storage, caching results also creates fewer objects which may relieve memory pressure on your application:
8880

8981
```swift
9082
let userLoader = DataLoader<Int, Int>(...)
91-
let future1 = userLoader.load(key: 1, on: eventLoopGroup)
92-
let future2 = userLoader.load(key: 1, on: eventLoopGroup)
93-
print(future1 == future2) // true
83+
async let result1 = userLoader.load(key: 1)
84+
async let result2 = userLoader.load(key: 1)
85+
await print(result1 == result2) // true
9486
```
9587

9688
### Caching per-Request
89+
DataLoader caching *does not* replace Redis, Memcache, or any other shared application-level cache. DataLoader is first and foremost a data loading mechanism, and its cache only serves the purpose of not repeatedly loading the same data in the context of a single request to your Application. To do this, it maintains a simple in-memory memoization cache (more accurately: `.load()` is a memoized function).
9790

98-
DataLoader caching *does not* replace Redis, Memcache, or any other shared
99-
application-level cache. DataLoader is first and foremost a data loading mechanism,
100-
and its cache only serves the purpose of not repeatedly loading the same data in
101-
the context of a single request to your Application. To do this, it maintains a
102-
simple in-memory memoization cache (more accurately: `.load()` is a memoized function).
103-
104-
Avoid multiple requests from different users using the DataLoader instance, which
105-
could result in cached data incorrectly appearing in each request. Typically,
106-
DataLoader instances are created when a Request begins, and are not used once the
107-
Request ends.
91+
Avoid multiple requests from different users using the DataLoader instance, which could result in cached data incorrectly appearing in each request. Typically, DataLoader instances are created when a Request begins, and are not used once the Request ends.
10892

10993
### Clearing Cache
110-
11194
In certain uncommon cases, clearing the request cache may be necessary.
11295

113-
The most common example when clearing the loader's cache is necessary is after
114-
a mutation or update within the same request, when a cached value could be out of
115-
date and future loads should not use any possibly cached value.
96+
The most common example when clearing the loader's cache is necessary is after a mutation or update within the same request, when a cached value could be out of date and future loads should not use any possibly cached value.
11697

11798
Here's a simple example using SQL UPDATE to illustrate.
11899

@@ -121,84 +102,68 @@ Here's a simple example using SQL UPDATE to illustrate.
121102
let userLoader = DataLoader<Int, Int>(...)
122103

123104
// And a value happens to be loaded (and cached).
124-
userLoader.load(key: 4, on: eventLoopGroup)
105+
try await userLoader.load(key: 4)
125106

126107
// A mutation occurs, invalidating what might be in cache.
127-
sqlRun('UPDATE users WHERE id=4 SET username="zuck"').whenComplete { userLoader.clear(key: 4) }
108+
try await sqlRun('UPDATE users WHERE id=4 SET username="zuck"')
109+
await userLoader.clear(key: 4)
128110

129111
// Later the value load is loaded again so the mutated data appears.
130-
userLoader.load(key: 4, on: eventLoopGroup)
112+
try await userLoader.load(key: 4)
131113

132114
// Request completes.
133115
```
134116

135117
### Caching Errors
136-
137-
If a batch load fails (that is, a batch function throws or returns a DataLoaderFutureValue.failure(Error)),
138-
then the requested values will not be cached. However if a batch
139-
function returns an `Error` instance for an individual value, that `Error` will
140-
be cached to avoid frequently loading the same `Error`.
118+
If a batch load fails (that is, a batch function throws or returns a DataLoaderFutureValue.failure(Error)), then the requested values will not be cached. However if a batch function returns an `Error` instance for an individual value, that `Error` will be cached to avoid frequently loading the same `Error`.
141119

142120
In some circumstances you may wish to clear the cache for these individual Errors:
143121

144122
```swift
145-
userLoader.load(key: 1, on: eventLoopGroup).whenFailure { error in
123+
do {
124+
try await userLoader.load(key: 1)
125+
} catch {
146126
if (/* determine if should clear error */) {
147-
userLoader.clear(key: 1);
127+
await userLoader.clear(key: 1);
148128
}
149129
throw error
150130
}
151131
```
152132

153133
### Disabling Cache
134+
In certain uncommon cases, a DataLoader which *does not* cache may be desirable. Calling `DataLoader(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: batchLoadFunction)` will ensure that every call to `.load()` will produce a *new* Future, and previously requested keys will not be saved in memory.
154135

155-
In certain uncommon cases, a DataLoader which *does not* cache may be desirable.
156-
Calling `DataLoader(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: batchLoadFunction)` will ensure that every
157-
call to `.load()` will produce a *new* Future, and previously requested keys will not be
158-
saved in memory.
159-
160-
However, when the memoization cache is disabled, your batch function will
161-
receive an array of keys which may contain duplicates! Each key will be
162-
associated with each call to `.load()`. Your batch loader should provide a value
163-
for each instance of the requested key.
136+
However, when the memoization cache is disabled, your batch function will receive an array of keys which may contain duplicates! Each key will be associated with each call to `.load()`. Your batch loader should provide a value for each instance of the requested key.
164137

165138
For example:
166139

167140
```swift
168-
let myLoader = DataLoader<String, String>(
141+
let myLoader = DataLoader<String, String>(
169142
options: DataLoaderOptions(cachingEnabled: false),
170143
batchLoadFunction: { keys in
171144
self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) }
172145
}
173146
)
174147

175-
myLoader.load(key: "A", on: eventLoopGroup)
176-
myLoader.load(key: "B", on: eventLoopGroup)
177-
myLoader.load(key: "A", on: eventLoopGroup)
148+
try await myLoader.load(key: "A")
149+
try await myLoader.load(key: "B")
150+
try await myLoader.load(key: "A")
178151

179152
// > [ "A", "B", "A" ]
180153
```
181154

182-
More complex cache behavior can be achieved by calling `.clear()` or `.clearAll()`
183-
rather than disabling the cache completely. For example, this DataLoader will
184-
provide unique keys to a batch function due to the memoization cache being
185-
enabled, but will immediately clear its cache when the batch function is called
186-
so later requests will load new values.
155+
More complex cache behavior can be achieved by calling `.clear()` or `.clearAll()` rather than disabling the cache completely. For example, this DataLoader will provide unique keys to a batch function due to the memoization cache being enabled, but will immediately clear its cache when the batch function is called so later requests will load new values.
187156

188157
```swift
189158
let myLoader = DataLoader<String, String>(batchLoadFunction: { keys in
190-
identityLoader.clearAll()
159+
await identityLoader.clearAll()
191160
return someBatchLoad(keys: keys)
192161
})
193162
```
194163

195164
## Using with GraphQL 🎀
196165

197-
DataLoader pairs nicely well with [GraphQL](https://github.com/GraphQLSwift/GraphQL) and
198-
[Graphiti](https://github.com/GraphQLSwift/Graphiti). GraphQL fields are designed to be
199-
stand-alone functions. Without a caching or batching mechanism,
200-
it's easy for a naive GraphQL server to issue new database requests each time a
201-
field is resolved.
166+
DataLoader pairs nicely well with [GraphQL](https://github.com/GraphQLSwift/GraphQL) and [Graphiti](https://github.com/GraphQLSwift/Graphiti). GraphQL fields are designed to be stand-alone functions. Without a caching or batching mechanism, it's easy for a naive GraphQL server to issue new database requests each time a field is resolved.
202167

203168
Consider the following GraphQL request:
204169

@@ -219,12 +184,9 @@ Consider the following GraphQL request:
219184
}
220185
```
221186

222-
Naively, if `me`, `bestFriend` and `friends` each need to request the backend,
223-
there could be at most 12 database requests!
187+
Naively, if `me`, `bestFriend` and `friends` each need to request the backend, there could be at most 12 database requests!
224188

225-
By using DataLoader, we could batch our requests to a `User` type, and
226-
only require at most 4 database requests, and possibly fewer if there are cache hits.
227-
Here's a full example using Graphiti:
189+
By using DataLoader, we could batch our requests to a `User` type, and only require at most 4 database requests, and possibly fewer if there are cache hits. Here's a full example using Graphiti:
228190

229191
```swift
230192
struct User : Codable {
@@ -233,15 +195,15 @@ struct User : Codable {
233195
let bestFriendID: Int
234196
let friendIDs: [Int]
235197

236-
func getBestFriend(context: UserContext, arguments: NoArguments, group: EventLoopGroup) throws -> EventLoopFuture<User> {
237-
return try context.userLoader.load(key: user.bestFriendID, on: group)
198+
func getBestFriend(context: UserContext, arguments: NoArguments) throws -> User {
199+
return try await context.userLoader.load(key: user.bestFriendID)
238200
}
239201

240202
struct FriendArguments {
241203
first: Int
242204
}
243-
func getFriends(context: UserContext, arguments: FriendArguments, group: EventLoopGroup) throws -> EventLoopFuture<[User]> {
244-
return try context.userLoader.loadMany(keys: user.friendIDs[0..<arguments.first], on: group)
205+
func getFriends(context: UserContext, arguments: FriendArguments) throws -> [User] {
206+
return try await context.userLoader.loadMany(keys: user.friendIDs[0..<arguments.first])
245207
}
246208
}
247209

@@ -255,10 +217,9 @@ class UserContext {
255217
let database = ...
256218
let userLoader = DataLoader<Int, User>() { [weak self] keys in
257219
guard let self = self else { throw ContextError }
258-
return User.query(on: self.database).filter(\.$id ~~ keys).all().map { users in
259-
keys.map { key in
260-
users.first { $0.id == key }!
261-
}
220+
let users = try await User.query(on: self.database).filter(\.$id ~~ keys).all()
221+
return keys.map { key in
222+
users.first { $0.id == key }!
262223
}
263224
}
264225
}
@@ -283,24 +244,18 @@ struct UserAPI : API {
283244

284245
## Contributing 🤘
285246

286-
All your feedback and help to improve this project is very welcome. Please create issues for your bugs, ideas and
287-
enhancement requests, or better yet, contribute directly by creating a PR. 😎
247+
All your feedback and help to improve this project is very welcome. Please create issues for your bugs, ideas and enhancement requests, or better yet, contribute directly by creating a PR. 😎
288248

289-
When reporting an issue, please add a detailed example, and if possible a code snippet or test
290-
to reproduce your problem. 💥
249+
When reporting an issue, please add a detailed example, and if possible a code snippet or test to reproduce your problem. 💥
291250

292-
When creating a pull request, please adhere to the current coding style where possible, and create tests with your
293-
code so it keeps providing an awesome test coverage level 💪
251+
When creating a pull request, please adhere to the current coding style where possible, and create tests with your code so it keeps providing an awesome test coverage level 💪
294252

295-
This repo uses [SwiftFormat](https://github.com/nicklockwood/SwiftFormat), and includes lint checks to enforce these formatting standards.
296-
To format your code, install `swiftformat` and run:
253+
This repo uses [SwiftFormat](https://github.com/nicklockwood/SwiftFormat), and includes lint checks to enforce these formatting standards. To format your code, install `swiftformat` and run:
297254

298255
```bash
299256
swiftformat .
300257
```
301258

302259
## Acknowledgements 👏
303260

304-
This library is entirely a Swift version of Facebooks [DataLoader](https://github.com/facebook/dataloader).
305-
Developed by [Lee Byron](https://github.com/leebyron) and [Nicholas Schrock](https://github.com/schrockn)
306-
from [Facebook](https://www.facebook.com/).
261+
This library is entirely a Swift version of Facebook's [DataLoader](https://github.com/facebook/dataloader). Developed by [Lee Byron](https://github.com/leebyron) and [Nicholas Schrock](https://github.com/schrockn) from [Facebook](https://www.facebook.com/).

0 commit comments

Comments
 (0)