Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
Andreas Simon committed Mar 7, 2013
0 parents commit 5151904
Show file tree
Hide file tree
Showing 24 changed files with 803 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gradle
build
out/
tmp/
2 changes: 2 additions & 0 deletions bash_aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alias im='gradle ideaModule'
alias install='gradle clean install'
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
apply plugin: 'idea'
37 changes: 37 additions & 0 deletions core-api/build.gradle
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
}
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 core-api/src/main/groovy/de/oneos/eventsourcing/Event.groovy
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()
}
}
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)

}
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)
}
}
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

}
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)
}
}
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()
}

}
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
}

}
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 core-api/src/main/groovy/de/oneos/eventstore/EventStore.groovy
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)

}
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 core-api/src/main/groovy/de/oneos/eventstore/UnitOfWork.groovy
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) }
}

}
Loading

0 comments on commit 5151904

Please sign in to comment.