@@ -6,27 +6,34 @@ This is a Swift version of the Facebook [DataLoader](https://github.com/facebook
6
6
[ ![ Swift] [ swift-badge ]] [ swift-url ]
7
7
[ ![ License] [ mit-badge ]] [ mit-url ]
8
8
9
- ## Installation 💻
9
+ ## Gettings started 🚀
10
10
11
11
Include this repo in your ` Package.swift ` file.
12
12
13
13
``` swift
14
14
.Package (url : " https://github.com/GraphQLSwift/DataLoader.git" , .upToNextMajor (from : " 1.1.0" ))
15
15
```
16
16
17
- ## Gettings started 🚀
18
- ### Batching
17
+ To get started, create a DataLoader. Each DataLoader instance represents a unique cache. Typically instances are created per request when used
18
+ within a web-server if different users can see different things.
19
+
20
+ ## Batching
19
21
Batching is not an advanced feature, it's DataLoader's primary feature.
20
22
21
23
Create a DataLoader by providing a batch loading function:
22
24
``` swift
23
25
let userLoader = Dataloader< Int , User> (batchLoadFunction : { keys in
24
26
try User.query (on : req).filter (\User.id ~~ keys).all ().map { users in
25
- return users.map { DataLoaderFutureValue.success ($0 ) }
27
+ keys.map { key in
28
+ DataLoaderFutureValue.success (users.filter { $0 .id == key })
29
+ }
26
30
}
27
31
})
28
32
```
29
- #### Load individual keys
33
+
34
+ The order of the returned DataLoaderFutureValues must match the order of the keys.
35
+
36
+ ### Load individual keys
30
37
``` swift
31
38
let future1 = try userLoader.load (key : 1 , on : eventLoopGroup)
32
39
let future2 = try userLoader.load (key : 2 , on : eventLoopGroup)
@@ -35,13 +42,13 @@ let future3 = try userLoader.load(key: 1, on: eventLoopGroup)
35
42
36
43
The example above will only fetch two users, because the user with key ` 1 ` is present twice in the list.
37
44
38
- #### Load multiple keys
45
+ ### Load multiple keys
39
46
There is also a method to load multiple keys at once
40
47
``` swift
41
48
try userLoader.loadMany (keys : [1 , 2 , 3 ], on : eventLoopGroup)
42
49
```
43
50
44
- #### Execution
51
+ ### Execution
45
52
By default, a DataLoader will wait for a short time (2ms) from the moment ` load ` is called to collect keys prior
46
53
to running the ` batchLoadFunction ` and completing the ` load ` futures. This is to let keys accumulate and
47
54
batch into a smaller number of total requests. This amount of time is configurable using the ` executionPeriod `
@@ -65,12 +72,12 @@ If desired, you can manually execute the `batchLoadFunction` and complete the fu
65
72
Scheduled execution can be disabled by setting ` executionPeriod ` to ` nil ` , but be careful - you * must* call ` .execute() `
66
73
manually in this case. Otherwise, the futures will never complete.
67
74
68
- #### Disable batching
75
+ ### Disable batching
69
76
It is possible to disable batching by setting the ` batchingEnabled ` option to ` false `
70
77
It will invoke the ` batchLoadFunction ` immediately when a key is loaded.
71
78
72
79
73
- ### Caching
80
+ ## Caching
74
81
75
82
DataLoader provides a memoization cache. After ` .load() ` is called with a key, the resulting value is cached
76
83
for the lifetime of the DataLoader object. This eliminates redundant loads.
@@ -85,7 +92,7 @@ let future2 = userLoader.load(key: 1, on: eventLoopGroup)
85
92
assert (future1 === future2)
86
93
```
87
94
88
- #### Caching per-Request
95
+ ### Caching per-Request
89
96
90
97
DataLoader caching * does not* replace Redis, Memcache, or any other shared
91
98
application-level cache. DataLoader is first and foremost a data loading mechanism,
@@ -98,7 +105,7 @@ could result in cached data incorrectly appearing in each request. Typically,
98
105
DataLoader instances are created when a Request begins, and are not used once the
99
106
Request ends.
100
107
101
- #### Clearing Cache
108
+ ### Clearing Cache
102
109
103
110
In certain uncommon cases, clearing the request cache may be necessary.
104
111
@@ -124,7 +131,7 @@ userLoader.load(key: 4, on: eventLoopGroup)
124
131
// Request completes.
125
132
```
126
133
127
- #### Caching Errors
134
+ ### Caching Errors
128
135
129
136
If a batch load fails (that is, a batch function throws or returns a DataLoaderFutureValue.failure(Error)),
130
137
then the requested values will not be cached. However if a batch
@@ -142,7 +149,7 @@ userLoader.load(key: 1, on: eventLoopGroup).catch { error in {
142
149
}
143
150
```
144
151
145
- #### Disabling Cache
152
+ ### Disabling Cache
146
153
147
154
In certain uncommon cases, a DataLoader which * does not* cache may be desirable.
148
155
Calling `DataLoader (options : DataLoaderOptions (cachingEnabled : false ), batchLoadFunction : batchLoadFunction)` will ensure that every
@@ -184,6 +191,94 @@ let myLoader = DataLoader<String, String>(batchLoadFunction: { keys in
184
191
})
185
192
```
186
193
194
+ ## Using with GraphQL
195
+
196
+ DataLoader pairs nicely well with [GraphQL](https :// github.com/GraphQLSwift/GraphQL) and
197
+ [Graphiti](https :// github.com/GraphQLSwift/Graphiti). GraphQL fields are designed to be
198
+ stand- alone functions. Without a caching or batching mechanism,
199
+ it's easy for a naive GraphQL server to issue new database requests each time a
200
+ field is resolved.
201
+
202
+ Consider the following GraphQL request:
203
+
204
+ ```
205
+ {
206
+ me {
207
+ name
208
+ bestFriend {
209
+ name
210
+ }
211
+ friends (first : 5 ) {
212
+ name
213
+ bestFriend {
214
+ name
215
+ }
216
+ }
217
+ }
218
+ }
219
+ ```
220
+
221
+ Naively, if `me`, `bestFriend` and `friends` each need to request the backend,
222
+ there could be at most 13 database requests!
223
+
224
+ By using DataLoader, we could batch our requests to a `User` type, and
225
+ only require at most 4 database requests, and possibly fewer if there are cache hits.
226
+ Here's a full example using Graphiti:
227
+
228
+ ```swift
229
+ struct User : Codable {
230
+ let id: Int
231
+ let name: String
232
+ let bestFriendID: Int
233
+ let friendIDs: [Int ]
234
+
235
+ func getBestFriend (context : UserContext, arguments : NoArguments, group : EventLoopGroup) throws -> EventLoopFuture<User> {
236
+ return try context.userLoader .load (key : user.bestFriendID , on : group)
237
+ }
238
+
239
+ struct FriendArguments {
240
+ first: Int
241
+ }
242
+ func getFriends (context : UserContext, arguments : FriendArguments, group : EventLoopGroup) throws -> EventLoopFuture<[User]> {
243
+ return try context.userLoader .loadMany (keys : user.friendIDs [0 ..< arguments.first ], on : group)
244
+ }
245
+ }
246
+
247
+ struct UserResolver {
248
+ public func me (context : UserContext, arguments : NoArguments) -> User {
249
+ ...
250
+ }
251
+ }
252
+
253
+ class UserContext {
254
+ let database = ...
255
+ let userLoader = DataLoader< Int , User> () { keys in
256
+ return User.query (on : database).filter (\.$id ~~ keys).all ().map { users in
257
+ keys.map { key in
258
+ users.first { $0 .id == key }!
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ struct UserAPI : API {
265
+ let resolver = UserResolver ()
266
+ let schema = Schema< UserResolver, UserContext> {
267
+ Type (User.self ) {
268
+ Field (" name" , at : \.content )
269
+ Field (" bestFriend" , at : \.getBestFriend , as : TypeReference< User> .self )
270
+ Field (" friends" , at : \.getFriends , as : [TypeReference< User> ]? .self ) {
271
+ Argument (" first" , at : .\first)
272
+ }
273
+ }
274
+
275
+ Query {
276
+ Field (" me" , at : UserResolver.hero , as : User.self )
277
+ }
278
+ }
279
+ }
280
+ ```
281
+
187
282
## Contributing 🤘
188
283
189
284
All your feedback and help to improve this project is very welcome. Please create issues for your bugs, ideas and
@@ -201,6 +296,8 @@ This library is entirely a Swift version of Facebooks [DataLoader](https://githu
201
296
Developed by [Lee Byron](https :// github.com/leebyron) and [Nicholas Schrock](https://github.com/schrockn)
202
297
from [Facebook](https :// www.facebook.com/).
203
298
299
+
300
+
204
301
[swift- badge]: https: // img.shields.io/badge/Swift-5.2-orange.svg?style=flat
205
302
[swift- url]: https: // swift.org
206
303
0 commit comments