{"id":28680722,"url":"https://github.com/opencredo/concursus","last_synced_at":"2025-10-30T19:01:35.060Z","repository":{"id":37270797,"uuid":"52522585","full_name":"opencredo/concursus","owner":"opencredo","description":null,"archived":false,"fork":false,"pushed_at":"2023-12-05T22:00:51.000Z","size":3259,"stargazers_count":93,"open_issues_count":6,"forks_count":14,"subscribers_count":17,"default_branch":"master","last_synced_at":"2024-06-26T09:41:53.458Z","etag":null,"topics":["blog-article"],"latest_commit_sha":null,"homepage":"https://opencredo.com/blogs/concursus-programming-model/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/opencredo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-25T12:23:28.000Z","updated_at":"2024-05-12T16:05:07.000Z","dependencies_parsed_at":"2022-07-15T21:16:54.591Z","dependency_job_id":null,"html_url":"https://github.com/opencredo/concursus","commit_stats":null,"previous_names":["opencredo/concourse"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/opencredo/concursus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencredo%2Fconcursus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencredo%2Fconcursus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencredo%2Fconcursus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencredo%2Fconcursus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/opencredo","download_url":"https://codeload.github.com/opencredo/concursus/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opencredo%2Fconcursus/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259747197,"owners_count":22905308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blog-article"],"created_at":"2025-06-14T02:01:11.721Z","updated_at":"2025-10-30T19:01:34.954Z","avatar_url":"https://github.com/opencredo.png","language":"Java","funding_links":[],"categories":["开发框架","JVM"],"sub_categories":["Java"],"readme":"# Concursus\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.opencredo/concursus.svg)](http://search.maven.org/#search%7Cga%7C1%7Cconcursus)\n[![Build Status](https://travis-ci.org/opencredo/concursus.svg?branch=master)](https://travis-ci.org/opencredo/concursus)\n\n_Concursus_ is a Java 8 framework for building applications that use CQRS and event sourcing patterns, with a Cassandra event log implementation.\n\nSee the [wiki](https://github.com/opencredo/concursus/wiki) for further documentation, or browse the [javadocs](http://opencredo.github.io/concursus/apidocs).\n\n## Getting Started\n\nCreate a project with dependencies on `concursus-mapping`, `concursus-domain-json` and `jackson-datatype-jsr310`:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.opencredo\u003c/groupId\u003e\n    \u003cartifactId\u003econcursus-mapping\u003c/artifactId\u003e\n\u003c/dependency\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.opencredo\u003c/groupId\u003e\n    \u003cartifactId\u003econcursus-domain-json\u003c/artifactId\u003e\n\u003c/dependency\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.fasterxml.jackson.datatype\u003c/groupId\u003e\n    \u003cartifactId\u003ejackson-datatype-jsr310\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\nThe 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:\n\n```java\n@HandlesEventsFor(\"person\")\npublic interface Events {\n    @Initial\n    void created(StreamTimestamp ts, String personId, String name, LocalDate dateOfBirth);\n    void changedName(StreamTimestamp ts, String personId, String newName);\n    void movedToAddress(StreamTimestamp ts, String personId, String addressId);\n    @Terminal\n    void deleted(StreamTimestamp ts, String personId);\n}\n```\n\nEach 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:\n\n```java\n// Create an EventOutChannel that simply prints events to the command line\nEventOutChannel outChannel = System.out::println;\n\n// Create a proxy that sends events to the outChannel.\nPersonEvents proxy = EventEmittingProxy.proxying(outChannel, PersonEvents.class);\n\n// Send an event via the proxy.\nproxy.created(StreamTimestamp.now(), \"id1\", \"Arthur Putey\", LocalDate.parse(\"1968-05-28\"));\n```\n\nThis will output a String like the following:\n\n```\nperson:id1 created_0\nat 2016-03-31T10:31:17.981Z/\nwith person/created_0{dateOfBirth=1968-05-28, name=Arthur Putey}\n```\n\nThis 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`.\n\nWe can have the event encoded as JSON if we use an `EventOutChannel` that performs the encoding:\n\n```java\n// Create an EventOutChannel that formats events as JSON and sends them to a command line printer.\nEventInChannel\u003cString\u003e print = System.out::println;\nObjectMapper objectMapper = new ObjectMapper()\n        .findAndRegisterModules()\n        .configure(SerializationFeature.INDENT_OUTPUT, true);\nEventOutChannel outChannel = JsonEventOutChannel.using(objectMapper, print);\n\n// Create a proxy that sends events to the outChannel.\nPersonEvents proxy = EventEmittingProxy.proxying(outChannel, PersonEvents.class);\n\n// Send an event via the proxy.\nproxy.created(StreamTimestamp.now(), \"id1, \"Arthur Putey\", LocalDate.parse(\"1968-05-28\"));\n```\n\nThis will output JSON like the following:\n\n```json\n{\n  \"aggregateType\" : \"person\",\n  \"aggregateId\" : \"id1\",\n  \"name\" : \"created\",\n  \"version\" : \"0\",\n  \"eventTimestamp\" : 1459420919667,\n  \"streamId\" : \"\",\n  \"processingId\" : \"\",\n  \"characteristics\" : 1,\n  \"parameters\" : {\n    \"dateOfBirth\" : [ 1968, 5, 28 ],\n    \"name\" : \"Arthur Putey\"\n  }\n}\n```\n\nInstead of simply printing things to the console, let's start storing events. We can use an `InMemoryEventStore` to begin with:\n\n```java\n// Create an InMemoryEventStore, and a proxy that sends events to it.\nInMemoryEventStore eventStore = InMemoryEventStore.empty();\nPersonEvents proxy = EventEmittingProxy.proxying(eventStore.toEventOutChannel(), PersonEvents.class);\n\n// Send an event via the proxy.\nfinal String personId = String.randomString();\nproxy.created(StreamTimestamp.now(), personId, \"Arthur Putey\", LocalDate.parse(\"1968-05-28\"));\n\n// Create an EventTypeMatcher based on the Events interface, and use it to map events back out of the store\nEventTypeMatcher typeMatcher = EmitterInterfaceInfo.forInterface(PersonEvents.class).getEventTypeMatcher();\n// Retrieve the stored events for the aggregate with id=person/personId, and print them to the console.\nEventSource.retrievingWith(eventStore)\n        .getEvents(typeMatcher, AggregateId.of(\"person\", personId))\n        .forEach(System.out::println);\n```\n\nOnce we have stored events, we can replay them to event handlers, mapping them back into method calls on the `PersonEvents` interface:\n\n```\n// Create a mock handler for person events.\nPersonEvents handler = mock(PersonEvents.class);\n\n// Create an InMemoryEventStore, and a proxy that sends events to it.\nInMemoryEventStore eventStore = InMemoryEventStore.empty();\nPersonEvents proxy = EventEmittingProxy.proxying(eventStore.toEventOutChannel(), PersonEvents.class);\n\n// Send an event via the proxy.\nString personId = \"id1;\nproxy.created(StreamTimestamp.now(), personId, \"Arthur Putey\", LocalDate.parse(\"1968-05-28\"));\n\n// Replay the stored events for the person with id=person/personId to the handler instance.\nDispatchingEventSource.dispatching(EventSource.retrievingWith(eventStore), PersonEvents.class)\n        .replaying(personId)\n        .replayAll(handler);\n\n// Verify that the handler received the event.\nverify(handler).created(any(StreamTimestamp.class), any(String.class), eq(\"Arthur Putey\"), eq(LocalDate.parse(\"1968-05-28\")));\n```\n\nCheck 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.\n\n## Using Cassandra and Redis\n\nEventually 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:\n\n```cql\nCREATE KEYSPACE IF NOT EXISTS concursus\n  WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 2 };\n\nCREATE TABLE IF NOT EXISTS concursus.Event (\n   aggregateType text,\n   aggregateId text,\n   eventTimestamp timestamp,\n   streamId text,\n   processingId timeuuid,\n   name text,\n   version text,\n   parameters map\u003ctext, text\u003e,\n   characteristics int,\n   PRIMARY KEY((aggregateType, aggregateId), eventTimestamp, streamId)\n) WITH CLUSTERING ORDER BY (eventTimestamp DESC);\n\nCREATE TABLE IF NOT EXISTS concursus.Catalogue (\n    aggregateType text,\n    bucket int,\n    aggregateId text,\n    PRIMARY KEY ((aggregateType, bucket), aggregateId)\n) WITH CLUSTERING ORDER BY (aggregateId DESC);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopencredo%2Fconcursus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopencredo%2Fconcursus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopencredo%2Fconcursus/lists"}