Skip to content

Commit 37234e9

Browse files
committed
Add RetryableClient enhancements and demo
1. The modified RetryableClient.java file 2. The new RetryClientDemo.java example
1 parent f3986bf commit 37234e9

2 files changed

Lines changed: 339 additions & 25 deletions

File tree

examples/v1/RetryClientDemo.java

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/*
2+
* Authzed API examples for RetryableClient
3+
*/
4+
package v1;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.Random;
9+
import java.util.concurrent.TimeUnit;
10+
11+
import com.authzed.api.v1.ConflictStrategy;
12+
import com.authzed.api.v1.Core;
13+
import com.authzed.api.v1.Core.ObjectReference;
14+
import com.authzed.api.v1.Core.Relationship;
15+
import com.authzed.api.v1.Core.SubjectReference;
16+
import com.authzed.api.v1.RetryableClient;
17+
import com.authzed.api.v1.SchemaServiceOuterClass;
18+
import com.authzed.api.v1.SchemaServiceOuterClass.ReadSchemaRequest;
19+
import com.authzed.api.v1.SchemaServiceOuterClass.ReadSchemaResponse;
20+
import com.authzed.api.v1.SchemaServiceOuterClass.WriteSchemaRequest;
21+
import com.authzed.api.v1.SchemaServiceOuterClass.WriteSchemaResponse;
22+
import com.authzed.grpcutil.BearerToken;
23+
24+
/**
25+
* RetryClientDemo demonstrates using RetryableClient with different conflict strategies.
26+
*
27+
* This program connects to a local SpiceDB instance and imports relationships
28+
* using each of the available conflict strategies:
29+
* - FAIL: Returns an error if duplicate relationships are found
30+
* - SKIP: Ignores duplicates and continues with import
31+
* - TOUCH: Retries the import with TOUCH semantics for duplicates
32+
*/
33+
public class RetryClientDemo {
34+
// SpiceDB connection details
35+
private static final String SPICEDB_ADDRESS = "localhost:50051";
36+
private static final String PRESHARED_KEY = "foobar";
37+
38+
// Number of relationships to create in each test
39+
private static final int RELATIONSHIPS_COUNT = 1000;
40+
41+
public static void main(String[] args) {
42+
System.out.println("RetryClientDemo: Demonstrating RetryableClient with different conflict strategies");
43+
44+
// Create a RetryableClient connected to SpiceDB
45+
RetryableClient client = null;
46+
try {
47+
client = RetryableClient.newClient(
48+
SPICEDB_ADDRESS,
49+
new BearerToken(PRESHARED_KEY),
50+
true); // Using plaintext connection
51+
52+
// Write schema for document and user types
53+
writeSchema(client);
54+
55+
// Verify connection and read schema
56+
verifyConnection(client);
57+
58+
// Demonstrate each conflict strategy
59+
demonstrateFailStrategy(client);
60+
demonstrateSkipStrategy(client);
61+
demonstrateTouchStrategy(client);
62+
63+
System.out.println("\nDemo completed successfully!");
64+
} catch (Exception e) {
65+
System.err.println("Error in RetryClientDemo: " + e.getMessage());
66+
e.printStackTrace();
67+
} finally {
68+
if (client != null) {
69+
client.close();
70+
}
71+
}
72+
}
73+
74+
/**
75+
* Write a schema to SpiceDB with document and user types.
76+
*/
77+
private static void writeSchema(RetryableClient client) {
78+
System.out.println("Writing schema to SpiceDB...");
79+
80+
// Define a schema with document and user types
81+
String schema = """
82+
definition document {
83+
relation reader: user
84+
relation writer: user
85+
86+
permission read = reader + writer
87+
permission write = writer
88+
}
89+
90+
definition user {}
91+
""";
92+
93+
// Build the write schema request
94+
WriteSchemaRequest request = WriteSchemaRequest.newBuilder()
95+
.setSchema(schema)
96+
.build();
97+
98+
try {
99+
// Write the schema
100+
WriteSchemaResponse response = client.schemaService()
101+
.withDeadlineAfter(5, TimeUnit.SECONDS)
102+
.writeSchema(request);
103+
104+
System.out.println("Schema written successfully!");
105+
} catch (Exception e) {
106+
System.err.println("Failed to write schema: " + e.getMessage());
107+
throw new RuntimeException("Could not write schema to SpiceDB", e);
108+
}
109+
}
110+
111+
/**
112+
* Verify connection to SpiceDB by reading the schema.
113+
*/
114+
private static void verifyConnection(RetryableClient client) {
115+
try {
116+
ReadSchemaResponse response = client.schemaService()
117+
.withDeadlineAfter(5, TimeUnit.SECONDS)
118+
.readSchema(ReadSchemaRequest.newBuilder().build());
119+
120+
System.out.println("\nSuccessfully connected to SpiceDB!");
121+
System.out.println("Schema: " + response.getSchemaText());
122+
} catch (Exception e) {
123+
System.err.println("Failed to connect to SpiceDB: " + e.getMessage());
124+
throw new RuntimeException("Could not connect to SpiceDB", e);
125+
}
126+
}
127+
128+
/**
129+
* Demonstrate FAIL conflict strategy.
130+
* This strategy will fail if duplicate relationships are found.
131+
*/
132+
private static void demonstrateFailStrategy(RetryableClient client) {
133+
System.out.println("\n=== Demonstrating FAIL Strategy ===");
134+
try {
135+
// Create unique relationships
136+
List<Relationship> relationships = generateUniqueRelationships(RELATIONSHIPS_COUNT);
137+
138+
System.out.println("Importing " + relationships.size() + " unique relationships with FAIL strategy...");
139+
long numLoaded = client.retryableBulkImportRelationships(relationships, ConflictStrategy.FAIL);
140+
System.out.println("Successfully imported " + numLoaded + " relationships!");
141+
142+
// Now try with some duplicate relationships
143+
try {
144+
System.out.println("Now attempting to import same relationships again...");
145+
client.retryableBulkImportRelationships(relationships, ConflictStrategy.FAIL);
146+
System.out.println("ERROR: Import should have failed but didn't!");
147+
} catch (Exception e) {
148+
System.out.println("As expected, import failed with error: " + e.getMessage());
149+
}
150+
} catch (Exception e) {
151+
System.err.println("Error demonstrating FAIL strategy: " + e.getMessage());
152+
e.printStackTrace();
153+
}
154+
}
155+
156+
/**
157+
* Demonstrate SKIP conflict strategy.
158+
* This strategy will ignore duplicates and continue with the import.
159+
*/
160+
private static void demonstrateSkipStrategy(RetryableClient client) {
161+
System.out.println("\n=== Demonstrating SKIP Strategy ===");
162+
try {
163+
// Create a mix of new and existing relationships
164+
List<Relationship> mixedRelationships = generateMixedRelationships(RELATIONSHIPS_COUNT / 2);
165+
166+
System.out.println("Importing " + mixedRelationships.size() + " relationships (mix of new and existing) with SKIP strategy...");
167+
long numLoaded = client.retryableBulkImportRelationships(mixedRelationships, ConflictStrategy.SKIP);
168+
169+
System.out.println("Successfully processed " + numLoaded + " relationships with SKIP strategy!");
170+
System.out.println("Note: Duplicates were skipped, but operation completed successfully");
171+
} catch (Exception e) {
172+
System.err.println("Error demonstrating SKIP strategy: " + e.getMessage());
173+
e.printStackTrace();
174+
}
175+
}
176+
177+
/**
178+
* Demonstrate TOUCH conflict strategy.
179+
* This strategy will retry the import with TOUCH semantics for duplicates.
180+
*/
181+
private static void demonstrateTouchStrategy(RetryableClient client) {
182+
System.out.println("\n=== Demonstrating TOUCH Strategy ===");
183+
try {
184+
// Create all new relationships to ensure initial write works
185+
List<Relationship> newRelationships = generateUniqueRelationships(RELATIONSHIPS_COUNT / 2, RELATIONSHIPS_COUNT);
186+
187+
System.out.println("Importing " + newRelationships.size() + " new relationships...");
188+
long numLoaded = client.retryableBulkImportRelationships(newRelationships, ConflictStrategy.TOUCH);
189+
System.out.println("Successfully imported " + numLoaded + " relationships!");
190+
191+
// Now use TOUCH on a mix of new and existing
192+
List<Relationship> mixedRelationships = new ArrayList<>(newRelationships);
193+
mixedRelationships.addAll(generateUniqueRelationships(RELATIONSHIPS_COUNT / 4, RELATIONSHIPS_COUNT * 2));
194+
195+
System.out.println("Now importing " + mixedRelationships.size() + " relationships with some duplicates using TOUCH strategy...");
196+
numLoaded = client.retryableBulkImportRelationships(mixedRelationships, ConflictStrategy.TOUCH);
197+
198+
System.out.println("Successfully processed " + numLoaded + " relationships with TOUCH strategy!");
199+
System.out.println("Note: Duplicates were touched (re-written) rather than causing an error");
200+
} catch (Exception e) {
201+
System.err.println("Error demonstrating TOUCH strategy: " + e.getMessage());
202+
e.printStackTrace();
203+
}
204+
}
205+
206+
/**
207+
* Generate a list of unique relationships.
208+
*/
209+
private static List<Relationship> generateUniqueRelationships(int count) {
210+
return generateUniqueRelationships(count, 0);
211+
}
212+
213+
/**
214+
* Generate a list of unique relationships with IDs starting from offset.
215+
*/
216+
private static List<Relationship> generateUniqueRelationships(int count, int offset) {
217+
List<Relationship> relationships = new ArrayList<>(count);
218+
Random random = new Random();
219+
220+
for (int i = 0; i < count; i++) {
221+
String docId = "doc" + (i + offset);
222+
String userId = "user" + (random.nextInt(20) + 1); // 20 possible users
223+
String relation = random.nextBoolean() ? "reader" : "writer";
224+
225+
relationships.add(createRelationship(docId, relation, userId));
226+
}
227+
228+
return relationships;
229+
}
230+
231+
/**
232+
* Generate a mix of new and potentially duplicate relationships.
233+
*/
234+
private static List<Relationship> generateMixedRelationships(int count) {
235+
List<Relationship> relationships = new ArrayList<>(count);
236+
Random random = new Random();
237+
238+
for (int i = 0; i < count; i++) {
239+
// Use a lower document ID range to increase chance of duplicates
240+
String docId = "doc" + (random.nextInt(count / 2) + 1);
241+
String userId = "user" + (random.nextInt(10) + 1);
242+
String relation = random.nextBoolean() ? "reader" : "writer";
243+
244+
relationships.add(createRelationship(docId, relation, userId));
245+
}
246+
247+
return relationships;
248+
}
249+
250+
/**
251+
* Create a relationship between a document and user with the specified relation.
252+
*/
253+
private static Relationship createRelationship(String docId, String relation, String userId) {
254+
return Relationship.newBuilder()
255+
.setResource(ObjectReference.newBuilder()
256+
.setObjectType("document")
257+
.setObjectId(docId)
258+
.build())
259+
.setRelation(relation)
260+
.setSubject(SubjectReference.newBuilder()
261+
.setObject(ObjectReference.newBuilder()
262+
.setObjectType("user")
263+
.setObjectId(userId)
264+
.build())
265+
.build())
266+
.build();
267+
}
268+
}

0 commit comments

Comments
 (0)