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 ("\n Demo 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 ("\n Successfully 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