https://github.com/opencredo/concursus
https://github.com/opencredo/concursus
blog-article
Last synced: 19 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/opencredo/concursus
- Owner: opencredo
- License: mit
- Created: 2016-02-25T12:23:28.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2023-12-05T22:00:51.000Z (almost 2 years ago)
- Last Synced: 2024-06-26T09:41:53.458Z (over 1 year ago)
- Topics: blog-article
- Language: Java
- Homepage: https://opencredo.com/blogs/concursus-programming-model/
- Size: 3.11 MB
- Stars: 93
- Watchers: 17
- Forks: 14
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-java - Concursus
- awesome_cqrs - GitHub - opencredo/concursus: Concursus is a Java 8 framework for building applications that use CQRS and event sourcing patterns, with a Cassandra event log implementation.
README
# Concursus
[](http://search.maven.org/#search%7Cga%7C1%7Cconcursus)
[](https://travis-ci.org/opencredo/concursus)
_Concursus_ is a Java 8 framework for building applications that use CQRS and event sourcing patterns, with a Cassandra event log implementation.
See the [wiki](https://github.com/opencredo/concursus/wiki) for further documentation, or browse the [javadocs](http://opencredo.github.io/concursus/apidocs).
## Getting Started
Create a project with dependencies on `concursus-mapping`, `concursus-domain-json` and `jackson-datatype-jsr310`:
```xml
com.opencredo
concursus-mapping
com.opencredo
concursus-domain-json
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
```
The first thing we might want to do is generate some events. To begin with, we need an interface class that defines the events we want to create:
```java
@HandlesEventsFor("person")
public interface Events {
@Initial
void created(StreamTimestamp ts, String personId, String name, LocalDate dateOfBirth);
void changedName(StreamTimestamp ts, String personId, String newName);
void movedToAddress(StreamTimestamp ts, String personId, String addressId);
@Terminal
void deleted(StreamTimestamp ts, String personId);
}
```
Each method in this interface defines an event which can occur to a person. We generate an event by calling one of these methods, which results in an event being sent to an `EventOutChannel`. Let's create a channel that simply prints the event to the console, and then create a proxy implementation of `PersonEvents` that sends events to this channel:
```java
// Create an EventOutChannel that simply prints events to the command line
EventOutChannel outChannel = System.out::println;
// Create a proxy that sends events to the outChannel.
PersonEvents proxy = EventEmittingProxy.proxying(outChannel, PersonEvents.class);
// Send an event via the proxy.
proxy.created(StreamTimestamp.now(), "id1", "Arthur Putey", LocalDate.parse("1968-05-28"));
```
This will output a String like the following:
```
person:id1 created_0
at 2016-03-31T10:31:17.981Z/
with person/created_0{dateOfBirth=1968-05-28, name=Arthur Putey}
```
This means that an event of type `created_0` occurred to the object `person:b2fb2f38-0473-4359-b62b-fad149caf2d5` at `2016-03-31T10:31:17.981Z`, and this event had two parameters associated with it, `name` and `dateOfBirth`.
We can have the event encoded as JSON if we use an `EventOutChannel` that performs the encoding:
```java
// Create an EventOutChannel that formats events as JSON and sends them to a command line printer.
EventInChannel print = System.out::println;
ObjectMapper objectMapper = new ObjectMapper()
.findAndRegisterModules()
.configure(SerializationFeature.INDENT_OUTPUT, true);
EventOutChannel outChannel = JsonEventOutChannel.using(objectMapper, print);
// Create a proxy that sends events to the outChannel.
PersonEvents proxy = EventEmittingProxy.proxying(outChannel, PersonEvents.class);
// Send an event via the proxy.
proxy.created(StreamTimestamp.now(), "id1, "Arthur Putey", LocalDate.parse("1968-05-28"));
```
This will output JSON like the following:
```json
{
"aggregateType" : "person",
"aggregateId" : "id1",
"name" : "created",
"version" : "0",
"eventTimestamp" : 1459420919667,
"streamId" : "",
"processingId" : "",
"characteristics" : 1,
"parameters" : {
"dateOfBirth" : [ 1968, 5, 28 ],
"name" : "Arthur Putey"
}
}
```
Instead of simply printing things to the console, let's start storing events. We can use an `InMemoryEventStore` to begin with:
```java
// Create an InMemoryEventStore, and a proxy that sends events to it.
InMemoryEventStore eventStore = InMemoryEventStore.empty();
PersonEvents proxy = EventEmittingProxy.proxying(eventStore.toEventOutChannel(), PersonEvents.class);
// Send an event via the proxy.
final String personId = String.randomString();
proxy.created(StreamTimestamp.now(), personId, "Arthur Putey", LocalDate.parse("1968-05-28"));
// Create an EventTypeMatcher based on the Events interface, and use it to map events back out of the store
EventTypeMatcher typeMatcher = EmitterInterfaceInfo.forInterface(PersonEvents.class).getEventTypeMatcher();
// Retrieve the stored events for the aggregate with id=person/personId, and print them to the console.
EventSource.retrievingWith(eventStore)
.getEvents(typeMatcher, AggregateId.of("person", personId))
.forEach(System.out::println);
```
Once we have stored events, we can replay them to event handlers, mapping them back into method calls on the `PersonEvents` interface:
```
// Create a mock handler for person events.
PersonEvents handler = mock(PersonEvents.class);
// Create an InMemoryEventStore, and a proxy that sends events to it.
InMemoryEventStore eventStore = InMemoryEventStore.empty();
PersonEvents proxy = EventEmittingProxy.proxying(eventStore.toEventOutChannel(), PersonEvents.class);
// Send an event via the proxy.
String personId = "id1;
proxy.created(StreamTimestamp.now(), personId, "Arthur Putey", LocalDate.parse("1968-05-28"));
// Replay the stored events for the person with id=person/personId to the handler instance.
DispatchingEventSource.dispatching(EventSource.retrievingWith(eventStore), PersonEvents.class)
.replaying(personId)
.replayAll(handler);
// Verify that the handler received the event.
verify(handler).created(any(StreamTimestamp.class), any(String.class), eq("Arthur Putey"), eq(LocalDate.parse("1968-05-28")));
```
Check out the [Examples](https://github.com/opencredo/concursus/tree/master/concursus-examples/src/test/java/com/opencredo/concursus/examples) for more detailed examples, including command processing and state-building.
## Using Cassandra and Redis
Eventually you will want to store events more permanently. Cassandra and Redis event store implementations are provided in `concursus-cassandra` and `concursus-redis` respectively. You will need to create a suitable keyspace and tables in Cassandra before you can use it:
```cql
CREATE KEYSPACE IF NOT EXISTS concursus
WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 2 };
CREATE TABLE IF NOT EXISTS concursus.Event (
aggregateType text,
aggregateId text,
eventTimestamp timestamp,
streamId text,
processingId timeuuid,
name text,
version text,
parameters map,
characteristics int,
PRIMARY KEY((aggregateType, aggregateId), eventTimestamp, streamId)
) WITH CLUSTERING ORDER BY (eventTimestamp DESC);
CREATE TABLE IF NOT EXISTS concursus.Catalogue (
aggregateType text,
bucket int,
aggregateId text,
PRIMARY KEY ((aggregateType, bucket), aggregateId)
) WITH CLUSTERING ORDER BY (aggregateId DESC);
```