Skip to content

Commit e4dab0d

Browse files
[spring] Add global default service configuration (#604) (#605)
Top-level restate.* properties (inactivity-timeout, retry-policy, etc.) now act as defaults for all services. Per-service restate.components.* overrides them. Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 1c56be5 commit e4dab0d

3 files changed

Lines changed: 371 additions & 11 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) 2023 - Restate Software, Inc., Restate GmbH
2+
//
3+
// This file is part of the Restate Java SDK,
4+
// which is released under the MIT license.
5+
//
6+
// You can find a copy of the license in file LICENSE in the root
7+
// directory of this repository or package, or at
8+
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
9+
package dev.restate.sdk.springboot.java;
10+
11+
import static org.assertj.core.api.Assertions.*;
12+
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import dev.restate.sdk.core.generated.manifest.EndpointManifestSchema;
15+
import dev.restate.sdk.springboot.RestateEndpointConfiguration;
16+
import dev.restate.sdk.springboot.RestateHttpConfiguration;
17+
import dev.restate.sdk.springboot.RestateHttpEndpointBean;
18+
import java.io.IOException;
19+
import java.net.URI;
20+
import java.net.http.HttpClient;
21+
import java.net.http.HttpRequest;
22+
import java.net.http.HttpResponse;
23+
import java.time.Duration;
24+
import org.junit.jupiter.api.Test;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.test.context.SpringBootTest;
27+
28+
@SpringBootTest(
29+
classes = {
30+
RestateEndpointConfiguration.class,
31+
RestateHttpConfiguration.class,
32+
Greeter.class,
33+
GreeterNewApi.class,
34+
ServicesConfiguration.class
35+
},
36+
properties = {
37+
"restate.sdk.http.port=0",
38+
// Default applied to both services
39+
"restate.journal-retention=PT48H",
40+
// Per-service override for greeterNewApi
41+
"restate.components.greeterNewApi.journal-retention=PT72H",
42+
"greetingPrefix=Hello "
43+
})
44+
public class RestateHttpEndpointBeanDefaultsTest {
45+
46+
@Autowired private RestateHttpEndpointBean restateHttpEndpointBean;
47+
48+
@Test
49+
public void defaultConfigShouldApplyToAllServicesAndPerServiceOverrideWins()
50+
throws IOException, InterruptedException {
51+
assertThat(restateHttpEndpointBean.isRunning()).isTrue();
52+
53+
var client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();
54+
var response =
55+
client.send(
56+
HttpRequest.newBuilder()
57+
.GET()
58+
.version(HttpClient.Version.HTTP_2)
59+
.uri(
60+
URI.create(
61+
"http://localhost:" + restateHttpEndpointBean.actualPort() + "/discover"))
62+
.header("Accept", "application/vnd.restate.endpointmanifest.v4+json")
63+
.build(),
64+
HttpResponse.BodyHandlers.ofString());
65+
assertThat(response.statusCode()).isEqualTo(200);
66+
67+
var endpointManifest =
68+
new ObjectMapper().readValue(response.body(), EndpointManifestSchema.class);
69+
70+
assertThat(endpointManifest.getServices())
71+
.extracting(
72+
dev.restate.sdk.core.generated.manifest.Service::getName,
73+
dev.restate.sdk.core.generated.manifest.Service::getJournalRetention)
74+
.containsExactlyInAnyOrder(
75+
// greeter gets the global default
76+
tuple("greeter", Duration.ofHours(48).toMillis()),
77+
// greeterNewApi gets its per-service override
78+
tuple("greeterNewApi", Duration.ofHours(72).toMillis()));
79+
}
80+
}

sdk-spring-boot/src/main/java/dev/restate/sdk/springboot/RestateComponentsProperties.java

Lines changed: 251 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// https://github.com/restatedev/sdk-java/blob/main/LICENSE
99
package dev.restate.sdk.springboot;
1010

11+
import java.time.Duration;
1112
import java.util.HashMap;
1213
import java.util.Map;
1314
import org.jspecify.annotations.Nullable;
@@ -16,12 +17,21 @@
1617
/**
1718
* Properties for configuring Restate services.
1819
*
20+
* <p>Top-level fields (e.g. {@code restate.inactivity-timeout}) act as defaults applied to all
21+
* services. Per-service configuration in {@link #getComponents()} takes precedence over these
22+
* defaults.
23+
*
1924
* <p>Example configuration in {@code application.properties}:
2025
*
2126
* <pre>{@code
22-
* # Configuration for a service named "MyService"
27+
* # Default configuration applied to all services
28+
* restate.executor=myGlobalExecutor
29+
* restate.inactivity-timeout=10m
30+
* restate.retry-policy.max-attempts=5
31+
*
32+
* # Per-service configuration (overrides defaults)
2333
* restate.components.MyService.executor=myServiceExecutor
24-
* restate.components.MyService.inactivity-timeout=10m
34+
* restate.components.MyService.inactivity-timeout=5m
2535
* restate.components.MyService.abort-timeout=1m
2636
* restate.components.MyService.idempotency-retention=1d
2737
* restate.components.MyService.journal-retention=7d
@@ -47,14 +57,23 @@
4757
public class RestateComponentsProperties {
4858

4959
@Nullable private String executor;
60+
@Nullable private String documentation;
61+
@Nullable private Map<String, String> metadata;
62+
@Nullable private Duration inactivityTimeout;
63+
@Nullable private Duration abortTimeout;
64+
@Nullable private Duration idempotencyRetention;
65+
@Nullable private Duration workflowRetention;
66+
@Nullable private Duration journalRetention;
67+
@Nullable private Boolean ingressPrivate;
68+
@Nullable private Boolean enableLazyState;
69+
@Nullable private RetryPolicyProperties retryPolicy;
5070

51-
// Map keyed by function bean name (e.g. restate.function.my-function.inactivity-timeout)
71+
// Map keyed by service name (e.g. restate.components.MyService.inactivity-timeout)
5272
private Map<String, RestateComponentProperties> components = new HashMap<>();
5373

5474
/**
5575
* Name of the {@link java.util.concurrent.Executor} bean to use for running handlers of all
56-
* services. This is the global default and can be overridden per-service in {@link
57-
* #getComponents()}.
76+
* services. Can be overridden per-service in {@link #getComponents()}.
5877
*
5978
* <p><b>NOTE:</b> This option is only used for Java services, not Kotlin services.
6079
*
@@ -68,8 +87,7 @@ public class RestateComponentsProperties {
6887

6988
/**
7089
* Name of the {@link java.util.concurrent.Executor} bean to use for running handlers of all
71-
* services. This is the global default and can be overridden per-service in {@link
72-
* #getComponents()}.
90+
* services. Can be overridden per-service in {@link #getComponents()}.
7391
*
7492
* <p><b>NOTE:</b> This option is only used for Java services, not Kotlin services.
7593
*
@@ -82,7 +100,231 @@ public void setExecutor(@Nullable String executor) {
82100
}
83101

84102
/**
85-
* Per-component configuration, keyed by component/service name.
103+
* Default documentation for all services, as shown in the UI, Admin REST API, and the generated
104+
* OpenAPI documentation. Can be overridden per-service in {@link #getComponents()}.
105+
*/
106+
public @Nullable String getDocumentation() {
107+
return documentation;
108+
}
109+
110+
/**
111+
* Default documentation for all services, as shown in the UI, Admin REST API, and the generated
112+
* OpenAPI documentation. Can be overridden per-service in {@link #getComponents()}.
113+
*/
114+
public void setDocumentation(@Nullable String documentation) {
115+
this.documentation = documentation;
116+
}
117+
118+
/**
119+
* Default metadata for all services, as propagated in the Admin REST API. Can be overridden
120+
* per-service in {@link #getComponents()}.
121+
*/
122+
public @Nullable Map<String, String> getMetadata() {
123+
return metadata;
124+
}
125+
126+
/**
127+
* Default metadata for all services, as propagated in the Admin REST API. Can be overridden
128+
* per-service in {@link #getComponents()}.
129+
*/
130+
public void setMetadata(@Nullable Map<String, String> metadata) {
131+
this.metadata = metadata;
132+
}
133+
134+
/**
135+
* Default inactivity timeout for all services. Can be overridden per-service in {@link
136+
* #getComponents()}.
137+
*
138+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
139+
* 1.4, otherwise service discovery will fail.
140+
*
141+
* @see RestateComponentProperties#getInactivityTimeout()
142+
*/
143+
public @Nullable Duration getInactivityTimeout() {
144+
return inactivityTimeout;
145+
}
146+
147+
/**
148+
* Default inactivity timeout for all services. Can be overridden per-service in {@link
149+
* #getComponents()}.
150+
*
151+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
152+
* 1.4, otherwise service discovery will fail.
153+
*/
154+
public void setInactivityTimeout(@Nullable Duration inactivityTimeout) {
155+
this.inactivityTimeout = inactivityTimeout;
156+
}
157+
158+
/**
159+
* Default abort timeout for all services. Can be overridden per-service in {@link
160+
* #getComponents()}.
161+
*
162+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
163+
* 1.4, otherwise service discovery will fail.
164+
*
165+
* @see RestateComponentProperties#getAbortTimeout()
166+
*/
167+
public @Nullable Duration getAbortTimeout() {
168+
return abortTimeout;
169+
}
170+
171+
/**
172+
* Default abort timeout for all services. Can be overridden per-service in {@link
173+
* #getComponents()}.
174+
*
175+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
176+
* 1.4, otherwise service discovery will fail.
177+
*/
178+
public void setAbortTimeout(@Nullable Duration abortTimeout) {
179+
this.abortTimeout = abortTimeout;
180+
}
181+
182+
/**
183+
* Default idempotency retention for all services. Can be overridden per-service in {@link
184+
* #getComponents()}.
185+
*
186+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
187+
* 1.4, otherwise service discovery will fail.
188+
*
189+
* @see RestateComponentProperties#getIdempotencyRetention()
190+
*/
191+
public @Nullable Duration getIdempotencyRetention() {
192+
return idempotencyRetention;
193+
}
194+
195+
/**
196+
* Default idempotency retention for all services. Can be overridden per-service in {@link
197+
* #getComponents()}.
198+
*
199+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
200+
* 1.4, otherwise service discovery will fail.
201+
*/
202+
public void setIdempotencyRetention(@Nullable Duration idempotencyRetention) {
203+
this.idempotencyRetention = idempotencyRetention;
204+
}
205+
206+
/**
207+
* Default workflow retention for all workflow services. Can be overridden per-service in {@link
208+
* #getComponents()}.
209+
*
210+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
211+
* 1.4, otherwise service discovery will fail.
212+
*
213+
* @see RestateComponentProperties#getWorkflowRetention()
214+
*/
215+
public @Nullable Duration getWorkflowRetention() {
216+
return workflowRetention;
217+
}
218+
219+
/**
220+
* Default workflow retention for all workflow services. Can be overridden per-service in {@link
221+
* #getComponents()}.
222+
*
223+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
224+
* 1.4, otherwise service discovery will fail.
225+
*/
226+
public void setWorkflowRetention(@Nullable Duration workflowRetention) {
227+
this.workflowRetention = workflowRetention;
228+
}
229+
230+
/**
231+
* Default journal retention for all services. Can be overridden per-service in {@link
232+
* #getComponents()}.
233+
*
234+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
235+
* 1.4, otherwise service discovery will fail.
236+
*
237+
* @see RestateComponentProperties#getJournalRetention()
238+
*/
239+
public @Nullable Duration getJournalRetention() {
240+
return journalRetention;
241+
}
242+
243+
/**
244+
* Default journal retention for all services. Can be overridden per-service in {@link
245+
* #getComponents()}.
246+
*
247+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
248+
* 1.4, otherwise service discovery will fail.
249+
*/
250+
public void setJournalRetention(@Nullable Duration journalRetention) {
251+
this.journalRetention = journalRetention;
252+
}
253+
254+
/**
255+
* Default ingress-private setting for all services. Can be overridden per-service in {@link
256+
* #getComponents()}.
257+
*
258+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
259+
* 1.4, otherwise service discovery will fail.
260+
*
261+
* @see RestateComponentProperties#getIngressPrivate()
262+
*/
263+
public @Nullable Boolean getIngressPrivate() {
264+
return ingressPrivate;
265+
}
266+
267+
/**
268+
* Default ingress-private setting for all services. Can be overridden per-service in {@link
269+
* #getComponents()}.
270+
*
271+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
272+
* 1.4, otherwise service discovery will fail.
273+
*/
274+
public void setIngressPrivate(@Nullable Boolean ingressPrivate) {
275+
this.ingressPrivate = ingressPrivate;
276+
}
277+
278+
/**
279+
* Default lazy-state setting for all services. Can be overridden per-service in {@link
280+
* #getComponents()}.
281+
*
282+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
283+
* 1.4, otherwise service discovery will fail.
284+
*
285+
* @see RestateComponentProperties#getEnableLazyState()
286+
*/
287+
public @Nullable Boolean getEnableLazyState() {
288+
return enableLazyState;
289+
}
290+
291+
/**
292+
* Default lazy-state setting for all services. Can be overridden per-service in {@link
293+
* #getComponents()}.
294+
*
295+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
296+
* 1.4, otherwise service discovery will fail.
297+
*/
298+
public void setEnableLazyState(@Nullable Boolean enableLazyState) {
299+
this.enableLazyState = enableLazyState;
300+
}
301+
302+
/**
303+
* Default retry policy for all services. Can be overridden per-service in {@link
304+
* #getComponents()}.
305+
*
306+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
307+
* 1.5, otherwise service discovery will fail.
308+
*
309+
* @see RestateComponentProperties#getRetryPolicy()
310+
*/
311+
public @Nullable RetryPolicyProperties getRetryPolicy() {
312+
return retryPolicy;
313+
}
314+
315+
/**
316+
* Default retry policy for all services. Can be overridden per-service in {@link
317+
* #getComponents()}.
318+
*
319+
* <p><b>NOTE:</b> You can set this field only if you register services against restate-server >=
320+
* 1.5, otherwise service discovery will fail.
321+
*/
322+
public void setRetryPolicy(@Nullable RetryPolicyProperties retryPolicy) {
323+
this.retryPolicy = retryPolicy;
324+
}
325+
326+
/**
327+
* Per-component configuration, keyed by component/service name. Overrides any top-level defaults.
86328
*
87329
* <p>Example configuration in {@code application.properties}:
88330
*
@@ -96,7 +338,7 @@ public Map<String, RestateComponentProperties> getComponents() {
96338
}
97339

98340
/**
99-
* Per-component configuration, keyed by component/service name.
341+
* Per-component configuration, keyed by component/service name. Overrides any top-level defaults.
100342
*
101343
* <p>Example configuration in {@code application.properties}:
102344
*

0 commit comments

Comments
 (0)