|
1 |
| -# SwiftDataLoader |
| 1 | +# SwiftDataLoader |
| 2 | +SwiftDataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching. |
| 3 | + |
| 4 | +This is a Swift version of the Facebook [DataLoader](https://github.com/facebook/dataloader). |
| 5 | + |
| 6 | +[](https://circleci.com/gh/kimdv/SwiftDataLoader) |
| 7 | +[](https://codecov.io/gh/kimdv/SwiftDataLoader) |
| 8 | + |
| 9 | +## Installation 💻 |
| 10 | + |
| 11 | +Update your `Package.swift` file. |
| 12 | + |
| 13 | +```swift |
| 14 | +.Package(url: "https://github.com/kimdv/SwiftDataLoader.git", majorVersion: 1) |
| 15 | +``` |
| 16 | + |
| 17 | +## Gettings started 🚀 |
| 18 | +### Batching |
| 19 | +Batching is not an advanced feature, it's DataLoader's primary feature. |
| 20 | + |
| 21 | +Create a DataLoader by providing a batch loading function |
| 22 | +```swift |
| 23 | +let userLoader = Dataloader<Int, User>(batchLoadFunction: { keys in |
| 24 | + try User.query(on: req).filter(\User.id ~~ keys).all().map { users in |
| 25 | + return users.map { DataLoaderFutureValue.success($0) } |
| 26 | + } |
| 27 | +}) |
| 28 | +``` |
| 29 | +#### Load single key |
| 30 | +``` |
| 31 | +let future1 = try userLoader.load(key: 1, on: req) |
| 32 | +let future2 = try userLoader.load(key: 2, on: req) |
| 33 | +let future3 = try userLoader.load(key: 1, on: req) |
| 34 | +``` |
| 35 | + |
| 36 | +Now there is only one thing left and that is to dispathc it `try userLoader.dispatchQueue(on: req.eventLoop)` |
| 37 | + |
| 38 | +The example above will only fetch users twice because `future1 == future3` |
| 39 | + |
| 40 | +#### Load multiple keys |
| 41 | +There is also an API to load multiple keys at once |
| 42 | +``` |
| 43 | +try userLoader.loadMany(keys: [1, 2, 3], on: req.eventLoop) |
| 44 | +``` |
| 45 | + |
| 46 | +### Disable batching |
| 47 | +It is also possible to disable batching `DataLoaderOptions(batchingEnabled: false)` |
| 48 | +It will invoke `batchLoadFunction` with a single key |
| 49 | + |
| 50 | +### Chaching |
| 51 | + |
| 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. |
| 55 | + |
| 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: |
| 58 | + |
| 59 | +```swift |
| 60 | +let userLoader = DataLoader<Int, Int>(...) |
| 61 | +let future1 = userLoader.load(1) |
| 62 | +let future2 = userLoader.load(1) |
| 63 | +assert(future1 === future2) |
| 64 | +``` |
| 65 | + |
| 66 | +#### Caching per-Request |
| 67 | + |
| 68 | +DataLoader caching *does not* replace Redis, Memcache, or any other shared |
| 69 | +application-level cache. DataLoader is first and foremost a data loading mechanism, |
| 70 | +and its cache only serves the purpose of not repeatedly loading the same data in |
| 71 | +the context of a single request to your Application. To do this, it maintains a |
| 72 | +simple in-memory memoization cache (more accurately: `.load()` is a memoized function). |
| 73 | + |
| 74 | +Avoid multiple requests from different users using the DataLoader instance, which |
| 75 | +could result in cached data incorrectly appearing in each request. Typically, |
| 76 | +DataLoader instances are created when a Request begins, and are not used once the |
| 77 | +Request ends. |
| 78 | + |
| 79 | +#### Clearing Cache |
| 80 | + |
| 81 | +In certain uncommon cases, clearing the request cache may be necessary. |
| 82 | + |
| 83 | +The most common example when clearing the loader's cache is necessary is after |
| 84 | +a mutation or update within the same request, when a cached value could be out of |
| 85 | +date and future loads should not use any possibly cached value. |
| 86 | + |
| 87 | +Here's a simple example using SQL UPDATE to illustrate. |
| 88 | + |
| 89 | +```swift |
| 90 | +// Request begins... |
| 91 | +let userLoader = DataLoader<Int, Int>(...) |
| 92 | + |
| 93 | +// And a value happens to be loaded (and cached). |
| 94 | +userLoader.load(4) |
| 95 | + |
| 96 | +// A mutation occurs, invalidating what might be in cache. |
| 97 | +sqlRun('UPDATE users WHERE id=4 SET username="zuck"').then { userLoader.clear(4) } |
| 98 | + |
| 99 | +// Later the value load is loaded again so the mutated data appears. |
| 100 | +userLoader.load(4) |
| 101 | + |
| 102 | +// Request completes. |
| 103 | +``` |
| 104 | + |
| 105 | +#### Caching Errors |
| 106 | + |
| 107 | +If a batch load fails (that is, a batch function throws or returns a DataLoaderFutureValue.failure(Error)), |
| 108 | +then the requested values will not be cached. However if a batch |
| 109 | +function returns an `Error` instance for an individual value, that `Error` will |
| 110 | +be cached to avoid frequently loading the same `Error`. |
| 111 | + |
| 112 | +In some circumstances you may wish to clear the cache for these individual Errors: |
| 113 | + |
| 114 | +```swift |
| 115 | +userLoader.load(1).catch { error in { |
| 116 | + if (/* determine if should clear error */) { |
| 117 | + userLoader.clear(1); |
| 118 | + } |
| 119 | + throw error |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +#### Disabling Cache |
| 124 | + |
| 125 | +In certain uncommon cases, a DataLoader which *does not* cache may be desirable. |
| 126 | +Calling `DataLoader(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: batchLoadFunction)` will ensure that every |
| 127 | +call to `.load()` will produce a *new* Future, and requested keys will not be |
| 128 | +saved in memory. |
| 129 | + |
| 130 | +However, when the memoization cache is disabled, your batch function will |
| 131 | +receive an array of keys which may contain duplicates! Each key will be |
| 132 | +associated with each call to `.load()`. Your batch loader should provide a value |
| 133 | +for each instance of the requested key. |
| 134 | + |
| 135 | +For example: |
| 136 | + |
| 137 | +```swift |
| 138 | +let myLoader = DataLoader<String, String>(options: DataLoaderOptions(cachingEnabled: false), batchLoadFunction: { keys in |
| 139 | + self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) } |
| 140 | +}) |
| 141 | + |
| 142 | +myLoader.load("A") |
| 143 | +myLoader.load("B") |
| 144 | +myLoader.load("A") |
| 145 | + |
| 146 | +// > [ "A", "B", "A" ] |
| 147 | +``` |
| 148 | + |
| 149 | +More complex cache behavior can be achieved by calling `.clear()` or `.clearAll()` |
| 150 | +rather than disabling the cache completely. For example, this DataLoader will |
| 151 | +provide unique keys to a batch function due to the memoization cache being |
| 152 | +enabled, but will immediately clear its cache when the batch function is called |
| 153 | +so later requests will load new values. |
| 154 | + |
| 155 | +```swift |
| 156 | +let myLoader = DataLoader<String, String>(batchLoadFunction: { keys in |
| 157 | + identityLoader.clearAll() |
| 158 | + return someBatchLoad(keys: keys) |
| 159 | +}) |
| 160 | +``` |
| 161 | + |
| 162 | +## Contributing 🤘 |
| 163 | + |
| 164 | +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. 😎 |
| 165 | + |
| 166 | +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. 💥 |
| 167 | + |
| 168 | +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 💪 |
| 169 | + |
| 170 | +## Acknowledgements 👏 |
| 171 | + |
| 172 | +This library is entirely a Swift version of Facebooks [DataLoader](https://github.com/facebook/dataloader). Developed by [Lee Byron](https://github.com/leebyron) and |
| 173 | +[Nicholas Schrock](https://github.com/schrockn) from [Facebook](https://www.facebook.com/). |
0 commit comments