Skip to content

Commit 2e72362

Browse files
authored
fix: remove model.api.version (#2542)
Fixes #2516. Also, removed the SDK version from metadata (and keep only the Solver version). See also: TimefoldAI/timefold-solver-enterprise#791 The API version is now defined by the `timefold.application.version` in the `pom.xml`. The maven property is used to define a model descriptor classifier, e.g. `timefold-pickup-delivery-routing-descriptor-1.2.0-v1.zip`. Thus, it must be defined in `pom.xml` and not in the `application.properties`, where it gets injected by the Quarkus maven plugin. The service-parent `pom.xml` defines the default value `v1`, which can be overridden in the model `pom.xml`. The user must not override the value in `application.properties`, which may lead to the model being updated to the new version, but its model descriptor classifier still being `v1`.
1 parent d725659 commit 2e72362

10 files changed

Lines changed: 31 additions & 29 deletions

File tree

docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ Create the `src/main/resources/application.properties` file:
250250
ai.timefold.platform.termination.spent-limit=PT30S
251251
252252
# Application metadata — included in the generated OpenAPI specification
253-
model.api.version=v1
254253
timefold.application.name=my-model
255254
timefold.application.contact.email=info@example.com
256255
timefold.application.contact.name=Your Name
@@ -264,7 +263,6 @@ For production use, consider at least five minutes (`PT5M`) to allow the solver
264263
NOTE: The per-request `config.run.termination.spentLimit` field in the POST body (shown later) overrides this default for individual runs.
265264

266265
`timefold.application.name` and the contact fields are *required* metadata.
267-
`model.api.version` is required (for example `v1`) and is used as the default for `timefold.application.version` and `quarkus.rest.path`.
268266
They are used to identify your service, validate the JSON schema of requests, and populate the generated OpenAPI specification.
269267

270268
== REST API
@@ -300,8 +298,8 @@ public interface TimetableResource extends ModelRest {
300298
This is the most basic version of the interface. The SDK will automatically create multiple xref:running-timefold-solver/service/rest-api.adoc[REST API endpoints] for optimization actions.
301299
The `@Path` annotation is used to configure the base path of all those endpoints.
302300

303-
NOTE: Do not include an API version in `@Path`. The SDK default configuration sets `quarkus.rest.path=${model.api.version}`, which Quarkus automatically prepends to every `@Path` at runtime.
304-
So with `model.api.version=v1`, `@Path("/timetables")` is served at `/v1/timetables` — locally and on the platform.
301+
NOTE: Do not include an API version in `@Path`. The SDK default configuration sets `quarkus.rest.path=${timefold.application.version}`, which Quarkus automatically prepends to every `@Path` at runtime.
302+
So with `timefold.application.version=v1`, `@Path("/timetables")` is served at `/v1/timetables` — locally and on the platform.
305303

306304
== Running and manually testing the model
307305

docs/src/modules/ROOT/pages/running-timefold-solver/service/rest-api.adoc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,24 @@ Since an xref:#openAPISpecification[OpenAPI specification] will also be automati
7979

8080
The REST module generates several endpoints; the following are the core endpoints.
8181
The `<root>` is the full served path, which is the model API version prefix followed by the value of the `@Path` annotation.
82-
The SDK default configuration sets `quarkus.rest.path=${model.api.version}`, which Quarkus prepends to every `@Path` at runtime.
83-
So for `model.api.version=v1` and `@Path("/timetables")`, `<root>` is `/v1/timetables`.
82+
The SDK default configuration sets `quarkus.rest.path=${timefold.application.version}`, which Quarkus prepends to every `@Path` at runtime.
83+
So for `timefold.application.version=v1` and `@Path("/timetables")`, `<root>` is `/v1/timetables`.
8484

8585
NOTE: Do not include the version in your `@Path` annotation. The SDK handles it automatically via `quarkus.rest.path`.
8686
Including it (e.g. `@Path("/v1/timetables")`) results in a doubled prefix (`/v1/v1/timetables`) at runtime.
8787

88+
NOTE: To change the API version, override the `timefold.application.version` Maven property in the `pom.xml`.
89+
The Quarkus property of the same name gets configured automatically from the Maven property.
90+
8891
- `GET /<root>`: Retrieve all registered optimization datasets
8992
- `POST /<root>`: Create a new optimization dataset
9093
- `GET /<root>/\{id\}`: Retrieve the (intermediate) result of a dataset optimization run
9194
- `GET /<root>/\{id\}/score-analysis`: Retrieve score analysis of an optimization dataset
9295
- `DELETE /<root>/\{id\}`: Terminate a dataset optimization run
9396

9497
If your model provides xref:./demo-data.adoc[demo data], the following endpoints are also created.
95-
These are *not* nested under `<root>`: the path is `/{model.api.version}/demo-data`, not `/<root>/demo-data`.
96-
For example, with `model.api.version=v1` this is `/v1/demo-data`.
98+
These are *not* nested under `<root>`: the path is `/{timefold.application.version}/demo-data`, not `/<root>/demo-data`.
99+
For example, with `timefold.application.version=v1` this is `/v1/demo-data`.
97100

98101
- `GET /v<version>/demo-data`: Retrieve all available demo dataset ids.
99102
- `GET /v<version>/demo-data/\{demoDataId\}`: Retrieve the demo dataset with the given identifier
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package ai.timefold.solver.service.definition.internal.descriptor;
22

3-
public record ModelBuildInfo(String solverVersion, String sdkVersion, String version, String buildTime, String branch,
3+
public record ModelBuildInfo(String solverVersion, String version, String buildTime, String branch,
44
String buildCommit) {
5+
6+
public static ModelBuildInfo empty() {
7+
return new ModelBuildInfo(null, null, null, null, null);
8+
}
59
}

service/facade/service-parent/pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@
4848
<ai.timefold.platform.model.test.api-key>${env.TF_PLATFORM_TEST_API_KEY}</ai.timefold.platform.model.test.api-key>
4949
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5050

51-
<!-- the model api version has to be kept in sync with the model version in model POM -->
52-
<model.api.version>v1</model.api.version>
51+
<!-- the application version defines the API version of the model -->
52+
<timefold.application.version>v1</timefold.application.version>
5353
<!-- default configuration, can be overridden by a model if needed -->
5454
<ai.timefold.model.descriptor.repository>${project.artifactId}</ai.timefold.model.descriptor.repository>
5555
<ai.timefold.model.native.image.path>${project.build.directory}/${project.build.finalName}-runner</ai.timefold.model.native.image.path>
5656
<ai.timefold.model.descriptor.groupId>${project.groupId}</ai.timefold.model.descriptor.groupId>
5757
<ai.timefold.model.descriptor.artifactId>${project.artifactId}-descriptor</ai.timefold.model.descriptor.artifactId>
58-
<ai.timefold.model.descriptor.classifier>${model.api.version}</ai.timefold.model.descriptor.classifier>
58+
<ai.timefold.model.descriptor.classifier>${timefold.application.version}</ai.timefold.model.descriptor.classifier>
5959
<ai.timefold.model.descriptor.version>${project.version}</ai.timefold.model.descriptor.version>
6060
<ai.timefold.model.descriptor.file>${project.build.directory}/model-descriptor.zip</ai.timefold.model.descriptor.file>
6161
<ai.timefold.model.descriptor.repository.id>github</ai.timefold.model.descriptor.repository.id>
@@ -596,7 +596,7 @@
596596
<extensions>true</extensions>
597597
<configuration>
598598
<properties>
599-
<model.api.version>${model.api.version}</model.api.version>
599+
<timefold.application.version>${timefold.application.version}</timefold.application.version>
600600
</properties>
601601
</configuration>
602602
<executions>

service/quarkus/deployment/src/main/java/ai/timefold/solver/service/quarkus/deployment/TimefoldModelDescriptorProcessor.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,6 @@ void generateBuildTimeInfoBean(ModelBuildInfoRecorder recorder,
347347

348348
String solverVersion =
349349
SolverVersionUtils.bareVersion(SolverVersionUtils.CORE_GIT_PROPERTIES, SolverFactory.class);
350-
String sdkVersion = SolverVersionUtils.bareVersion(ModelDescriptor.class);
351350
String version = buildInfo != null ? (String) buildInfo.getValue().get("version") : null;
352351
String buildTime = buildInfo != null ? (String) buildInfo.getValue().get("time") : null;
353352
if (buildTime != null) {
@@ -359,7 +358,7 @@ void generateBuildTimeInfoBean(ModelBuildInfoRecorder recorder,
359358
gitInfo != null ? (String) ((Map<String, Object>) gitInfo.getValue().getOrDefault("commit", Map.of())).get("id")
360359
: null;
361360

362-
ModelBuildInfo modelBuildInfo = new ModelBuildInfo(solverVersion, sdkVersion, version, buildTime, branch, commit);
361+
ModelBuildInfo modelBuildInfo = new ModelBuildInfo(solverVersion, version, buildTime, branch, commit);
363362

364363
byte[] buildInfoContent = MAPPER.writeValueAsBytes(modelBuildInfo);
365364
Path buildInfoFile = Paths.get(out.getOutputDirectory().toString(), "timefold", "build-info.json");

service/service-defaults/src/main/resources/application.properties

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ config_ordinal=230
1616
timefold.application.id=custom-timefold-model
1717
timefold.application.name=Custom Timefold model
1818
timefold.application.description=
19-
timefold.application.version=${model.api.version}
2019
timefold.application.build-timestamp=
2120
timefold.application.contact.email=
2221
timefold.application.contact.name=
@@ -84,7 +83,7 @@ quarkus.log.console.darken=1
8483
########################
8584
# REST API configuration
8685
########################
87-
quarkus.rest.path=${model.api.version}
86+
quarkus.rest.path=${timefold.application.version}
8887

8988
# Workaround for https://github.com/quarkusio/quarkus/issues/55362
9089
quarkus.rest.jackson.optimization.enable-reflection-free-serializers=false
@@ -108,10 +107,10 @@ mp.openapi.extensions.smallrye.remove-unused-schemas.enable=true
108107
########################
109108
image.native-suffix=-native
110109
# the end model should also set %container.quarkus.container-image.group
111-
%container.quarkus.container-image.name=model-${timefold.application.id}-${model.api.version}
110+
%container.quarkus.container-image.name=model-${timefold.application.id}-${timefold.application.version}
112111

113112
# the end model should also set %container-native.quarkus.container-image.group
114-
%container-native.quarkus.container-image.name=model-${timefold.application.id}-${model.api.version}${image.native-suffix}
113+
%container-native.quarkus.container-image.name=model-${timefold.application.id}-${timefold.application.version}${image.native-suffix}
115114

116115
########################################################################
117116
# Default configuration profile settings relevant for the platform only
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
########################
22
# Timefold application info
33
########################
4-
model.api.version=v1
54
timefold.application.name=Integration Tests - Simplified Employee Shift Scheduling model
65
timefold.application.id=integration-tests-simplified-employee-shift-scheduling
76
%test.ai.timefold.platform.termination.best-score-limit=0hard/0medium/*soft

service/test-model/src/test/java/ai/timefold/solver/model/testmodel/ApiVersion2ConfigProfile.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class ApiVersion2ConfigProfile implements QuarkusTestProfile {
1212

1313
@Override
1414
public Map<String, String> getConfigOverrides() {
15-
return Map.of("model.api.version", "v2-beta");
15+
return Map.of("timefold.application.version", "v2-beta");
1616
}
1717

1818
}

service/test-model/src/test/java/ai/timefold/solver/model/testmodel/EmployeeScheduleResourceTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ public class EmployeeScheduleResourceTest {
9292
InMemorySink<FinalBestSolutionEvent> finalBestSolutionSink;
9393
InMemorySink<DatasetComputedEvent> datasetComputedSink;
9494

95-
@ConfigProperty(name = "model.api.version")
96-
String modelApiVersion;
95+
@ConfigProperty(name = "timefold.application.version")
96+
String applicationVersion;
9797

9898
@TestHTTPResource
9999
URI baseUri;
@@ -108,7 +108,7 @@ Multi<SseEvent<Metadata<HardMediumSoftScore>>> getEvents(@PathParam("apiversion"
108108

109109
@BeforeEach
110110
void before() {
111-
RestAssured.basePath = "/" + modelApiVersion;
111+
RestAssured.basePath = "/" + applicationVersion;
112112
datasetComputedSink = connector.sink(SolverChannels.DATASET_COMPUTED);
113113
datasetComputedSink.clear();
114114
initSolutionSink = connector.sink(SolverChannels.INIT_SOLUTION);
@@ -229,7 +229,7 @@ void solveEmployeeScheduleWithWatching() throws InterruptedException {
229229
List<Metadata<HardMediumSoftScore>> receivedResults = new ArrayList<>();
230230

231231
CountDownLatch waitOnRequestSubscribtion = new CountDownLatch(1);
232-
Multi<SseEvent<Metadata<HardMediumSoftScore>>> eventStream = client.getEvents(modelApiVersion, metadata.getId());
232+
Multi<SseEvent<Metadata<HardMediumSoftScore>>> eventStream = client.getEvents(applicationVersion, metadata.getId());
233233

234234
Cancellable subscription = eventStream.subscribe().with(
235235
event -> {

service/test-model/src/test/java/ai/timefold/solver/model/testmodel/OpenApiTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
@QuarkusTest
2828
public class OpenApiTest {
2929

30-
@ConfigProperty(name = "model.api.version")
31-
String modelApiVersion;
30+
@ConfigProperty(name = "timefold.application.version")
31+
String applicationVersion;
3232

3333
@Test
3434
public void testOperationHasPriorityQueryParam() throws IOException {
35-
Path openApiFilePath = Paths.get("target/timefold/timefold-test-model_" + modelApiVersion + "/openapi/service.json");
35+
Path openApiFilePath = Paths.get("target/timefold/timefold-test-model_" + applicationVersion + "/openapi/service.json");
3636

3737
if (!Files.exists(openApiFilePath)) {
3838
fail("OpenAPI file not found at: " + openApiFilePath.toAbsolutePath());
@@ -48,7 +48,7 @@ public void testOperationHasPriorityQueryParam() throws IOException {
4848
operationsId.forEach(operationId -> assertOperation(openAPI, operationId));
4949

5050
// verify that paths start with api version configured
51-
openAPI.getPaths().getPathItems().forEach((k, v) -> assertThat(k).startsWith("/" + modelApiVersion));
51+
openAPI.getPaths().getPathItems().forEach((k, v) -> assertThat(k).startsWith("/" + applicationVersion));
5252
}
5353

5454
private void assertOperation(OpenAPI openAPI, String operationId) {

0 commit comments

Comments
 (0)