Skip to content

Commit 32a5de2

Browse files
committed
Refactor: extract DurableEntityClient class to match .NET SDK pattern
1 parent 1bca544 commit 32a5de2

17 files changed

Lines changed: 459 additions & 223 deletions

azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.microsoft.azure.functions.HttpStatus;
1010
import com.microsoft.durabletask.DurableTaskClient;
1111
import com.microsoft.durabletask.DurableTaskGrpcClientFactory;
12+
import com.microsoft.durabletask.DurableEntityClient;
1213
import com.microsoft.durabletask.EntityInstanceId;
1314
import com.microsoft.durabletask.EntityMetadata;
1415
import com.microsoft.durabletask.EntityQuery;
@@ -139,6 +140,17 @@ public HttpManagementPayload createHttpManagementPayload(HttpRequestMessage<?> r
139140
return this.getClientResponseLinks(request, instanceId);
140141
}
141142

143+
/**
144+
* Gets the entity client for interacting with durable entities.
145+
* <p>
146+
* This mirrors the .NET SDK's {@code DurableTaskClient.Entities} property.
147+
*
148+
* @return the {@link DurableEntityClient} for this client
149+
*/
150+
public DurableEntityClient getEntities() {
151+
return getClient().getEntities();
152+
}
153+
142154
/**
143155
* Sends a fire-and-forget signal to a durable entity.
144156
*
@@ -147,7 +159,7 @@ public HttpManagementPayload createHttpManagementPayload(HttpRequestMessage<?> r
147159
* @param input the input to pass to the operation (may be {@code null})
148160
*/
149161
public void signalEntity(EntityInstanceId entityId, String operationName, Object input) {
150-
getClient().signalEntity(entityId, operationName, input);
162+
getClient().getEntities().signalEntity(entityId, operationName, input);
151163
}
152164

153165
/**
@@ -157,7 +169,7 @@ public void signalEntity(EntityInstanceId entityId, String operationName, Object
157169
* @param operationName the name of the operation to invoke on the entity
158170
*/
159171
public void signalEntity(EntityInstanceId entityId, String operationName) {
160-
getClient().signalEntity(entityId, operationName);
172+
getClient().getEntities().signalEntity(entityId, operationName);
161173
}
162174

163175
/**
@@ -168,7 +180,7 @@ public void signalEntity(EntityInstanceId entityId, String operationName) {
168180
* @return the entity metadata, or {@code null} if the entity does not exist
169181
*/
170182
public EntityMetadata getEntityMetadata(EntityInstanceId entityId, boolean includeState) {
171-
return getClient().getEntityMetadata(entityId, includeState);
183+
return getClient().getEntities().getEntityMetadata(entityId, includeState);
172184
}
173185

174186
/**
@@ -178,7 +190,7 @@ public EntityMetadata getEntityMetadata(EntityInstanceId entityId, boolean inclu
178190
* @return the entity metadata, or {@code null} if the entity does not exist
179191
*/
180192
public EntityMetadata getEntityMetadata(EntityInstanceId entityId) {
181-
return getClient().getEntityMetadata(entityId);
193+
return getClient().getEntities().getEntityMetadata(entityId);
182194
}
183195

184196
/**
@@ -188,7 +200,7 @@ public EntityMetadata getEntityMetadata(EntityInstanceId entityId) {
188200
* @return the query result containing matching entities and an optional continuation token
189201
*/
190202
public EntityQueryResult queryEntities(EntityQuery query) {
191-
return getClient().queryEntities(query);
203+
return getClient().getEntities().queryEntities(query);
192204
}
193205

194206
/**
@@ -198,7 +210,7 @@ public EntityQueryResult queryEntities(EntityQuery query) {
198210
* @return the result of the clean operation, including counts of removed entities and released locks
199211
*/
200212
public CleanEntityStorageResult cleanEntityStorage(CleanEntityStorageRequest request) {
201-
return getClient().cleanEntityStorage(request);
213+
return getClient().getEntities().cleanEntityStorage(request);
202214
}
203215

204216
private HttpManagementPayload getClientResponseLinks(HttpRequestMessage<?> request, String instanceId) {

client/src/main/java/com/microsoft/durabletask/CleanEntityStorageRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Represents a request to clean up entity storage by removing empty entities and/or releasing orphaned locks.
99
* <p>
1010
* Use the builder-style setters to configure the request, then pass it to
11-
* {@link DurableTaskClient#cleanEntityStorage(CleanEntityStorageRequest)}.
11+
* {@link DurableEntityClient#cleanEntityStorage(CleanEntityStorageRequest)}.
1212
*/
1313
public final class CleanEntityStorageRequest {
1414
private String continuationToken;

client/src/main/java/com/microsoft/durabletask/CleanEntityStorageResult.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import javax.annotation.Nullable;
66

77
/**
8-
* Represents the result of a {@link DurableTaskClient#cleanEntityStorage(CleanEntityStorageRequest)} operation.
8+
* Represents the result of a {@link DurableEntityClient#cleanEntityStorage(CleanEntityStorageRequest)} operation.
99
*/
1010
public final class CleanEntityStorageResult {
1111
private final String continuationToken;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.microsoft.durabletask;
4+
5+
import javax.annotation.Nullable;
6+
7+
/**
8+
* Client for interacting with durable entities.
9+
* <p>
10+
* This class provides operations for signaling entities, querying entity metadata,
11+
* and performing entity storage maintenance. Instances are obtained from
12+
* {@link DurableTaskClient#getEntities()}.
13+
* <p>
14+
* This design mirrors the .NET SDK's {@code DurableEntityClient} which is accessed
15+
* via the {@code DurableTaskClient.Entities} property.
16+
*/
17+
public abstract class DurableEntityClient {
18+
19+
private final String name;
20+
21+
/**
22+
* Creates a new {@code DurableEntityClient} instance.
23+
*
24+
* @param name the name of the client
25+
*/
26+
protected DurableEntityClient(String name) {
27+
this.name = name;
28+
}
29+
30+
/**
31+
* Gets the name of this client.
32+
*
33+
* @return the client name
34+
*/
35+
public String getName() {
36+
return this.name;
37+
}
38+
39+
/**
40+
* Sends a signal to a durable entity instance without waiting for a response.
41+
* <p>
42+
* If the target entity does not exist, it will be created automatically when it receives the signal.
43+
*
44+
* @param entityId the target entity's instance ID
45+
* @param operationName the name of the operation to invoke on the entity
46+
*/
47+
public void signalEntity(EntityInstanceId entityId, String operationName) {
48+
this.signalEntity(entityId, operationName, null, null);
49+
}
50+
51+
/**
52+
* Sends a signal with input to a durable entity instance without waiting for a response.
53+
* <p>
54+
* If the target entity does not exist, it will be created automatically when it receives the signal.
55+
*
56+
* @param entityId the target entity's instance ID
57+
* @param operationName the name of the operation to invoke on the entity
58+
* @param input the serializable input for the operation, or {@code null}
59+
*/
60+
public void signalEntity(EntityInstanceId entityId, String operationName, @Nullable Object input) {
61+
this.signalEntity(entityId, operationName, input, null);
62+
}
63+
64+
/**
65+
* Sends a signal with input and options to a durable entity instance without waiting for a response.
66+
* <p>
67+
* If the target entity does not exist, it will be created automatically when it receives the signal.
68+
* Use {@link SignalEntityOptions#setScheduledTime(java.time.Instant)} to schedule the signal for
69+
* delivery at a future time.
70+
*
71+
* @param entityId the target entity's instance ID
72+
* @param operationName the name of the operation to invoke on the entity
73+
* @param input the serializable input for the operation, or {@code null}
74+
* @param options additional options for the signal, or {@code null}
75+
*/
76+
public abstract void signalEntity(
77+
EntityInstanceId entityId,
78+
String operationName,
79+
@Nullable Object input,
80+
@Nullable SignalEntityOptions options);
81+
82+
/**
83+
* Fetches the metadata for a durable entity instance, excluding its state.
84+
*
85+
* @param entityId the entity instance ID to query
86+
* @return the entity metadata, or {@code null} if the entity does not exist
87+
*/
88+
@Nullable
89+
public EntityMetadata getEntityMetadata(EntityInstanceId entityId) {
90+
return this.getEntityMetadata(entityId, false);
91+
}
92+
93+
/**
94+
* Fetches the metadata for a durable entity instance, optionally including its state.
95+
*
96+
* @param entityId the entity instance ID to query
97+
* @param includeState {@code true} to include the entity's serialized state in the result
98+
* @return the entity metadata, or {@code null} if the entity does not exist
99+
*/
100+
@Nullable
101+
public abstract EntityMetadata getEntityMetadata(EntityInstanceId entityId, boolean includeState);
102+
103+
/**
104+
* Queries the durable store for entity instances matching the specified filter criteria.
105+
*
106+
* @param query the query filter criteria
107+
* @return the query result containing matching entities and an optional continuation token
108+
*/
109+
public abstract EntityQueryResult queryEntities(EntityQuery query);
110+
111+
/**
112+
* Returns an auto-paginating iterable over entity instances matching the specified filter criteria.
113+
* <p>
114+
* This method automatically handles pagination when iterating over results. It fetches pages
115+
* from the store on demand, making it convenient when you want to process all matching entities
116+
* without manually managing continuation tokens.
117+
* <p>
118+
* You can iterate over individual items:
119+
* <pre>{@code
120+
* for (EntityMetadata entity : client.getEntities().getAllEntities(query)) {
121+
* System.out.println(entity.getEntityInstanceId());
122+
* }
123+
* }</pre>
124+
* <p>
125+
* Or iterate page by page for more control:
126+
* <pre>{@code
127+
* for (EntityQueryResult page : client.getEntities().getAllEntities(query).byPage()) {
128+
* for (EntityMetadata entity : page.getEntities()) {
129+
* System.out.println(entity.getEntityInstanceId());
130+
* }
131+
* }
132+
* }</pre>
133+
*
134+
* @param query the query filter criteria
135+
* @return a pageable iterable over all matching entities
136+
*/
137+
public EntityQueryPageable getAllEntities(EntityQuery query) {
138+
return new EntityQueryPageable(query, this::queryEntities);
139+
}
140+
141+
/**
142+
* Returns an auto-paginating iterable over all entity instances.
143+
* <p>
144+
* This is a convenience overload equivalent to {@code getAllEntities(new EntityQuery())}.
145+
*
146+
* @return a pageable iterable over all entities
147+
*/
148+
public EntityQueryPageable getAllEntities() {
149+
return getAllEntities(new EntityQuery());
150+
}
151+
152+
/**
153+
* Cleans up entity storage by removing empty entities and/or releasing orphaned locks.
154+
* <p>
155+
* This is an administrative operation that can be used to reclaim storage space and fix
156+
* entity state inconsistencies.
157+
*
158+
* @param request the clean storage request specifying what to clean
159+
* @return the result of the clean operation, including counts of removed entities and released locks
160+
*/
161+
public abstract CleanEntityStorageResult cleanEntityStorage(CleanEntityStorageRequest request);
162+
}

0 commit comments

Comments
 (0)