Skip to content

1.9.0

Compare
Choose a tag to compare
@armiol armiol released this 20 May 13:48
· 21 commits to 1.x-dev since this release

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 a WorkerID of the worker who owns the session at the moment, and the Timestamp 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 for SystemSettings (#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 via GrpcContainer (#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 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 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 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 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 Sample type providing the generation of sample Proto messages was improved in relation to generation more humane String values (#1506).