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 aWorkerID
of the worker who owns the session at the moment, and theTimestamp
when 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
Executor
forSystemSettings
(#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
Server
viaGrpcContainer
(#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 BoundedContextBuilder
s 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
InboxMessage
as delivered — so that it does not block further delivery of messages; - repeat the dispatching of
InboxMessage
in 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
Mirror
s in a multi-Bounded Context environment (#1502).
Previously, when several Bounded Contexts had their Aggregate
s "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
Shard
pick-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 RuntimeException
s, 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
Sample
type providing the generation of sample Proto messages was improved in relation to generation more humaneString
values (#1506).