Skip to content

Commit 7e1ac35

Browse files
committed
Readme updates
1 parent 3df56db commit 7e1ac35

File tree

4 files changed

+196
-7
lines changed

4 files changed

+196
-7
lines changed

README.md

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,85 @@ and Nicholas Schrock (@schrockn) from [Facebook](https://www.facebook.com/), the
4949
- Schedule a load request in queue for batching
5050
- Add load requests from anywhere in code
5151
- Request returns a [`CompleteableFuture<V>`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) of the requested value
52-
- Can create multiple requests at once, returns a `CombinedFutures` object
52+
- Can create multiple requests at once
5353
- Caches load requests, so data is only fetched once
54-
- Can clear individual cache keys, so data is fetched on next batch queue dispatch
54+
- Can clear individual cache keys, so data is re-fetched on next batch queue dispatch
5555
- Can prime the cache with key/values, to avoid data being fetched needlessly
5656
- Can configure cache key function with lambda expression to extract cache key from complex data loader key types
57-
- Dispatch load request queue after batch is prepared, also returns `CombinedFutures`
5857
- Individual batch futures complete / resolve as batch is processed
59-
- `CombinedFutures` results are ordered according to insertion order of load requests
58+
- Results are ordered according to insertion order of load requests
6059
- Deals with partial errors when a batch future fails
6160
- Can disable batching and/or caching in configuration
6261
- Can supply your own [`CacheMap<K, V>`](https://github.com/bbakerman/java-dataloader/blob/master/src/main/java/io/engagingspaces/vertx/dataloader/CacheMap.java) implementations
6362
- Has very high test coverage (see [Acknowledgements](#acknowlegdements))
6463

64+
## Examples
65+
66+
A `DataLoader` object requires a `BatchLoader` function that is responsible for loading a promise of values given
67+
a list of keys
68+
69+
```java
70+
71+
BatchLoader<Long, User> userBatchLoader = new BatchLoader<Long, User>() {
72+
@Override
73+
public PromisedValues<User> load(List<Long> userIds) {
74+
List<CompletableFuture<User>> futures = userIds.stream()
75+
.map(userId ->
76+
CompletableFuture.supplyAsync(() ->
77+
userManager.loadUsersById(userId)))
78+
.collect(Collectors.toList());
79+
return PromisedValues.allOf(futures);
80+
}
81+
};
82+
DataLoader<Long, User> userLoader = new DataLoader<>(userBatchLoader);
83+
84+
```
85+
86+
You can then use it to load values which will be `CompleteableFuture` promises to values
87+
88+
```java
89+
CompletableFuture<User> load1 = userLoader.load(1L);
90+
```
91+
92+
or you can use it to compose future computations as follows. The key requirement is that you call
93+
`dataloader.dispatch()` at some point in order to make the underlying calls happen to the batch loader.
94+
In this version of data loader, this does not happen automatically. More on this later.
95+
96+
```java
97+
userLoader.load(1L)
98+
.thenAccept(user -> {
99+
System.out.println("user = " + user);
100+
userLoader.load(user.getInvitedByID())
101+
.thenAccept(invitedBy -> {
102+
System.out.println("invitedBy = " + invitedBy);
103+
});
104+
});
105+
106+
userLoader.load(2L)
107+
.thenAccept(user -> {
108+
System.out.println("user = " + user);
109+
userLoader.load(user.getInvitedByID())
110+
.thenAccept(invitedBy -> {
111+
System.out.println("invitedBy = " + invitedBy);
112+
});
113+
});
114+
115+
userLoader.dispatch().join();
116+
117+
```
118+
119+
As stated on the original Facebook project :
120+
121+
>A naive application may have issued four round-trips to a backend for the required information,
122+
but with DataLoader this application will make at most two.
123+
124+
> DataLoader allows you to decouple unrelated parts of your application without sacrificing the
125+
performance of batch data-loading. While the loader presents an API that loads individual values, all
126+
concurrent requests will be coalesced and presented to your batch loading function. This allows your
127+
application to safely distribute data fetching requirements throughout your application and
128+
maintain minimal outgoing data requests.
129+
130+
65131
## Differences to reference implementation
66132

67133
### Manual dispatching
@@ -118,9 +184,6 @@ To build from source use the Gradle wrapper:
118184
./gradlew clean build
119185
```
120186

121-
### Using
122-
123-
124187
## Project plans
125188

126189
### Current releases

src/main/java/org/dataloader/PromisedValues.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.dataloader.impl.PromisedValuesImpl;
44

55
import java.util.List;
6+
import java.util.concurrent.CancellationException;
67
import java.util.concurrent.CompletableFuture;
78
import java.util.concurrent.CompletionException;
89
import java.util.function.Consumer;
@@ -160,6 +161,20 @@ static <T> PromisedValues allOf(CompletableFuture<T> f1, CompletableFuture<T> f2
160161
*/
161162
int size();
162163

164+
/**
165+
* Waits for the underlying futures to complete. To better
166+
* conform with the use of common functional forms, if a
167+
* computation involved in the completion of this
168+
* CompletableFuture threw an exception, this method throws an
169+
* (unchecked) {@link CompletionException} with the underlying
170+
* exception as its cause.
171+
*
172+
* @throws CancellationException if the computation was cancelled
173+
* @throws CompletionException if this future completed
174+
* exceptionally or a completion computation threw an exception
175+
*/
176+
void join();
177+
163178
/**
164179
* @return this as a {@link CompletableFuture} that returns the list of underlying values
165180
*/

src/main/java/org/dataloader/impl/PromisedValuesImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ public int size() {
109109
return futures.size();
110110
}
111111

112+
@Override
113+
public void join() {
114+
controller.join();
115+
}
116+
112117
@Override
113118
public CompletableFuture<List<T>> toCompletableFuture() {
114119
return controller.thenApply(v -> toList());

src/test/java/ReadmeExamples.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import org.dataloader.BatchLoader;
2+
import org.dataloader.DataLoader;
3+
import org.dataloader.PromisedValues;
4+
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.stream.Collectors;
10+
11+
public class ReadmeExamples {
12+
13+
UserManager userManager = new UserManager();
14+
15+
public static void main(String[] args) {
16+
ReadmeExamples examples = new ReadmeExamples();
17+
examples.basicExample();
18+
}
19+
20+
@SuppressWarnings({"Convert2Lambda", "Convert2MethodRef", "CodeBlock2Expr"})
21+
void basicExample() {
22+
23+
BatchLoader<Long, User> userBatchLoader = new BatchLoader<Long, User>() {
24+
@Override
25+
public PromisedValues<User> load(List<Long> userIds) {
26+
List<CompletableFuture<User>> futures = userIds.stream()
27+
.map(userId ->
28+
CompletableFuture.supplyAsync(() ->
29+
userManager.loadUsersById(userId)))
30+
.collect(Collectors.toList());
31+
return PromisedValues.allOf(futures);
32+
}
33+
};
34+
DataLoader<Long, User> userLoader = new DataLoader<>(userBatchLoader);
35+
36+
CompletableFuture<User> load1 = userLoader.load(1L);
37+
38+
userLoader.load(1L)
39+
.thenAccept(user -> {
40+
System.out.println("user = " + user);
41+
userLoader.load(user.getInvitedByID())
42+
.thenAccept(invitedBy -> {
43+
System.out.println("invitedBy = " + invitedBy);
44+
});
45+
});
46+
47+
userLoader.load(2L)
48+
.thenAccept(user -> {
49+
System.out.println("user = " + user);
50+
userLoader.load(user.getInvitedByID())
51+
.thenAccept(invitedBy -> {
52+
System.out.println("invitedBy = " + invitedBy);
53+
});
54+
});
55+
56+
userLoader.dispatch().join();
57+
userLoader.dispatch().join();
58+
}
59+
60+
class User {
61+
String name;
62+
Long invitedByID;
63+
64+
public User(Long invitedByID, String name) {
65+
this.name = name;
66+
this.invitedByID = invitedByID;
67+
}
68+
69+
public String getName() {
70+
return name;
71+
}
72+
73+
public Long getInvitedByID() {
74+
return invitedByID;
75+
}
76+
77+
@Override
78+
public String toString() {
79+
return "User{" +
80+
"name='" + name + '\'' +
81+
"invitedByID='" + invitedByID + '\'' +
82+
'}';
83+
}
84+
}
85+
86+
class UserManager {
87+
88+
Map<Long, User> users = new LinkedHashMap<>();
89+
90+
{
91+
users.put(10001L, new User(-1L, "Aulë"));
92+
users.put(10002L, new User(-1L, "Oromë"));
93+
users.put(10003L, new User(-1L, "Yavanna"));
94+
users.put(10004L, new User(-1L, "Manwë"));
95+
96+
users.put(1L, new User(10004L, "Olórin"));
97+
users.put(2L, new User(10001L, "Curunir"));
98+
users.put(3L, new User(10002L, "Alatar"));
99+
users.put(4L, new User(10003L, "Aiwendil"));
100+
}
101+
102+
public User loadUsersById(Long userId) {
103+
return users.get(userId);
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)