Posted on October 17, 2017

Spine 0.10.0

Core Java

This release brings even more features to the framework, making it very close to 1.0 release.

IMPORTANT. The framework package names were renamed from org.spine3.* to io.spine.*.

Here is a quick summary of changes. For more details please refer to the list of pull requests and Javadocs.

Reacting upon Events and Rejections

It is now possible to emit events in reaction to other events. This is particularly useful for AggregateParts:

/**
 * A part of {@code Task}  aggregate. Defines the essence of a task.
 */
public class TaskDefinition 
    extends AggregatePart<TaskId, Task, TaskVBuilder, TaskRoot> {

    @Assign
    public TaskCompleted handle(CompleteTask cmd) {
        // Handle the command and emit a `TaskCompleted` event.
        // ...
        return taskCompletedEvent;
    }

    @Apply
    void on(TaskCompleted event) {
        // Update state accordingly.
        // ...
    }
} 

/**
 * Another part of {@code Task}  aggregate. Responsible for all comments 
 * of a single task.
 */
public class TaskComments 
    extends AggregatePart<TaskId, TaskComments, TaskCommentsVBuilder, TaskRoot> {

    @React
    public TaskCommentingDisabled handle(TaskCompleted event) {
        // Disable commenting of the completed task.
        //...
        return commentingDisabledEvent;
    }

    @Apply
    void on(TaskCommentingDisabled event) {
        // Update own state as well.
        // ...
    }
}

In the example above TaskComments reacts to the event, emitted by another part and updates own state. Before such a feature became available, an intermediate ProcessManager should have been used for this purpose.

It is also possible to @React upon a rejection in the same manner.

This feature is available for Aggregate, AggregatePart and ProcessManager entities.

Entity API Updates

All the Entity implementations, which are able to handle events, are now descendants of a newly introduced EventPlayingEntity. Its API allows to play events in a unified fashion.

Entity state modification is now available via ValidatingBuilder.

The direct state update, which was previously available to Projections, Aggregates and ProcessManagers via numerous methods (such as updateState and incrementState), is NOT available anymore. The designed way to modify the entity is via getBuilder().set... calls.

In addition, an automatic version management is introduced. For each applied event the version is automatically incremented. Direct version modification is not exposed as public API anymore.

For more details please see #466.

Entity Column Queries

It is now possible to use EntityColumns in the queries:

public class CustomerProjection 
    extends Projection<CustomerId, Customer, CustomerVBuilder> {

    // ...

    // Defines an entity column.
    public PersonName getContactName() {
        return getState().getContactPerson().getName();
    }
}

Then in your client code:

// Retrieve customers by a contact name.

// Obtain the parameters for the query from somewhere like user interface.
final PersonName contactName = getContactName();

// Create the `Query` instance.
final Query customerQuery = myRequestFactory.query()
                                            .select(Customer.class)
                                            .withMask("id", "address", "domain")
                                            .where(eq("contactName", contactName))
                                            .build();

// Get the client stub of the `QueryService`.
final QueryService queryService = getServerApi();

// Execute the query, setting the callback for results.
queryService.read(query, callback);

In the example above the results will contain customers, whose contactPerson.name value matches the one set in the query.

IntegrationBus

To integrate several BoundedContexts, an IntegrationBus is defined. It allows to subscribe to so called “external” events in one BoundedContext to receive events of a required type from another BoundedContext.

E.g. the first bounded context Projects defines an external event handler method in the projection as follows:

public class ProjectListView extends Projection<...> {

    @Subscribe(external = true)
    public void on(UserDeleted event) {
        // Remove the projects that belong to this user.
        // ...
    }

    // ...
}

Let’s say the second bounded context is Users. In case these bounded contexts share the same transport, any UserDeleted event emitted inside of Users will also get delivered to ProjectListView of Projects bounded context. It will be done via IntegrationBus.

For more details see #580.

One-liner updates

  • Base data types and utilities have been extracted into base repository.
  • core module was introduced in the core-java repository; it defines classes and interfaces, that are essential for building application in the Event Sourcing paradigm.
  • Custom Protobuf options defined by the framework now take numbers in the range 73812-75000.
  • Failure is renamed to Rejection to comply with Reactive Manifesto definition of “Failure” better.
  • EndpointDelivery strategy is now available; it allows to define how messages are delivered to particular Entity instances.
  • Entity visibility options are added; VisibilityGuard is a registry of repositories that controls an access.

Version updates

  • Gradle 4.1.
  • Protobuf 3.3.0.
  • gRPC 1.4.0.