@@ -6,64 +6,92 @@ This is a Swift version of the Facebook [DataLoader](https://github.com/facebook
66[ ![ Swift] [ swift-badge ]] [ swift-url ]
77[ ![ License] [ mit-badge ]] [ mit-url ]
88
9- ## Installation 💻
9+ ## Gettings started 🚀
1010
11- Update your ` Package.swift ` file.
11+ Include this repo in your ` Package.swift ` file.
1212
1313``` swift
1414.Package (url : " https://github.com/GraphQLSwift/DataLoader.git" , .upToNextMajor (from : " 1.1.0" ))
1515```
1616
17- ## Gettings started 🚀
18- ### Batching
19- Batching is not an advanced feature, it's DataLoader's primary feature.
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.
2019
21- Create a DataLoader by providing a batch loading function
20+ ## Batching 🍪
21+ Batching is not an advanced feature, it's DataLoader's primary feature.
22+
23+ Create a DataLoader by providing a batch loading function:
2224``` swift
2325let userLoader = Dataloader< Int , User> (batchLoadFunction : { keys in
2426 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+ }
2630 }
2731})
2832```
29- #### Load single key
33+
34+ The order of the returned DataLoaderFutureValues must match the order of the keys.
35+
36+ ### Load individual keys
3037``` swift
3138let future1 = try userLoader.load (key : 1 , on : eventLoopGroup)
3239let future2 = try userLoader.load (key : 2 , on : eventLoopGroup)
3340let future3 = try userLoader.load (key : 1 , on : eventLoopGroup)
3441```
3542
36- Now there is only one thing left and that is to dispathc it ` try userLoader.dispatchQueue(on: req.eventLoop) `
37-
3843The example above will only fetch two users, because the user with key ` 1 ` is present twice in the list.
3944
40- #### Load multiple keys
45+ ### Load multiple keys
4146There is also a method to load multiple keys at once
4247``` swift
4348try userLoader.loadMany (keys : [1 , 2 , 3 ], on : eventLoopGroup)
4449```
4550
46- #### Disable batching
47- It is possible to disable batching ` DataLoaderOptions(batchingEnabled: false) `
48- It will invoke ` batchLoadFunction ` immediately whenever any key is loaded
51+ ### Execution
52+ By default, a DataLoader will wait for a short time from the moment ` load ` is called to collect keys prior
53+ to running the ` batchLoadFunction ` and completing the ` load ` futures. This is to let keys accumulate and
54+ batch into a smaller number of total requests. This amount of time is configurable using the ` executionPeriod `
55+ option:
56+
57+ ``` swift
58+ let myLoader = DataLoader< String , String > (
59+ options : DataLoaderOptions (executionPeriod : .milliseconds (50 )),
60+ batchLoadFunction : { keys in
61+ self .someBatchLoader (keys : keys).map { DataLoaderFutureValue.success ($0 ) }
62+ }
63+ )
64+ ```
65+
66+ Longer execution periods reduce the number of total data requests, but also reduce the responsiveness of the
67+ ` load ` futures.
68+
69+ If desired, you can manually execute the ` batchLoadFunction ` and complete the futures at any time, using the
70+ ` .execute() ` method.
71+
72+ Scheduled execution can be disabled by setting ` executionPeriod ` to ` nil ` , but be careful - you * must* call ` .execute() `
73+ manually in this case. Otherwise, the futures will never complete!
74+
75+ ### Disable batching
76+ It is possible to disable batching by setting the ` batchingEnabled ` option to ` false ` .
77+ In this case, the ` batchLoadFunction ` will be invoked immediately when a key is loaded.
4978
50- ### Caching
5179
52- DataLoader provides a memoization cache for all loads which occur in a single
53- request to your application . After ` .load() ` is called once with a given key,
54- the resulting value is cached to eliminate redundant loads.
80+ ## Caching 💰
81+ DataLoader provides a memoization cache . After ` .load() ` is called with a key, the resulting value is cached
82+ for the lifetime of the DataLoader object. This eliminates redundant loads.
5583
56- In addition to relieving pressure on your data storage, caching results per-request
57- also creates fewer objects which may relieve memory pressure on your application:
84+ In addition to relieving pressure on your data storage, caching results also creates fewer objects which may
85+ relieve memory pressure on your application:
5886
5987``` swift
6088let userLoader = DataLoader< Int , Int > (... )
6189let future1 = userLoader.load (key : 1 , on : eventLoopGroup)
6290let future2 = userLoader.load (key : 1 , on : eventLoopGroup)
63- assert (future1 === future2)
91+ print (future1 == future2) // true
6492```
6593
66- #### Caching per-Request
94+ ### Caching per-Request
6795
6896DataLoader caching * does not* replace Redis, Memcache, or any other shared
6997application-level cache. DataLoader is first and foremost a data loading mechanism,
@@ -76,7 +104,7 @@ could result in cached data incorrectly appearing in each request. Typically,
76104DataLoader instances are created when a Request begins, and are not used once the
77105Request ends.
78106
79- #### Clearing Cache
107+ ### Clearing Cache
80108
81109In certain uncommon cases, clearing the request cache may be necessary.
82110
@@ -94,15 +122,15 @@ let userLoader = DataLoader<Int, Int>(...)
94122userLoader.load (key : 4 , on : eventLoopGroup)
95123
96124// A mutation occurs, invalidating what might be in cache.
97- sqlRun ('UPDATE users WHERE id= 4 SET username= " zuck" ').then { userLoader.clear (4 ) }
125+ sqlRun ('UPDATE users WHERE id= 4 SET username= " zuck" ').whenComplete { userLoader.clear (key : 4 ) }
98126
99127// Later the value load is loaded again so the mutated data appears.
100128userLoader.load (key : 4 , on : eventLoopGroup)
101129
102130// Request completes.
103131```
104132
105- #### Caching Errors
133+ ### Caching Errors
106134
107135If a batch load fails (that is, a batch function throws or returns a DataLoaderFutureValue.failure(Error)),
108136then the requested values will not be cached. However if a batch
@@ -112,15 +140,15 @@ be cached to avoid frequently loading the same `Error`.
112140In some circumstances you may wish to clear the cache for these individual Errors:
113141
114142``` swift
115- userLoader.load (key : 1 , on : eventLoopGroup).catch { error in {
143+ userLoader.load (key : 1 , on : eventLoopGroup).whenFailure { error in
116144 if (/* determine if should clear error */ ) {
117145 userLoader.clear (key : 1 );
118146 }
119147 throw error
120148}
121149```
122150
123- #### Disabling Cache
151+ ### Disabling Cache
124152
125153In certain uncommon cases, a DataLoader which * does not* cache may be desirable.
126154Calling ` DataLoader(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: batchLoadFunction) ` will ensure that every
@@ -162,18 +190,112 @@ let myLoader = DataLoader<String, String>(batchLoadFunction: { keys in
162190})
163191```
164192
193+ ## Using with GraphQL 🎀
194+
195+ DataLoader pairs nicely well with [ GraphQL] ( https://github.com/GraphQLSwift/GraphQL ) and
196+ [ Graphiti] ( https://github.com/GraphQLSwift/Graphiti ) . GraphQL fields are designed to be
197+ stand-alone functions. Without a caching or batching mechanism,
198+ it's easy for a naive GraphQL server to issue new database requests each time a
199+ field is resolved.
200+
201+ Consider the following GraphQL request:
202+
203+ ```
204+ {
205+ me {
206+ name
207+ bestFriend {
208+ name
209+ }
210+ friends(first: 5) {
211+ name
212+ bestFriend {
213+ name
214+ }
215+ }
216+ }
217+ }
218+ ```
219+
220+ Naively, if ` me ` , ` bestFriend ` and ` friends ` each need to request the backend,
221+ there could be at most 12 database requests!
222+
223+ By using DataLoader, we could batch our requests to a ` User ` type, and
224+ only require at most 4 database requests, and possibly fewer if there are cache hits.
225+ Here's a full example using Graphiti:
226+
227+ ``` swift
228+ struct User : Codable {
229+ let id: Int
230+ let name: String
231+ let bestFriendID: Int
232+ let friendIDs: [Int ]
233+
234+ func getBestFriend (context : UserContext, arguments : NoArguments, group : EventLoopGroup) throws -> EventLoopFuture<User> {
235+ return try context.userLoader .load (key : user.bestFriendID , on : group)
236+ }
237+
238+ struct FriendArguments {
239+ first: Int
240+ }
241+ func getFriends (context : UserContext, arguments : FriendArguments, group : EventLoopGroup) throws -> EventLoopFuture<[User]> {
242+ return try context.userLoader .loadMany (keys : user.friendIDs [0 ..< arguments.first ], on : group)
243+ }
244+ }
245+
246+ struct UserResolver {
247+ public func me (context : UserContext, arguments : NoArguments) -> User {
248+ ...
249+ }
250+ }
251+
252+ class UserContext {
253+ let database = ...
254+ let userLoader = DataLoader< Int , User> () { [unowned self ] keys in
255+ return User.query (on : self .database ).filter (\.$id ~~ keys).all ().map { users in
256+ keys.map { key in
257+ users.first { $0 .id == key }!
258+ }
259+ }
260+ }
261+ }
262+
263+ struct UserAPI : API {
264+ let resolver = UserResolver ()
265+ let schema = Schema< UserResolver, UserContext> {
266+ Type (User.self ) {
267+ Field (" name" , at : \.content )
268+ Field (" bestFriend" , at : \.getBestFriend , as : TypeReference< User> .self )
269+ Field (" friends" , at : \.getFriends , as : [TypeReference< User> ]? .self ) {
270+ Argument (" first" , at : .\first)
271+ }
272+ }
273+
274+ Query {
275+ Field (" me" , at : UserResolver.hero , as : User.self )
276+ }
277+ }
278+ }
279+ ```
280+
165281## Contributing 🤘
166282
167- 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. 😎
283+ All your feedback and help to improve this project is very welcome. Please create issues for your bugs, ideas and
284+ enhancement requests, or better yet, contribute directly by creating a PR. 😎
168285
169- When reporting an issue, please add a detailed instruction, and if possible a code snippet or test that can be used as a reproducer of your problem. 💥
286+ When reporting an issue, please add a detailed example, and if possible a code snippet or test
287+ to reproduce your problem. 💥
170288
171- 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 💪
289+ When creating a pull request, please adhere to the current coding style where possible, and create tests with your
290+ code so it keeps providing an awesome test coverage level 💪
172291
173292## Acknowledgements 👏
174293
175- This library is entirely a Swift version of Facebooks [DataLoader](https :// github.com/facebook/dataloader). Developed by [Lee Byron](https://github.com/leebyron) and
176- [Nicholas Schrock](https :// github.com/schrockn) from [Facebook](https://www.facebook.com/).
294+ This library is entirely a Swift version of Facebooks [ DataLoader] ( https://github.com/facebook/dataloader ) .
295+ Developed by [ Lee Byron] ( https://github.com/leebyron ) and [ Nicholas Schrock] ( https://github.com/schrockn )
296+ from [ Facebook] ( https://www.facebook.com/ ) .
297+
298+
177299
178300[ swift-badge ] : https://img.shields.io/badge/Swift-5.2-orange.svg?style=flat
179301[ swift-url ] : https://swift.org
0 commit comments