diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 5a399bd1a89..e5ff744791c 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -95,5 +95,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/src/main/java/io/spine/server/aggregate/AggregateEndpoint.java b/server/src/main/java/io/spine/server/aggregate/AggregateEndpoint.java
index 5098fa7e210..37fa23c52ef 100644
--- a/server/src/main/java/io/spine/server/aggregate/AggregateEndpoint.java
+++ b/server/src/main/java/io/spine/server/aggregate/AggregateEndpoint.java
@@ -90,7 +90,7 @@ public final void dispatchTo(I aggregateId) {
private void storeAndPost(A aggregate, DispatchOutcome outcome) {
Success success = outcome.getSuccess();
- if (success.hasProducedEvents()) {
+ if (success.hasEvents()) {
store(aggregate);
List events = success.getProducedEvents()
.getEventList();
@@ -126,7 +126,7 @@ private A loadOrCreate(I aggregateId) {
final DispatchOutcome handleAndApplyEvents(A aggregate) {
DispatchOutcome outcome = invokeDispatcher(aggregate);
Success successfulOutcome = outcome.getSuccess();
- return successfulOutcome.hasProducedEvents()
+ return successfulOutcome.hasEvents()
? applyProducedEvents(aggregate, outcome)
: outcome;
}
diff --git a/server/src/main/java/io/spine/server/dispatch/SuccessMixin.java b/server/src/main/java/io/spine/server/dispatch/SuccessMixin.java
index e022549fa10..268d2797875 100644
--- a/server/src/main/java/io/spine/server/dispatch/SuccessMixin.java
+++ b/server/src/main/java/io/spine/server/dispatch/SuccessMixin.java
@@ -49,4 +49,24 @@ default Object readValue(FieldDescriptor field) {
return getField(field);
}
}
+
+ /**
+ * Determines if the outcome has any produced events.
+ *
+ * @implNote Prefer using this method over the generated {@code hasProducedEvents}
+ * while the latter only checks if the message is set.
+ */
+ default boolean hasEvents() {
+ return hasProducedEvents() && getProducedEvents().getEventCount() > 0;
+ }
+
+ /**
+ * Determines if the outcome has any produced commands.
+ *
+ * @implNote Prefer using this method over the generated {@code hasProducedCommands}
+ * while the latter only checks if the message is set.
+ */
+ default boolean hasCommands() {
+ return hasProducedCommands() && getProducedCommands().getCommandCount() > 0;
+ }
}
diff --git a/server/src/test/java/io/spine/server/aggregate/AggregateTest.java b/server/src/test/java/io/spine/server/aggregate/AggregateTest.java
index fd2a40c762c..5410dc72f95 100644
--- a/server/src/test/java/io/spine/server/aggregate/AggregateTest.java
+++ b/server/src/test/java/io/spine/server/aggregate/AggregateTest.java
@@ -37,6 +37,7 @@
import io.spine.core.Ack;
import io.spine.core.Command;
import io.spine.core.Event;
+import io.spine.core.EventContext;
import io.spine.core.MessageId;
import io.spine.core.TenantId;
import io.spine.server.BoundedContext;
@@ -50,6 +51,11 @@
import io.spine.server.aggregate.given.aggregate.TaskAggregateRepository;
import io.spine.server.aggregate.given.aggregate.TestAggregate;
import io.spine.server.aggregate.given.aggregate.TestAggregateRepository;
+import io.spine.server.aggregate.given.thermometer.SafeThermometer;
+import io.spine.server.aggregate.given.thermometer.SafeThermometerRepo;
+import io.spine.server.aggregate.given.thermometer.Thermometer;
+import io.spine.server.aggregate.given.thermometer.ThermometerId;
+import io.spine.server.aggregate.given.thermometer.event.TemperatureChanged;
import io.spine.server.commandbus.CommandBus;
import io.spine.server.delivery.MessageEndpoint;
import io.spine.server.dispatch.BatchDispatchOutcome;
@@ -81,7 +87,7 @@
import io.spine.test.aggregate.rejection.Rejections.AggCannotReassignUnassignedTask;
import io.spine.testing.logging.MuteLogging;
import io.spine.testing.server.EventSubject;
-import io.spine.testing.server.blackbox.BlackBoxContext;
+import io.spine.testing.server.blackbox.ContextAwareTest;
import io.spine.testing.server.model.ModelTests;
import io.spine.time.testing.TimeTests;
import org.junit.jupiter.api.AfterEach;
@@ -317,12 +323,13 @@ void writeVersionIntoEventContext() {
dispatchCommand(aggregate, command(createProject));
// Get the first event since the command handler produces only one event message.
- Aggregate, ?, ?> agg = this.aggregate;
- List uncommittedEvents = agg.getUncommittedEvents().list();
+ Aggregate, ?, ?> agg = aggregate;
+ List uncommittedEvents = agg.getUncommittedEvents()
+ .list();
Event event = uncommittedEvents.get(0);
-
- assertEquals(this.aggregate.version(), event.context()
- .getVersion());
+ EventContext context = event.context();
+ assertThat(aggregate.version())
+ .isEqualTo(context.getVersion());
}
@Test
@@ -719,7 +726,8 @@ void throughNewestEventsFirst() {
private ProtoSubject assertNextCommandId() {
Event event = history.next();
- return assertThat(event.rootMessage().asCommandId());
+ return assertThat(event.rootMessage()
+ .asCommandId());
}
@Test
@@ -818,27 +826,20 @@ void checkEventsUponHistory() {
private static void dispatch(TenantId tenant,
Supplier> endpoint) {
with(tenant).run(
- () -> endpoint.get().dispatchTo(ID)
+ () -> endpoint.get()
+ .dispatchTo(ID)
);
}
@Nested
@DisplayName("create a single event when emitting a pair without second value")
- class CreateSingleEventForPair {
-
- private BlackBoxContext context;
-
- @BeforeEach
- void prepareContext() {
- context = BlackBoxContext.from(
- BoundedContextBuilder.assumingTests()
- .add(new TaskAggregateRepository())
- );
- }
+ class CreateSingleEventForPair extends ContextAwareTest {
- @AfterEach
- void closeContext() {
- context.close();
+ @Override
+ protected BoundedContextBuilder contextBuilder() {
+ return BoundedContextBuilder
+ .assumingTests()
+ .add(new TaskAggregateRepository());
}
/**
@@ -852,10 +853,10 @@ void closeContext() {
@Test
@DisplayName("when dispatching a command")
void fromCommandDispatch() {
- context.receivesCommand(createTask())
- .assertEvents()
- .withType(AggTaskCreated.class)
- .isNotEmpty();
+ context().receivesCommand(createTask())
+ .assertEvents()
+ .withType(AggTaskCreated.class)
+ .isNotEmpty();
}
/**
@@ -870,8 +871,8 @@ void fromCommandDispatch() {
@Test
@DisplayName("when reacting on an event")
void fromEventReact() {
- EventSubject assertEvents = context.receivesCommand(assignTask())
- .assertEvents();
+ EventSubject assertEvents = context().receivesCommand(assignTask())
+ .assertEvents();
assertEvents.hasSize(2);
assertEvents.withType(AggTaskAssigned.class)
.hasSize(1);
@@ -891,8 +892,8 @@ void fromEventReact() {
@Test
@DisplayName("when reacting on a rejection")
void fromRejectionReact() {
- EventSubject assertEvents = context.receivesCommand(reassignTask())
- .assertEvents();
+ EventSubject assertEvents = context().receivesCommand(reassignTask())
+ .assertEvents();
assertEvents.hasSize(2);
assertEvents.withType(AggCannotReassignUnassignedTask.class)
.hasSize(1);
@@ -900,4 +901,46 @@ void fromRejectionReact() {
.hasSize(1);
}
}
+
+ @Nested
+ @DisplayName("allow having validation on the aggregate state and")
+ class AllowValidatedAggregates extends ContextAwareTest {
+
+ private final ThermometerId thermometer = ThermometerId.generate();
+
+ @Override
+ protected BoundedContextBuilder contextBuilder() {
+ return BoundedContextBuilder
+ .assumingTests()
+ .add(new SafeThermometerRepo(thermometer));
+ }
+
+ @Test
+ @DisplayName("not change the Aggregate state when there is no reaction on the event")
+ void notChangeStateIfNoReaction() {
+ TemperatureChanged booksOnFire =
+ TemperatureChanged.newBuilder()
+ .setFahrenheit(451)
+ .vBuild();
+ context().receivesExternalEvent(booksOnFire)
+ .assertEntity(thermometer, SafeThermometer.class)
+ .doesNotExist();
+ }
+
+ @Test
+ @DisplayName("save valid aggregate state on change")
+ void safelySaveValidState() {
+ TemperatureChanged gettingWarmer =
+ TemperatureChanged.newBuilder()
+ .setFahrenheit(72)
+ .vBuild();
+ context().receivesExternalEvent(gettingWarmer);
+ Thermometer expected = Thermometer
+ .newBuilder()
+ .setId(thermometer)
+ .setFahrenheit(72)
+ .vBuild();
+ context().assertState(thermometer, expected);
+ }
+ }
}
diff --git a/server/src/test/java/io/spine/server/aggregate/given/thermometer/SafeThermometer.java b/server/src/test/java/io/spine/server/aggregate/given/thermometer/SafeThermometer.java
new file mode 100644
index 00000000000..dadf4fee9b6
--- /dev/null
+++ b/server/src/test/java/io/spine/server/aggregate/given/thermometer/SafeThermometer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021, TeamDev. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.server.aggregate.given.thermometer;
+
+import io.spine.core.External;
+import io.spine.server.aggregate.Aggregate;
+import io.spine.server.aggregate.Apply;
+import io.spine.server.aggregate.given.thermometer.event.TemperatureChanged;
+import io.spine.server.aggregate.given.thermometer.event.TermTemperatureChanged;
+import io.spine.server.event.React;
+
+import java.util.Optional;
+
+/**
+ * Ignores temperature changes outside its own range.
+ */
+public final class SafeThermometer extends Aggregate {
+
+ private static final double MIN = 0;
+ private static final double MAX = 120;
+
+ @React
+ Optional on(@External TemperatureChanged e) {
+ double temperature = e.getFahrenheit();
+ if (!withinBounds(temperature)) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ TermTemperatureChanged
+ .newBuilder()
+ .setThermometer(id())
+ .setChange(
+ TemperatureChange
+ .newBuilder()
+ .setNewValue(temperature)
+ .setPreviousValue(state().getFahrenheit())
+ )
+ .vBuild()
+ );
+ }
+
+ private static boolean withinBounds(double temperature) {
+ return temperature > MIN && temperature < MAX;
+ }
+
+ @Apply
+ private void on(TermTemperatureChanged e) {
+ builder().setFahrenheit(e.getChange()
+ .getNewValue());
+ }
+}
diff --git a/server/src/test/java/io/spine/server/aggregate/given/thermometer/SafeThermometerRepo.java b/server/src/test/java/io/spine/server/aggregate/given/thermometer/SafeThermometerRepo.java
new file mode 100644
index 00000000000..ef48add5221
--- /dev/null
+++ b/server/src/test/java/io/spine/server/aggregate/given/thermometer/SafeThermometerRepo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021, TeamDev. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.server.aggregate.given.thermometer;
+
+import io.spine.server.aggregate.AggregateRepository;
+import io.spine.server.aggregate.given.thermometer.event.TemperatureChanged;
+import io.spine.server.route.EventRouting;
+
+import static io.spine.util.Preconditions2.checkNotDefaultArg;
+
+/**
+ * A {@link SafeThermometer thermometer} repository.
+ */
+public final class SafeThermometerRepo extends AggregateRepository {
+
+ private final ThermometerId thermometer;
+
+ /**
+ * Creates a new repository for the {@code thermometer}.
+ */
+ public SafeThermometerRepo(ThermometerId thermometer) {
+ this.thermometer = checkNotDefaultArg(thermometer);
+ }
+
+ @Override
+ protected void setupEventRouting(EventRouting routing) {
+ routing.unicast(TemperatureChanged.class, (e) -> thermometer);
+ }
+}
diff --git a/server/src/test/java/io/spine/server/aggregate/given/thermometer/package-info.java b/server/src/test/java/io/spine/server/aggregate/given/thermometer/package-info.java
new file mode 100644
index 00000000000..1001511db32
--- /dev/null
+++ b/server/src/test/java/io/spine/server/aggregate/given/thermometer/package-info.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021, TeamDev. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * Test environment classes for the {@code Aggregate}
+ * {@link io.spine.server.aggregate.AggregateTest.AllowValidatedAggregates AllowValidatedAggregates}
+ * test.
+ */
+@CheckReturnValue
+@ParametersAreNonnullByDefault
+package io.spine.server.aggregate.given.thermometer;
+
+import com.google.errorprone.annotations.CheckReturnValue;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/src/test/java/io/spine/server/command/model/CommandReactionMethodTest.java b/server/src/test/java/io/spine/server/command/model/CommandReactionMethodTest.java
index 4e14ce88597..9a8744325f2 100644
--- a/server/src/test/java/io/spine/server/command/model/CommandReactionMethodTest.java
+++ b/server/src/test/java/io/spine/server/command/model/CommandReactionMethodTest.java
@@ -153,8 +153,7 @@ abstract static class EmptyReturn {
void setUp() {
target = supplier.get();
rawMethod = target.getMethod();
- method = signature.classify(rawMethod)
- .get();
+ method = signature.classify(rawMethod).get();
id = ProjectId.newBuilder()
.setId(newUuid())
.build();
@@ -190,8 +189,8 @@ void returnEmpty() {
DispatchOutcome outcome = method.invoke(target, envelope(message));
- assertThat(outcome.getSuccess().getProducedCommands().getCommandList())
- .isEmpty();
+ assertThat(outcome.getSuccess().hasCommands())
+ .isFalse();
}
private static CmdProjectCreated createVoidEvent(ProjectId givenId) {
@@ -256,9 +255,9 @@ private static void assertResult(DispatchOutcome outcome, ProjectId id) {
.build();
assertTrue(outcome.hasSuccess());
Success success = outcome.getSuccess();
- assertTrue(success.hasProducedCommands());
+ assertTrue(success.hasCommands());
List commands = success.getProducedCommands()
- .getCommandList();
+ .getCommandList();
List commandMessages = commands
.stream()
.map(Command::enclosedMessage)
diff --git a/server/src/test/proto/spine/test/aggregate/fibonacci/commands.proto b/server/src/test/proto/spine/test/aggregate/fibonacci/commands.proto
index 4b83564c95d..b72725f4e5b 100644
--- a/server/src/test/proto/spine/test/aggregate/fibonacci/commands.proto
+++ b/server/src/test/proto/spine/test/aggregate/fibonacci/commands.proto
@@ -22,7 +22,8 @@
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */syntax = "proto3";
+ */
+syntax = "proto3";
package spine.test.aggregate;
diff --git a/server/src/test/proto/spine/test/aggregate/thermometer/events.proto b/server/src/test/proto/spine/test/aggregate/thermometer/events.proto
new file mode 100644
index 00000000000..56e57ce25f6
--- /dev/null
+++ b/server/src/test/proto/spine/test/aggregate/thermometer/events.proto
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021, TeamDev. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+syntax = "proto3";
+
+package spine.test.aggregate;
+
+import "spine/options.proto";
+
+option (type_url_prefix) = "type.spine.io";
+option java_package = "io.spine.server.aggregate.given.thermometer.event";
+option java_multiple_files = true;
+
+import "spine/test/aggregate/thermometer/thermometer.proto";
+
+// A notion of the temperature change.
+message TemperatureChanged {
+
+ // The temperature in ℉.
+ double fahrenheit = 2;
+}
+
+// A temperature on a particular thermometer has changed.
+message TermTemperatureChanged {
+
+ // The ID of the thermometer on which the temperature has changed.
+ ThermometerId thermometer = 1 [(required) = true];
+
+ // The temperature change on the thermometer.
+ TemperatureChange change = 2 [(required) = true];
+}
diff --git a/server/src/test/proto/spine/test/aggregate/thermometer/thermometer.proto b/server/src/test/proto/spine/test/aggregate/thermometer/thermometer.proto
new file mode 100644
index 00000000000..3d1f39e7030
--- /dev/null
+++ b/server/src/test/proto/spine/test/aggregate/thermometer/thermometer.proto
@@ -0,0 +1,40 @@
+syntax = "proto3";
+
+package spine.test.aggregate;
+
+import "spine/options.proto";
+
+option (type_url_prefix) = "type.spine.io";
+option java_package = "io.spine.server.aggregate.given.thermometer";
+option java_multiple_files = true;
+
+
+// The unique factory-provided identifier of a thermometer.
+message ThermometerId {
+ string uuid = 1 [(required) = true];
+}
+
+// A US thermometer for mild-weather regions.
+//
+// This particular thermometer type is created for the mild-weather conditions and is not gonna
+// work in cold parts of the country while not being able to determine the cold temperature
+// under 0 ℉.
+//
+message Thermometer {
+ option (entity).kind = AGGREGATE;
+
+ ThermometerId id = 1 [(required) = true];
+
+ // The temperature in ℉.
+ double fahrenheit = 2 [(min).value = "0.1", (max).value = "120"];
+}
+
+// A change in the temperature.
+message TemperatureChange {
+
+ // The previous temperature in ℉.
+ double previous_value = 1;
+
+ // The previous temperature in ℉.
+ double new_value = 2;
+}
diff --git a/version.gradle.kts b/version.gradle.kts
index 18c1112df90..7fab7d0310f 100644
--- a/version.gradle.kts
+++ b/version.gradle.kts
@@ -34,7 +34,7 @@
/**
* Version of this library.
*/
-val coreJava = "1.7.1"
+val coreJava = "1.7.4"
/**
* Versions of the Spine libraries that `core-java` depends on.