-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Andreas Simon
committed
Mar 7, 2013
0 parents
commit 5151904
Showing
24 changed files
with
803 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.gradle | ||
build | ||
out/ | ||
tmp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
alias im='gradle ideaModule' | ||
alias install='gradle clean install' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
apply plugin: 'idea' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
apply plugin: 'idea' | ||
apply plugin: 'groovy' | ||
apply plugin: 'maven' | ||
|
||
group = 'de.oneos.cqrs' | ||
version = '0.0.1-SNAPSHOT' | ||
|
||
defaultTasks 'clean', 'install' | ||
|
||
install { | ||
repositories.mavenInstaller { | ||
pom.artifactId = 'core-api' | ||
} | ||
} | ||
install.dependsOn test | ||
|
||
task sourceJar(type: Jar) { | ||
from sourceSets.main.groovy | ||
classifier = 'sources' | ||
} | ||
|
||
artifacts { | ||
archives sourceJar | ||
} | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
groovy 'org.codehaus.groovy:groovy-all:2.0.+' | ||
} | ||
|
||
idea.module { | ||
downloadJavadoc = true | ||
downloadSources = true | ||
} |
8 changes: 8 additions & 0 deletions
8
core-api/src/main/groovy/de/oneos/eventsourcing/AggregateFactory.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package de.oneos.eventsourcing | ||
|
||
|
||
interface AggregateFactory { | ||
|
||
public <A> A newInstance(Map aggregateProperties, Class<A> rawAggregateClass) | ||
|
||
} |
27 changes: 27 additions & 0 deletions
27
core-api/src/main/groovy/de/oneos/eventsourcing/Event.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package de.oneos.eventsourcing | ||
|
||
abstract class Event<T> { | ||
protected static List<String> UNSERIALIZED_PROPERTIES = ['applicationName', 'boundedContextName', 'aggregateClass', 'aggregateName', 'aggregateClassName', 'aggregateId', 'class', 'name'] | ||
|
||
abstract void applyTo(T t) | ||
|
||
String getName() { | ||
this.class.name.split('\\.')[-1].replaceAll('_', ' ') | ||
} | ||
|
||
Map<String, String> attributes() { | ||
return properties.findAll({ key, value -> | ||
! UNSERIALIZED_PROPERTIES.contains(key) | ||
}).collectEntries { k, v -> [(k): v.toString()] } | ||
} | ||
|
||
@Override | ||
String toString() { | ||
"${name} ${attributes()}" | ||
} | ||
|
||
@Override | ||
boolean equals(Object that) { | ||
this.toString() == that.toString() | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
core-api/src/main/groovy/de/oneos/eventsourcing/EventAggregator.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package de.oneos.eventsourcing | ||
|
||
import de.oneos.eventsourcing.* | ||
|
||
|
||
public interface EventAggregator { | ||
|
||
void publishEvent(String applicationName, String boundedContextName, String aggregateName, UUID aggregateId, Event event) | ||
|
||
} |
67 changes: 67 additions & 0 deletions
67
core-api/src/main/groovy/de/oneos/eventsourcing/EventEnvelope.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package de.oneos.eventsourcing | ||
|
||
class EventEnvelope<AggregateType> { | ||
|
||
static final String TIMESTAMP_FORMAT = 'yyyy-MM-dd HH:mm:ss.SSS' | ||
final Date timestamp | ||
final String applicationName | ||
final String boundedContextName | ||
final String aggregateName | ||
final UUID aggregateId | ||
final Integer sequenceNumber | ||
final Event<AggregateType> event | ||
|
||
EventEnvelope(String applicationName, String boundedContextName, String aggregateName, UUID aggregateId, Event<AggregateType> event, int sequenceNumber = 0, Date timestamp = new Date()) { | ||
this.applicationName = applicationName | ||
this.boundedContextName = boundedContextName | ||
this.aggregateName = aggregateName | ||
this.aggregateId = aggregateId | ||
this.sequenceNumber = sequenceNumber | ||
this.event = event | ||
this.timestamp = timestamp | ||
} | ||
|
||
String getEventName() { | ||
return event.name | ||
} | ||
|
||
String getSerializedEvent() { | ||
return GenericEventSerializer.toJSON(event) | ||
} | ||
|
||
String getSerializedTimestamp() { | ||
timestamp.format(TIMESTAMP_FORMAT) | ||
} | ||
|
||
@Override | ||
String toString() { | ||
"EventEnvelope[$applicationName.$boundedContextName.$aggregateName{$aggregateId}#$sequenceNumber @${serializedTimestamp} :: <$event>]".toString() | ||
} | ||
|
||
@Override | ||
boolean equals(that) { | ||
this.applicationName == that.applicationName && | ||
this.boundedContextName == that.boundedContextName && | ||
this.aggregateName == that.aggregateName && | ||
this.aggregateId == that.aggregateId && | ||
this.event == that.event && | ||
this.sequenceNumber == that.sequenceNumber | ||
} | ||
|
||
String toJSON() { | ||
"""{\ | ||
"applicationName":"$applicationName",\ | ||
"boundedContextName":"$boundedContextName",\ | ||
"aggregateName":"$aggregateName",\ | ||
"aggregateId":"$aggregateId",\ | ||
"eventName":"${event.name}",\ | ||
"attributes":${GenericEventSerializer.toJSON(event)},\ | ||
"timestamp":"$serializedTimestamp",\ | ||
"":""\ | ||
}""" | ||
} | ||
|
||
void applyEventTo(aggregate) { | ||
event.applyTo(aggregate) | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
core-api/src/main/groovy/de/oneos/eventsourcing/EventPublisher.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package de.oneos.eventsourcing | ||
|
||
public interface EventPublisher { | ||
|
||
void publish(EventEnvelope eventEnvelope) throws EventPublishingException | ||
|
||
} |
8 changes: 8 additions & 0 deletions
8
core-api/src/main/groovy/de/oneos/eventsourcing/EventPublishingException.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package de.oneos.eventsourcing | ||
|
||
|
||
class EventPublishingException extends RuntimeException { | ||
EventPublishingException(String message) { | ||
super(message) | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
core-api/src/main/groovy/de/oneos/eventsourcing/GenericEventSerializer.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package de.oneos.eventsourcing | ||
|
||
import de.oneos.eventsourcing.Event | ||
import groovy.json.JsonBuilder | ||
|
||
class GenericEventSerializer { | ||
|
||
static String toJSON(Event<?> event) { | ||
new JsonBuilder(event.attributes()).toString() | ||
} | ||
|
||
} |
45 changes: 45 additions & 0 deletions
45
core-api/src/main/groovy/de/oneos/eventsourcing/MixinAggregateFactory.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package de.oneos.eventsourcing | ||
|
||
|
||
import static java.lang.System.identityHashCode | ||
|
||
|
||
class MixinAggregateFactory implements AggregateFactory { | ||
Map<Integer, EventAggregator> eventAggregators = Collections.synchronizedMap([:]) | ||
Map<Integer, UUID> aggregateIds = Collections.synchronizedMap([:]) | ||
|
||
public <A> A newInstance(Map aggregateProperties, Class<A> rawAggregateClass) { | ||
def instance = rawAggregateClass.newInstance() | ||
instance.metaClass = defineExpandoMetaClass(rawAggregateClass) { | ||
setAggregateId = { thisAggregateId -> | ||
aggregateIds[identityHashCode(delegate)] = thisAggregateId | ||
} | ||
|
||
setEventAggregator = { thisEventAggregator -> | ||
eventAggregators[identityHashCode(delegate)] = thisEventAggregator | ||
} | ||
|
||
emit = { Event event -> | ||
eventAggregators[identityHashCode(delegate)].publishEvent( | ||
rawAggregateClass.applicationName, | ||
rawAggregateClass.boundedContextName, | ||
rawAggregateClass.aggregateName, | ||
aggregateIds[identityHashCode(delegate)], | ||
event | ||
) | ||
event.applyTo(delegate) | ||
} | ||
} | ||
aggregateProperties.each { name, value -> | ||
instance[name] = value | ||
} | ||
return instance | ||
} | ||
|
||
static defineExpandoMetaClass(Class theClass, Closure definition) { | ||
ExpandoMetaClass expandoAggregateClass = new ExpandoMetaClass(theClass) | ||
expandoAggregateClass.define(definition).initialize() | ||
expandoAggregateClass | ||
} | ||
|
||
} |
14 changes: 14 additions & 0 deletions
14
core-api/src/main/groovy/de/oneos/eventstore/EventCollisionOccurred.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package de.oneos.eventstore | ||
|
||
import de.oneos.eventsourcing.EventEnvelope | ||
|
||
class EventCollisionOccurred extends RuntimeException { | ||
|
||
EventCollisionOccurred(EventEnvelope envelope, Throwable cause) { | ||
super( | ||
"Event ['${envelope.applicationName}'.'${envelope.boundedContextName}'.'${envelope.aggregateName}'[${envelope.aggregateId}] #${envelope.sequenceNumber}] already exists", | ||
cause | ||
) | ||
} | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
core-api/src/main/groovy/de/oneos/eventstore/EventStore.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package de.oneos.eventstore | ||
|
||
import de.oneos.eventsourcing.* | ||
|
||
|
||
interface EventStore { | ||
|
||
void setPublishers(List<EventPublisher> eventPublishers) | ||
|
||
void inUnitOfWork(Closure closure) | ||
|
||
UnitOfWork createUnitOfWork() | ||
|
||
void commit(UnitOfWork unitOfWork) throws IllegalArgumentException, EventCollisionOccurred | ||
|
||
List<EventEnvelope> loadEventEnvelopes(String applicationName, String boundedContextName, String aggregateName, UUID aggregateId, Closure<Event> eventFactory) | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
core-api/src/main/groovy/de/oneos/eventstore/SequenceNumberGenerator.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package de.oneos.eventstore | ||
|
||
|
||
// TODO Write unit tests | ||
class SequenceNumberGenerator<K> { | ||
|
||
Map<K, Integer> sequences = [:] | ||
|
||
int getAt(K key) { | ||
if(!sequences.containsKey(key)) { | ||
sequences[key] = 0 | ||
} | ||
return sequences[key]++ | ||
} | ||
|
||
void putAt(K key, int value) { | ||
sequences[key] = value | ||
} | ||
|
||
static Closure<SequenceNumberGenerator> newInstance = { | ||
new SequenceNumberGenerator() | ||
} | ||
|
||
} |
91 changes: 91 additions & 0 deletions
91
core-api/src/main/groovy/de/oneos/eventstore/UnitOfWork.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package de.oneos.eventstore | ||
|
||
import static java.lang.Math.* | ||
|
||
import de.oneos.eventsourcing.* | ||
|
||
|
||
class UnitOfWork implements EventAggregator { | ||
|
||
protected EventStore eventStore | ||
protected AggregateFactory aggregateFactory | ||
protected List<EventEnvelope> publishedEventEnvelopes = [] | ||
|
||
Map<String, Map<String, Map<String, Map<UUID, Integer>>>> nextSequenceNumbers = | ||
emptyMapWithDefault( | ||
emptyMapWithDefault( | ||
emptyMapWithDefault( | ||
SequenceNumberGenerator.newInstance)))() | ||
|
||
|
||
UnitOfWork(EventStore eventStore) { | ||
this.eventStore = eventStore | ||
this.aggregateFactory = new MixinAggregateFactory() | ||
} | ||
|
||
|
||
static Closure<Map> emptyMapWithDefault(Closure defaultFactory) { | ||
return { | ||
MapWithDefault.newInstance([:], defaultFactory) | ||
} | ||
} | ||
|
||
def get(Class aggregateClass, UUID aggregateId, Closure eventFactory) { | ||
def aggregate = newAggregateInstance(aggregateClass, aggregateId) | ||
|
||
List<EventEnvelope> eventEnvelopes = loadEventEnvelopes(aggregateClass, aggregateId, eventFactory) | ||
|
||
updateSequenceNumbers(aggregateClass, aggregateId, eventEnvelopes) | ||
eventEnvelopes.each { envelope -> | ||
envelope.applyEventTo(aggregate) | ||
} | ||
return aggregate | ||
} | ||
|
||
protected newAggregateInstance(Class aggregateClass, UUID aggregateId) { | ||
aggregateFactory.newInstance(aggregateClass, aggregateId: aggregateId, eventAggregator: this) | ||
} | ||
|
||
protected updateSequenceNumbers(Class aggregateClass, UUID aggregateId, List<EventEnvelope> eventEnvelopes) { | ||
nextSequenceNumbers(aggregateClass)[aggregateId] = maximumSequenceNumber(eventEnvelopes) + 1 | ||
} | ||
|
||
protected maximumSequenceNumber(List<EventEnvelope> eventEnvelopes) { | ||
eventEnvelopes.inject(0) { int maximumSequenceNumber, envelope -> max(maximumSequenceNumber, envelope.sequenceNumber) } | ||
} | ||
|
||
protected nextSequenceNumbers(Class aggregateClass) { | ||
nextSequenceNumbers[aggregateClass.applicationName][aggregateClass.boundedContextName][aggregateClass.aggregateName] | ||
} | ||
|
||
protected loadEventEnvelopes(Class aggregateClass, UUID aggregateId, Closure eventFactory) { | ||
eventStore.loadEventEnvelopes( | ||
aggregateClass.applicationName, | ||
aggregateClass.boundedContextName, | ||
aggregateClass.aggregateName, | ||
aggregateId, | ||
eventFactory | ||
) | ||
} | ||
|
||
@Override | ||
void publishEvent(String applicationName, String boundedContextName, String aggregateName, UUID aggregateId, Event event) { | ||
publishedEventEnvelopes << new EventEnvelope( | ||
applicationName, | ||
boundedContextName, | ||
aggregateName, | ||
aggregateId, | ||
event, | ||
nextSequenceNumber(applicationName, boundedContextName, aggregateName, aggregateId) | ||
) | ||
} | ||
|
||
protected int nextSequenceNumber(applicationName, boundedContextName, aggregateName, aggregateId) { | ||
nextSequenceNumbers[applicationName][boundedContextName][aggregateName][aggregateId] | ||
} | ||
|
||
void eachEventEnvelope(Closure callback) { | ||
publishedEventEnvelopes.each { callback(it) } | ||
} | ||
|
||
} |
Oops, something went wrong.