Skip to content

Releases: SpineEventEngine/core-java

1.9.1

28 May 18:05
Compare
Choose a tag to compare

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

20 May 13:48
Compare
Choose a tag to compare

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).

1.8.2

20 Jun 12:04
f33d010
Compare
Choose a tag to compare

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.Server API to allow to:

    • obtain the Query, Subscription, and Command services after the Server is built;
    • add extra gRPC services to the same server.
  • #1448: Allow supplying Executor for SystemWriteSide.

  • #1454: Allow to configure the underlying gRPC server via GrpcContainer API.

No breaking changes to the existing API were made.

1.8.0

15 Dec 17:57
3727246
Compare
Choose a tag to compare

This release brings numerous fixes, updates to API and performance improvements.

Breaking Changes

  • Instead of NodeId, ShardSessionRecord now uses WorkerId to 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 BlackBoxContext implement Closeable (as addition of #1402).

  • BlackBoxContext API has been extended to provide an instance of Client linked to the context under the test.

    It makes possible to use Client in 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 Client would inherit TenantId from BlackBoxContext, but would NOT inherit UserId and ZoneId.

    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 IntegrationBroker dispatch events regardless of registration order of subscribing and publishing Bounded Contexts (see #1402).

  • Transformation of an Entity's state during Migration has been changed so that the newState completely overwrites the old one within the migration transaction (see #1405).

  • The internals of IntegrationBroker were made more thread-safe (see #1423).

  • SubscriptionService now properly locates the Bounded Context when subscribing to events produced by standalone producers, such as descendants of AbstractCommandHandler or AbstractEventReactor (see #1423).

Performance

  • Improved Catch-up caching (see #1406 for details).

1.7.6

28 Sep 13:39
Compare
Choose a tag to compare

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

02 Sep 12:19
Compare
Choose a tag to compare

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.
  • BlackBoxContext API 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

14 Dec 15:59
7e994be
Compare
Choose a tag to compare

This release brings numerous API improvements, as well as fixes and infrastructure updates to the framework.

Breaking Changes

  1. 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 tolerateFailures method.

  2. The grand_origin field is no longer set to a default instance for the signal Origin if no grand origin is present (see #1341 for details).

  3. The ServerEnvironment API 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 fluent when().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

  1. Change type validation requirements are relaxed for primitive type changes.

    The new_value field is no longer a required one for StringChange and BytesChange types. See #1307 for details.

  2. Introduced simplified unicast() methods in the EventRouting.

    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.

  3. Added localDate and localDateTime helpers to the WithTime interface.

    This feature allows accessing localDate and localDateTime methods from within all the signals. From now on, if one requires a Java LocalDate or LocalDateTime instances over the Protobuf Timestamp or Java Instant that were previously available for signals, they may use the new API to simplify such a conversion and access to the signals timestamp field.

    See #1319 for additional details.

Fixes

  1. Improved multitenant delivery support by ensuring the tenant is properly propagated within the delivery (see #1308).

  2. Fixed a typo in the io.spine.client.Client shutdownTimeout method (see #1339).

  3. Fixed dispatching of rejections caused by a particular command (see #1318 and #1343).

Infrastructure

  1. The libraries now do not use implementation for compile-only annotations like errorprone annotations but use the newly introduced compileOnlyApi configuration for such dependencies (see #1340).

Compare v1.6.0 and v1.7.0.

1.6.0

09 Sep 17:35
995bdcf
Compare
Choose a tag to compare

This release brings numerous API improvements, as well as fixes and infrastructure updates to the framework.

API changes

Client

  1. The ability to postAndForget() a command is added to the Client. 
This method should be called when the user does not care about events/rejections produced by a command.
    The previously used post() method is reserved for cases when one or more event types are actually observed by the client. The value returned by post() can no longer be ignored [#1292].
  2. 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 any BoundedContext [#1258].
  3. The Client is extended with methods to handle streaming and server errors when executing requests [#1270].

Server

  1. The custom environments support is introduced [#1274, #1293].
    
The Environment now 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 of base.
    The ServerEnvironment allows 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.

  1. Breaking change: Most of the @Internal methods of BoundedContext moved to its internal class InternalAccess instance of which is available via the internalAccess() method.
    The method is available only to the server-side framework code.
  2. Delivery API is extended with a factory method which allows to create asynchronous version of local Delivery [#1265].
  3. The Pair can now be created from an already existing Optional [#1296].
  4. The proper support to the CommandBus filters which throw rejections is added [#1295].

Model

  1. The @External annotation 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 @Command annotation.
The attribute is deprecated [#1269].
  2. (set_once) constraint in entity states is no longer ignored [#1268].
  3. @ByField is deprecated in favour of @Where [#1270].

Logging

  1. The DiagnosticLog messages are made more detailed [#1262].
  2. 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:

  1. BlackBoxBoundedContext is renamed to BlackBoxContext.
  2. Outdated Verify-based API is removed.
  3. BlackBoxContext no longer exposes eventBus() and commandBus().
  4. The BlackBoxContext.subscribeTo(Topic) semantics changed. See #1249.
  5. Simplified combinations of UserId and ZoneId parameters of BlackBoxContext.

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

08 Mar 14:38
8963846
Compare
Choose a tag to compare

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 and Projections. 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 BlackBoxBoundedContext API (#1242).
  • CommandBus is no longer responsible for validating the first field of the transmitted Commands; instead, this is a job of the Repository for the target Entity (#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

08 Jan 10:05
d3db90e
Compare
Choose a tag to compare

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.