Releases: SpineEventEngine/core-jvm
1.9.1
This is a minor release bringing a quality-of-life improvement, basing on Spine's real world usage.
EnvSetting utility (#1550)
The EnvSetting is a tool that allows configuring the value that may differ per environment type.
Previously it was an internal utility, but now it is available for all Spine-based projects.
Usage example:
EnvSetting<UserReader> setting = new EnvSetting<>();
setting.use(projectsUserReader(), Production.class)
.use(localUserReader(), Local.class)
.use(testsUserReader(), Tests.class);
// ...
// The instance is returned according to the current `Environment` type.
final UserReader reader = setting.value(); In addition to making this class public, some changes were made to it comparing to its internal version:
- introduced method chaining for a more fluent and convenient API (as shown in the example);
- added a public endpoint to access the value configured for the current environment type;
- API is made thread-safe, allowing concurrent read operations while ensuring exclusive access for write operations to maintain data integrity.
Migration
This release is fully compatible with core-java libraries 1.9.0. No special migration steps required.
1.9.0
This is part of Spine 1.9.0 release.
This update brings a number of API changes, and also addresses several known issues.
Breaking changes
The API of the ShardedWorkRegistry has been changed.
In particular, a new PickUpOutcome pickUp(ShardIndex index, NodeId node) method is introduced. Note, it returns an explicit result instead of Optional, as previously. This outcome contains either of two:
ShardSessionRecord— meaning that the shard is picked successfully,ShardAlreadyPickedUp— a message that contains aWorkerIDof the worker who owns the session at the moment, and theTimestampwhen the shard was picked. This outcome means the session cannot be obtained as it's already picked.
Also, there is a new void release(ShardSessionRecord session) method that releases the passed session.
Here is a summary of code changes for those using ShardedWorkRegistry:
Before:
Optional<ShardProcessingSession> session = workRegistry.pickUp(index, currentNode);
if (session.isPresent()) { // Check if shard is picked.
// ...
session.get().complete(); // Release shard.
}After:
PickUpOutcome outcome = workRegistry.pickUp(index, currentNode);
if (outcome.hasSession()) { // Check if shard is picked
// ...
workRegistry.release(outcome.getSession()); // Release shard.
}Also, the new API allows getting the WorkerId of the worker who owns the session in case if the shard is already picked by someone else and the Timestamp when the shard was picked:
PickUpOutcome outcome = workRegistry.pickUp(index, currentNode);
if (outcome.hasAlreadyPickedBy()) {
WorkerId worker = outcome.getAlreadyPicked().getWorker();
Timestamp whenPicked = outcome.getAlreadyPicked().getWhenPicked();
// ...
}Other changes
- Custom
ExecutorforSystemSettings(#1448).
Now, SystemSettings allows customizing an Executor to post the system events in parallel. This provides an opportunity to improve the control over the available CPU resources on a server instance.
var builder = BoundedContextBuilder.assumingTests();
var executor = ...;
builder.systemSettings()
.enableParallelPosting()
.useCustomExecutor(executor);- Customization of gRPC
ServerviaGrpcContainer(#1454).
It is now possible to access an underlying instance of Server's builder when configuring the GrpcContainer:
GrpcContainer.atPort(1654)
// `server` is an instance of `io.grpc.ServerBuilder`.
.withServer((server) -> server.maxInboundMessageSize(16_000_000))
// ...
.build();This API is experimental and may change in future versions of Spine.
- Thorough copying of Bounded Contexts by
BlackBoxContext's builder (#1495).
Previously, the BlackBoxContext instances were built on top of BoundedContextBuilders by copying the internals of the latter builder. However, not all of the parts were copied properly.
This release improves the copying by including more pieces from the source BoundedContextBuilder. In particular, all changes made to BoundedContextBuilder.systemSettings() are now transferred as well.
- Custom handlers for failed delivery of a signal (#1496).
Now, the Delivery API allows to subscribe for any failures which occur during the reception of each signal. Additionally, end-users may now choose the way to handle the reception failures in terms of action in respect to the InboxMessage of interest.
Out-of-the-box, end-users are provided with two options:
- mark the
InboxMessageas delivered — so that it does not block further delivery of messages; - repeat the dispatching of
InboxMessagein a synchronous manner.
Alternatively, end-users may implement their own way of handling the reception failure.
The corresponding functionality is provided via the API of DeliveryMonitor:
public final class MarkFailureDelivered extends DeliveryMonitor {
/**
* In case the reception of the {@code InboxMessage} failed,
* mark it as {@code DELIVERED} anyway.
*/
@Override
public FailedReception.Action onReceptionFailure(FailedReception reception) {
//// Error details are available as well:
// InboxMessage msg = reception.message();
// Error error = reception.error();
// notifyOf(msg, error);
return reception.markDelivered();
}
}
// ...
// Plugging the monitor into the Delivery:
DeliveryMonitor monitor = new MarkFailureDelivered();
Delivery delivery = Delivery.newBuilder()
.setMonitor(monitor)
// ...
.build();
ServerEnvironment
.when(MyEnvironment.class)
.use(delivery);By default, InboxMessages are marked as DELIVERED in case of failure of their reception.
- Prohibit calling
state()from from@Apply-ers (#1501).
It is now not possible to call Aggregate.state() from @Apply-ers. Previously, it was possible, but as discovered from real-world cases, such a functionality is prone to logical errors. End-users must use Aggregate.builder() instead.
- Fix delivering signals to aggregate
Mirrors in a multi-Bounded Context environment (#1502).
Previously, when several Bounded Contexts had their Aggregates "visible" (i.e. exposed via Mirror), the delivery mechanism was confused with multiple Mirror entity types which technically were distinct, but at the same time had exactly the same Type URL. Such a use-cases led to failures when Aggregate state on read-side is updated by the framework code.
This release alters Type URLs, under which Mirror projections register themselves in Delivery. The new type URL value includes the name of the Bounded Context — which makes this type URL invalid in terms of type discovery, but addresses the issue.
- Importing domain events from 3rd-party contexts properly in multi-tenant environments (#1503).
Previously, in a multi-tenant application, the imported events were dispatched in a straightforward manner, without specifying the TenantId in the dispatching context. Now, this issue is resolved.
- Allow subscribers to receive a notification once an Entity stops matching the subscription criteria (#1504).
Starting this release, clients of gRPC Subscription API will start receiving updates once entities previously included into some subscription as matching, are modified and no longer pass the subscription criteria.
In particular, this will always be the case if an Entity becomes archived or deleted.
The new endpoint is available for Spine client under whenNoLongerMatching() DSL, and is a part of Client's request API:
Client client = client();
client
/* ... */
.subscribeTo(Task.class)
.observe((task) -> { /* ... */ })
.whenNoLongerMatching(TaskId.class, (idOfNonMatchingEntity) -> { /* ... */})
.post();- More granularity into
Shardpick-up results (#1505).
In this release we start to distinguish the shard pick-up results. In particular, it is now possible to find out the reason of an unsuccessful shard pick-up. In particular, there may be some runtime issues, or a shard may already be picked-up by another worker.
Two new API endpoints were added to the DeliveryMonitor to provide end-users with some control over such cases:
FailedPickUp.Action onShardAlreadyPicked(AlreadyPickedUp failure)
Invoked if the shared is already picked by another worker. The callback provides some insights into the pick-up failure, such as ID of the worker currently holding the shard, and Timestamp of the moment when the shard was picked by it.
It is also required to return an action to take in relation to this case. By default, an action silently accepting this scenario is returned. End-users may implement their own means, e.g. retrying the pick-up attempt:
final class MyDeliveryMonitor extends DeliveryMonitor {
...
@Override
public FailedPickUp.Action onShardAlreadyPicked(AlreadyPickedUp failure) {
return failure.retry();
}
...
}FailedPickUp.Action onShardPickUpFailure(RuntimeFailure failure)
This method is invoked if the shard could not be picked for some runtime technical reason. This method receives the ShardIndex of the shard that could not be picked, and the instance of the occurred Exception. It also requires to return an action to handle this case. By default, such failures are just rethrown as RuntimeExceptions, but end-users may choose to retry the pick-up:
final class MyDeliveryMonitor extends DeliveryMonitor {
...
@Override
public FailedPickUp.Action onShardPickUpFailure(RuntimeFailure failure) {
return failure.retry();
}
...
}- A build-in
Sampletype providing the generation of sample Proto messages was improved in relation to generation more humaneStringvalues (#1506).
1.8.2
This is a maintenance release of Spine core libraries. It extends the existing API, primarily focusing on adding new configuration knobs.
In particular, these changes were made since the last release:
-
#1443: Expand the
io.spine.server.ServerAPI to allow to:- obtain the Query, Subscription, and Command services after the
Serveris built; - add extra gRPC services to the same server.
- obtain the Query, Subscription, and Command services after the
-
#1448: Allow supplying
ExecutorforSystemWriteSide. -
#1454: Allow to configure the underlying gRPC server via
GrpcContainerAPI.
No breaking changes to the existing API were made.
1.8.0
This release brings numerous fixes, updates to API and performance improvements.
Breaking Changes
-
Instead of
NodeId,ShardSessionRecordnow usesWorkerIdto indicate who is currently processing which shard.Thus, all shard processing sessions should be completed before the migration.
Please see #1433 for details.
API Changes
-
Made
BlackBoxContextimplementCloseable(as addition of #1402). -
BlackBoxContextAPI has been extended to provide an instance ofClientlinked to the context under the test.It makes possible to use
Clientin tests, for example:BlackBoxContext context = BlackBoxContext.from(...); ClientRequest clientRequest = context.client().asGuest(); assertThat(clientRequest.select(ProjectView.class).run()) .hasSize(0); clientRequest.command(createProject()).postAndForget(); assertThat(clientRequest.select(ProjectView.class).run()) .hasSize(1);
Please note, that provided
Clientwould inheritTenantIdfromBlackBoxContext, but would NOT inheritUserIdandZoneId.Check #1407 for details.
-
Made API calls for the conditional settings of
ServerEnvironment"lazy".Previously, ServerEnvironment provided two kinds of API calls for the conditional settings:
ServerEnvironment.when(Staging.class) // This call is handy when `myStorageFactory` is already available. .use(myStorageFactory) ServerEnvironment.when(PreProduction.class) // And this one looks lazy, so that it is only executed when and _if_ requested. .use((env) -> createMySqlStorage())
However, in fact, there was no "lazy" behavior, which caused numerous workarounds to actually postpone the initialization of environment-specific settings until they start to make sense.
This release addresses the issue by making the behavior truly "lazy" (see #1421).
Fixes
-
Enabled
IntegrationBrokerdispatch events regardless of registration order of subscribing and publishing Bounded Contexts (see #1402). -
Transformation of an Entity's state during
Migrationhas been changed so that thenewStatecompletely overwrites the old one within the migration transaction (see #1405). -
The internals of
IntegrationBrokerwere made more thread-safe (see #1423). -
SubscriptionServicenow properly locates the Bounded Context when subscribing to events produced by standalone producers, such as descendants ofAbstractCommandHandlerorAbstractEventReactor(see #1423).
Performance
- Improved Catch-up caching (see #1406 for details).
1.7.6
This is another patch release of Spine 1.x. It addresses some issues and inconveniences operating the Projection catch-up.
- It is now not possible to start the catch-up for Aggregate's
MirrorProjection— as such projections are built from system events only, and their catch-up makes not much sense. - The run-time of catch-up is made more stable, taking into account the experience of exploring this feature in production use cases.
1.7.5
This is a patch release of Spine 1.x. It follows up on the production use of Spine libraries and brings a few requested improvements.
- The integration with gRPC server has been improved for better results in dealing with heavy-duty subscriptions.
BlackBoxContextAPI has been extended with a few endpoints to test for the positive outcomes.
Additionally, several improvements of config scripts were brought to this version.
Compare v1.7.0 and v1.7.5.
1.7.0
This release brings numerous API improvements, as well as fixes and infrastructure updates to the framework.
Breaking Changes
-
The
BlackBoxContext-based tests now fail if a runtime exception was thrown within the signal handlers.In order to address #1314, we've decided to enforce the fail-fast approach within the BBC tests done in #1322. From now on, if a test case had any runtime exceptions thrown from signal handlers the test is failed by the BBC. While it may be a breaking change for some, we believe it worth fixing such issues right away than hiding them under the carpet.
If one requires to fall back to the previous behavior, the BBC instance can be configured using the newly introduced
tolerateFailuresmethod. -
The
grand_originfield is no longer set to a default instance for the signalOriginif no grand origin is present (see #1341 for details). -
The
ServerEnvironmentAPI is improved and changed as a result of the #1315 and related discussions in a series of PRs (#1327, #1331, #1336).The previously deprecated
configure...()API is removed in favor of the new fluentwhen().use()API.So now, instead of smth like this:
ServerEnvironment.use(productionStorageFactory, Production.class) .use(testingStorageFactory, Tests.class) .use(memoizingTracerFactory, Production.class)
One should use the following snippet:
ServerEnvironment.when(Production.class) .use(productionStorageFactory) .use(memoizingTracerFactory); ServerEnvironment.when(Tests.class) .use(testingStorageFactory);
API Changes
-
Changetype validation requirements are relaxed for primitive type changes.The
new_valuefield is no longer a required one forStringChangeandBytesChangetypes. See #1307 for details. -
Introduced simplified
unicast()methods in theEventRouting.The new
unicast()API allows simplifying and prettifying the even routing for the cases where a singular ID is used.Now, instead of smth like this:
@OverridingMethodsMustInvokeSuper @Override protected void setupEventRouting(EventRouting<AirportId> routing) { super.setupEventRouting(routing); routing.route(FlightScheduled.class, (e, ctx)-> EventRoute.withId(e.getAirport())); }
One can use the following API:
@OverridingMethodsMustInvokeSuper @Override protected void setupEventRouting(EventRouting<AirportId> routing) { super.setupEventRouting(routing); routing.unicast(FlightScheduled.class, FlightScheduled::getAirport) }
To find out more details on the new API please check #1317.
-
Added
localDateandlocalDateTimehelpers to theWithTimeinterface.This feature allows accessing
localDateandlocalDateTimemethods from within all the signals. From now on, if one requires a JavaLocalDateorLocalDateTimeinstances over the ProtobufTimestampor JavaInstantthat were previously available for signals, they may use the new API to simplify such a conversion and access to the signalstimestampfield.See #1319 for additional details.
Fixes
-
Improved multitenant delivery support by ensuring the tenant is properly propagated within the delivery (see #1308).
-
Fixed a typo in the
io.spine.client.ClientshutdownTimeoutmethod (see #1339). -
Fixed dispatching of rejections caused by a particular command (see #1318 and #1343).
Infrastructure
- The libraries now do not use
implementationfor compile-only annotations likeerrorproneannotations but use the newly introducedcompileOnlyApiconfiguration for such dependencies (see #1340).
Compare v1.6.0 and v1.7.0.
1.6.0
This release brings numerous API improvements, as well as fixes and infrastructure updates to the framework.
API changes
Client
- The ability to
postAndForget()a command is added to theClient. This method should be called when the user does not care about events/rejections produced by a command.
The previously usedpost()method is reserved for cases when one or more event types are actually observed by the client. The value returned bypost()can no longer be ignored [#1292]. - Event subscriptions are now more flexible, allowing to subscribe to events produced by non-entity objects (e.g.
AbstractEventReactor) as well as events not explicitly declared in anyBoundedContext[#1258]. - The
Clientis extended with methods to handle streaming and server errors when executing requests [#1270].
Server
- The custom environments support is introduced [#1274, #1293].
TheEnvironmentnow exposes API to register user-defined environment types and to determine which one is enabled at any given moment of time. See the release notes ofbase.
TheServerEnvironmentallows to configure environment-dependent values, as follows:
StorageFactory factory = InMemoryStorageFactory.newInstance();
ServerEnvironment.instance()
.use(factory, Tests.class);
The Spine framework provides two environments out of the box: Production and Tests.
- Breaking change: Most of the
@Internalmethods ofBoundedContextmoved to its internal classInternalAccessinstance of which is available via theinternalAccess()method.
The method is available only to the server-side framework code. - Delivery API is extended with a factory method which allows to create asynchronous version of local
Delivery[#1265]. - The
Paircan now be created from an already existingOptional[#1296]. - The proper support to the
CommandBusfilters which throw rejections is added [#1295].
Model
- The
@Externalannotation is introduced to mark the handler method parameters of an external origin. It replaces the previously used for this purpose(external = true)attribute of@Subscribe,@React, and@Commandannotation. The attribute is deprecated [#1269]. (set_once)constraint in entity states is no longer ignored [#1268].@ByFieldis deprecated in favour of@Where[#1270].
Logging
- The
DiagnosticLogmessages are made more detailed [#1262]. - The standard framework exceptions are expanded with more info [#1255].
Testing
Various quality-of-life changes are introduced for the testing API.
See #1249, #1251, #1252, and #1261 for details.
Some of the testing API changes are breaking. They include:
BlackBoxBoundedContextis renamed toBlackBoxContext.- Outdated
Verify-based API is removed. BlackBoxContextno longer exposeseventBus()andcommandBus().- The
BlackBoxContext.subscribeTo(Topic)semantics changed. See #1249. - Simplified combinations of
UserIdandZoneIdparameters ofBlackBoxContext.
Fixes
The Migration logic is fixed to properly support entity state updates [#1298].
Infrastructure
The project build scripts are migrated to Kotlin [#1278].
1.5.0
This major update of the library brings a number of new features and performance improvements.
- Projections now support an automated run-time catch-up (#1221).
- The client API was made less error-prone by enforcing the strong typing for the columns and properties of Entities and Events. The list of the model fields is generated as Java code by the Spine compiler at build-time (#1229, #1246).
- A data migration API is now available for
ProcessManagers andProjections. It automates the process of updating the existing data upon the changes in the domain model (#1241). - The logging records made from within the Entity handlers now print the full signature of the called method (#1242).
- It is now possible to specify an actor when composing a test scenario with
BlackBoxBoundedContextAPI (#1242). CommandBusis no longer responsible for validating the first field of the transmittedCommands; instead, this is a job of theRepositoryfor the targetEntity(#1245).
The API of InboxMessage has been changed so that any of the existing InboxMessages become incompatible (#1239). Please make sure to deliver the messages from all of your production Inboxes prior to updating to the new version.
Also, a number of minor improvements and issues were addressed. Please see the list of closed pull requests for more details.
1.4.0
This release concentrates mainly on performance improvements.
- The new generated validation API is now available with the beta status.
(pattern)option now only applies to non-empty string values.- The bug preventing users from declaring enum-type columns is fixed.
- API of code libraries is extended.