Releases: SpineEventEngine/core-java
1.0.0-pre1
This is a preview release of the Spine 1.0 version.
New Maven Repository
In this version one more artifact repository is introduced:
- Releases: https://spine.mycloudrepo.io/public/repositories/releases
- Snapshots: https://spine.mycloudrepo.io/public/repositories/snapshots
The previously used repository is also maintained.
Features and Improvements
Since 0.10.0 several significant features were introduced, making the framework near the feature completeness:
- Provide automatic idempotency for the business entities.
- Allow to subscribe to the entity state updates.
- Allow to produce a command in response to an event or a command — the
@Command
ing methods. - Introduce
System
bounded context, which tracks the history of eachCommand
,Event
andEntity
, allowing to trace paths and consequences of incoming end-user imperatives. - Make
Rejection
s a special kind ofEvent
s to simplify thinking for framework users.
For more details on the features please see #733, #804 and #810 and consult the source code.
In addition the framework now uses Error Prone with a conjunction with the Checker Framework, which allows to catch the potential issues on a compilation stage.
Lots of other less significant improvements were introduced, addressing the issues reported from the real-world use-cases.
Dependency Updates
The framework dependencies were updated as follows.
- gRPC 1.15.0
- Protobuf 3.6.1
- Protobuf Gradle plugin 0.8.5
- Guava 26.0-jre
- JUnit 5.3.1
- Google Truth 0.42
0.10.0
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 Event
s and Rejection
s
It is now possible to emit events in reaction to other events. This is particularly useful for AggregatePart
s:
/**
* 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 Projection
s, Aggregate
s and ProcessManager
s 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 EntityColumn
s 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 BoundedContext
s, 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 thecore-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 toRejection
to comply with Reactive Manifesto definition of “Failure” better.EndpointDelivery
strategy is now available; it allows to define how messages are delivered to particularEntity
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.
Version 0.9.0
This version is a finalization of the framework API and most of the core features, that are scheduled for 1.0 release.
It also includes a lot of API polishing. Some of the changes are breaking, some are not.
The inner code structure (packaging, visibility of classes etc) has been modified significantly in order to follow the Onion architecture and become easier for understanding.
Below is a list of the most valuable changes and the references to the corresponding pull requests. More feature details are available upon the PR link.
- Multitenancy support. Please see
org.spine3.server.tenant
package for the details.
Client-side features:
ActorRequestFactory
is introduced as a single client-side utility to produce Commands, Queries and Topics: #431.- IDs now belong to
Command
,Event
andFailure
, not to their contexts: #450, #452. - An ability to define the custom
Command
attributes is introduced to the public API (#448). Also,Command
now has the system-only properties hidden from the public access (#455). ActorContext
is now a unified context, reflecting the properties of an environment, where aCommand
,Query
orTopic
instance is created.- Fail-fast
Command
creation is available viaValidatingBuilder
s. They are automatically generated for eachCommand
and serve to validate the field values while buildingCommand
messages. Stand
input parameters are now validated according to the Protobuf annotations set for the requests: #437.
Server-side changes:
- It is now possible to
@Subscribe
to Failures: #362, #299. FailureThrowable
API simplification: #454.- Entity lifecycle:
- Improvements to entity versioning: #346.
AggregatePart
andAggregateRoot
API improvements: #347.- Changes to
Command
dispatchers and handlers: #354, #358. - Simpler routing of commands for Process Managers: #313.
StorageFactory
is made more configurable for different environments. In particular, it is now easier to provide different Storage implementations for testing and production deployment cases: #289, #290.
It is now possible to store some ofEntity
fields separately to allow the further efficient querying: #398.CommandStore
andEventStorage
are now Repositories, based uponRecordStorage
: #394, #395.
Utilities:
StringifierRegistry
is introduced to organize and simplify string-to-object and object-to-string conversion for Entity state objects and other user-defined types: #389, #382, #383, #378, #423, #436.- Improvements to Date/Time API: #387.
Lots of less valuable changes were made as well, including performance improvements, extended configurability of the framework building blocks, more helper utilities and object factories for testing.
Version updates:
- gRPC 1.1.2 (#341).
- Gradle 3.5.
New test dependencies:
- Guava-testlib.
Version 0.8.0
The update 0.8.0 is aimed to extend and polish the existing framework API, making the code less repetitive and more testable. We also tried to handle the most common scenarios to reduce the number of boilerplate code in applications built on top of Spine.
Aggregate
API is extended
As learned from the real-world examples, sometimes the domain rules expressed within an Aggregate
can become an overwhelming responsibility for a single class. That’s why AggregatePart
and AggregateRoot
abstractions are introduced to represent a large Aggregate
as a set of smaller objects.
Such an approach also allows to access different parts of a composite Aggregate separately, improving the system performance.
Please see org.spine3.server.aggregate
package for more details.
Improvements in the testing framework
To help building the unit tests faster and do not bother with the boilerplate code, new testing routines were introduced to org.spine3.test
package. The improvements include:
- easier testing of the commands and the entities which handle commands,
- helpers for quick creation of
Aggregate
,AggregatePart
,Projection
,ProcessManager
andEntity
instances, - routines for nullability checks.
Repackaging of Storage
implementations
Storage
classes were moved to the same classes as related Protobuf types. Some of the methods were demoted from public
to protected
in order to make the API more clear.
Generated classes and interfaces are now available under storage sub-packages, and these packages are annotated as @SPI
via package-info.java
.
Public API improvements and changes
StorageFactorySwitch
introduced
As of now it is possible to provide different StorageFactory
implementations for production and testing modes in a unified way, taking care of the environment outside of the business and test logic.
The Supplier
interface contract is used to allow lazy initialisation of the StorageFactory
instances and improve the app startup performance:
final Supplier<StorageFactory> productionSupplier = …;
final Supplier<StorageFactory> testingSupplier = …;
// ...
StorageFactorySwitch.init(productionSupplier, testingSupplier).
BoundedContext
contract changes
StorageFactory
for a BoundedContext
is now provided via lazy-init Supplier
to speed up the context instantiation.
The BoundedContext.Builder
API no longer requires StorageFactory
instance. If it not set explicitly, the current StorageFactorySwitch
value is used. So it’s now possible to create an instance as simple as
// Using the `StorageFactorySwitch` default.
final BoundedContext context = BoundedContext.newBuilder().build();
And one more change is made. Now the BoundedContext
class made final
to clearly state it is not designed for subclassing.
EventEnricher
and EventStore
configuration moved from BoundedContext
to EventBus
In order to make the configuration more logical and avoid potential cross-dependencies, the configuration of the items used by the EventBus
is now available via the EventBus.Builder
, not via the BoundedContext.Builder
:
// Spine 0.7.0:
BoundedContext.newBuilder()
.setEventStore(customEventStore)
.setEnricher(customEnricher)
//…
.build();
// Spine 0.8.0:
final EventBus eventBus = EventBus.newBuilder()
.setEventStore(customEventStore)
.setEnricher(customEnricher)
// …
.build();
BoundedContext.newBuilder()
.setEventBus(eventBus)
// …
.build();
More flexibility in event dispatching
A single Event
can now be delivered to multiple dispatchers of the same type at once.
For instance, each projection repository can be configured to route the same incoming SomeEvent
instance to a number of projections, managed by this repo. In this case each of the projection instances will receive exactly the same SomeEvent
instance.
It is also possible to ignore the incoming Event
by returning an empty set of target projection IDs. That may become handy to reflect a certain conditional behaviour of the read side.
This feature is available for both ProcessManagerRepository
and ProjectionRepository
. See EventDispatchingRepository.addIdSetFunction
for details.
Dynamic configuration of the event enrichment process
Previously the EventEnricher
must have been fully initialized by the moment of the BoundedContext
creation. That made it hard to use the AggregateRepository
instances in the enrichment functions, since AggregateRepository
required the BoundedContext
. There was a circular dependency:
EventEnricher
(wants to use) —> AggregateRepo (requires) —> BoundedContext
(requires) —> EventEnricher
.
In order to break the circle, it is made possible to add the new enrichment functions at runtime:
boundedContext.getEventBus().addFieldEnrichment(...)
Event enrichment features
It is now possible to specify the whole package as a target for enrichment:
message EnrichmentForAllEventsInPackage {
option (enrichment_for) = "com.foo.bar.*";
...
}
OR syntax became available in scope of by
option. It is useful when the same enrichment is required for several events, but the anchor fields have different names:
package com.foo.bar;
// ...
message EventOne {
UserId user_id = 1;
}
message EventTwo {
UserId author_id = 1;
}
// …
message MyEnrichmentForEventOneAndTwo {
option (enrichment_for) = “com.foo.bar.*”
string name = 1 [(by) = “*.user_id | *.author_id”]
}
Postponed delivery of Event
s to the subscribers and dispatchers
It is now possible to define how and when the Event
instances are delivered to their destinations. In particular, the event propagation can be postponed until the system performance allows to continue it.
It is especially handy for the cloud environments such as Google AppEngine. A custom strategy at a certain app node may be implemented to speed up the command propagation with no need to wait until the read-side becomes fully consistent. That allows to handle the incoming requests blazingly fast and thus scale better.
See setDispatcherEventDelivery
and setSubscriberEventDelivery
methods of EventBus.Builder
.
Postponed delivery of Entity
updates to Stand
In addition to the customisable Event
delivery strategy, the same feature is available for Entity
updates delivered to the Stand
instance. Effective on per-BoundedContext
basis, the behaviour can be set via BoundedContext.Builder.setStandUpdateDelivery
. See StandUpdateDelivery
documentation for details.
Valid
option changes
Valid
option has been simplified and is now a bool
instead of Message
.
// Spine 0.7.0:
MyMessage field = 1[(valid).value = true]
// Spine 0.8.0:
MyMessage field = 1[(valid) = true]
EntityRepository
has been renamed to RecordBasedRepository
This is done to reflect the class nature better.
Various improvements and fixes
UnexpectedTypeException
is thrown in response to packing/unpacking the Protobuf values instead of plainRuntimeException
as previously.- Issue #255 fixed: return
Optional
onload()
inRepository
instances. Identifiers
utility class has been refactored, with a object-to-String conversion routines split into the separateStringifiers
utility class.Mismatch
es improvements: handle the case when a command expects some value and wants to change it, but discovers a default ProtobufMessage
value.- The recommended visibility level for the command handler methods was changed to
default
to make theAggregate
API cleaner. - A utility
Tests.hasPrivateUtilityConstructor()
method has been renamed toTests.hasPrivateParameterlessCtor()
to better reflect broader usage.
Dependency updates:
- Gradle 3.3.
Version 0.7.0
New artifact IDs for Core Java modules
Gradle:
repositories {
// Spine releases repository.
maven { url = 'http://maven.teamdev.com/repository/spine' }
}
dependencies {
compile 'org.spine3:spine-server-core:0.7.0'
compile 'org.spine3:spine-client-core:0.7.0'
compile 'org.spine3:spine-users:0.7.0'
compile 'org.spine3:spine-values:0.7.0'
testCompile 'org.spine3:spine-testutil-core:0.7.0'
}
Handling changes and mismatches of values
This version introduces Protobuf types for describing field changes. Examples are StringChange
, TimestampChange
, Int64Change
, etc. For more types, please see the spine/change/change.proto
file.
These types are for being used in commands (to request a change) and events (to describe the change made).
The ValueMismatch
type allows to fire a failure when an entity already had a value of a field, which differs from one described as a previousValue
of corresponding change request. Please also see Java utility classes in the org.spine3.change
package for details.
Wildcard (by)
option was introduced for enrichments
Now a single enrichment message can be used with multiple target events. E.g.
message EnrichmentForSeveralEvents {
string username = 1 [(by) = "*.user_id"];
}
message EventOne {
option (enrichment) = "EnrichmentForSeveralEvents";
int32 user_id = 1;
}
message EventTwo {
option (enrichment) = "EnrichmentForSeveralEvents";
int32 user_id = 1;
}
Behavioural changes and improvements
Automatic ProjectionRepository
catch-up after #initStorage
As we discovered from the real-world cases, the catch-up for the ProjectionRepository
is typically initiated right after the storage factory is set. E.g.
// Spine 0.6.0:
repository.initStorage(storageFactory);
repository.catchUp()
Now the default behaviour is to invoke #catchUp
automatically. So less code, less mistakes. E.g.
// Spine 0.7.0:
repository.initStorage(storageFactory);
Of course, you can always turn off the automatic catch-up passing false
to the ProjectionRepository
constructor.
ProcessManager
changes
ProcessManager
s may now have no commands handled. So it is now possible to build the process on top of events only.
Syntax sugar for type names
Some sugar is now available to get the type name for a Message
. E.g.
// Spine 0.6.0:
String name = TypeUrl.of(msg).getTypeName();
// Spine 0.7.0.
String name = TypeName.of(msg);
System and runtime improvements
Aggregate
state double-checking upon update
Improvements were made to keep the Aggregate state consistent across different threads or even scaled instances of the application. Now Aggregate repository checks that no new events have arrived to the event storage since the start of the command processing.
In case any new events are detected, the processing of this command is repeated.
CommandBus
configuration
CommandBus
can now be configured to avoid spawning new threads during the bus operation. It is helpful for sensitive target runtimes such as Google AppEngine. E.g.
final CommandBus commandBus = CommandBus.newBuilder()
// Allowed by default. Let's disable.
.setThreadSpawnAllowed(false)
.build();
Core singletons made thread-safe
ConverterRegistry
and DefaultStateRegistry
made thread-safe.
Validation
Goes
option introduced
Declarative validation rules for the Protobuf model were extended with (goes)
option. It is now possible to declare a message field that can only be present if another field is present. E.g.
message ScheduledItem {
...
spine.time.LocalDate date = 4;
spine.time.LocalTime time = 5 [(goes).with = "date"];
}
Required
option simplified
(required)
Protobuf option has been simplified and is now a bool
instead of a RequiredOption
message. E.g.
// Spine 0.6.0:
MyMessage field = 1 [(required).value = true];
// Spine 0.7.0.
MyMessage field = 1 [(required) = true];
Structural Changes:
CommandBus
made configurable viaCommandBus.Builder
instead ofCommandBus#newInstance
.
General Improvements
- Date/Time API improvements.
Entity
version is now propagated toStand
upon an update. That allows to store the version properly inStandStorage
.
Dependency Updates
- Guava 20.0
- gRPC 1.0.2
- Protobuf 3.1.0
And the last but not least:
Spine is now integrated with Codacy, exposing the code quality metrics to everyone.
Version 0.6.0
Features & Improvements
Version 0.6.0 introduces a main read-side concept — Stand.
It provides query and subscription API on per-BoundedContext
basis, acting as a facade for data retrieval. Being downstream with the Repositories, Stand also stores the latest Aggregate states, making the state fetch much more efficient.
Application interface has been extended with a couple of read-side oriented gRPC services:
QueryService
— enabling synchronous state retrieval;SubscriptionService
— allowing to subscribe for entity state changes.
Structural Changes:
ClientService
has been renamed toCommandService
; its subscription-related methods were moved to the newly introducedSubscriptionService
.- Model definitions and services related to
Users
domain were moved to a separate users module. - Examples have been extracted as a separate repository.
Dependency Updates
- gRPC 1.0.0.
- Protobuf Gradle Plugin 0.8.0
Version 0.5
Spine Version 0.5 is all about event enrichments.
Features & Improvements
- In this release we introduced Event enrichments - extra information of an event based on its type and content. The mechanism for enriching messages is based on
FieldOptions
of Protobuf message definitions. Just mark an enrichment message withenrichment_for
annotation, its fields - withby
annotation, and register functions which would transform corresponding event (or context) fields to enrichment fields. There can be multiple enrichments for an event, and one enrichment can be used for multiple events. - It is now required to define
type_url_prefix
Protobuf file option in all.proto
files, so that Spine can use your Protobuf types properly.
*As direct imports make dependency analysis much easier, we removed public imports in all.proto
files. - In this version, we excluded slf4j-jdk14 from the published dependencies and now use it only for tests. As a Library, we provide logging facade API, not specific logger binding. Target apps are free to use any binding they need.
- Starting from 0.5, an ability to use custom Connection Pool or Data Source is added in JDBC-based storage implementation. Also, it is possible to customize all the DB tables and queries easily, by overriding them.
- It is possible to configure Spine Gradle Protobuf plugin with a custom target, source, and other paths.
- Few important classes were covered with tests. Overall test coverage increased by 2.5%.
Below let us list the issues that, however, no longer endanger your development process.
- Fixed invalid entries in
known_types.properties
. - Issues with error messages were fixed.
Dependency Updates
As always, we strive for the latest and greatest. With the new release we updated to:
- gRPC 1.0.0-pre2
- Protobuf 3.0.0
v0.4.4
- Extended
MessageOptions
for annotating commands withevents
andfailures
. - Added
ValueMistatch
message type for easier declaration of failures related to data mutation.
Version 0.4.2
Features & Improvements
- Starting from this version we added an ability to import events via special command. This may be useful for:
- importing data via transforming it to aggregate events (as facts in the past);
- integrating with external data sources.
- Following the best practices, now we use
StreamObserver.onError()
instead of returning responses while posting integration events. The same approach is used inCommandBus
. - New custom Protobuf options were introduced:
internal
,SPI
,experimental
andbeta
. They are to be used as the similar Java annotations.
Improvements always come together with some new wisdom and old issues correction. Here is the one for release 0.4.2.
- Rescheduling commands from the storage now happens in parallel on
CommandBus
creation. This way we avoid blocking of the client code execution.
Version 0.4.1
Core Java
Features & Improvements
- Starting from 0.4.1 version Spine supports integration events. An integration event is sent between loosely coupled parts of a system.
BoundedContext
now isIntegrationEventSubscriber
and it is possible to post integration events usingBoundedContext
. - As events may have multiple subscribers that register and “listen” them, in the new release we renamed event handlers to event subscribers.
- Now you can use a
target version
attribute of theCommandContext
. The attribute indicates a version of the entity for which this command is intended. - The
context
parameter in event subscribers and command handlers used to be required in the previous versions of the framework. Now it is optional. - The
ProjectionRepository
catch-up is now supported. So, once switched to the 0.4.1 version of the framework, you will be able to update projections from the event stream obtained from theEventStore
. - If you would like to gather statistics on errors and invalid commands, you can do so with the new release. Invalid and unsupported commands are stored with the error status to make further analysis.
- In this version, we interjected a wrapper that tames the inscrutable and continuously changing cloud application — the
ClientService
— facade for client apps. Create aClientService
using builder with multipleBoundedContext
s and use it to post commands. - This is fairly cool, we made
Storage
s multi-tenant! To make multi-tenancy obvious, useTenantId
instead ofNamespace
for multi-tenancy support. - When you are writing or rewriting an enterprise application, there are going to be parts of the system that facilitate the business but are not core to the business. For example, in most businesses there is a concept of tracking time. That is why we added a new type definition in Protobuf —
Work
. This type represents the amount of work done in minutes. - Similarly to
Work
type, we addedURL
definition in the Protobuf. TheURL
can be defined in raw string form or as a structured parsed record. To support the new type there is aQueryParameters
— the utility class, which helps to perform URL query parameters parsing and String conversion. And one more utility class —Urls
, which simplifies working withURL
. It provides all necessary operations, such as conversion and validation. - To make current time obtaining more abstract and do not use the one from the Protobuf, you can publish the
testutil
project and add Provider interface.
Below let us list the issues that, however, no longer endanger your development process.
- We do care about all your events and do not want to lose them anymore. Therefore, we made Repositories post events that were produced as a result of the command dispatching and
CommandBus
to post events generated by command handlers. - The automatic aggregate snapshots creation did not work as intended in the previous version. Now it is fixed.
- The server is restarted? This is not a reason to stop posting already scheduled commands. Now, if there are any scheduled commands found on the
CommandBus
startup, they are re-scheduled again. - We thought you would enjoy the example of the
Aggregate
with all new features described. So, we fixed it and improved a bit.
Dependency Updates
As always, we strive for the latest and greatest. With the new release we updated to:
- gRPC 0.14.0
- Protobuf 3.0.0 beta-3.
- Gradle 2.13.