From e18c7a47c8fab5340b5adbac9fe410b0e8694abd Mon Sep 17 00:00:00 2001 From: Rahul Somasunderam Date: Tue, 27 Jun 2017 21:41:03 -0700 Subject: [PATCH] Document usage --- .travis.yml | 14 ++- CHANGELOG.adoc | 1 + README.adoc | 15 +++ bds_init.gradle | 14 +++ build.gradle | 4 +- buildSrc/build.gradle | 2 +- .../grooves/grails/mongo/Patient.groovy | 9 +- .../grails/mongo/PatientAccount.groovy | 14 ++- .../grooves/grails/mongo/PatientEvent.groovy | 37 ++++-- .../grails/mongo/PatientAccountQuery.groovy | 33 +++--- .../java/grooves/example/javaee/Database.java | 9 ++ .../example/javaee/domain/Patient.java | 9 +- .../example/javaee/domain/PatientAccount.java | 34 ++++-- .../example/javaee/domain/PatientCreated.java | 10 +- .../example/javaee/domain/PatientEvent.java | 23 ++-- .../javaee/domain/PatientEventReverted.java | 11 +- .../javaee/queries/CustomQuerySupport.java | 45 ++++--- .../javaee/queries/PatientAccountQuery.java | 20 +++- .../grooves/boot/jpa/domain/Patient.groovy | 12 +- .../boot/jpa/domain/PatientAccount.groovy | 14 ++- .../boot/jpa/domain/PatientEvent.groovy | 38 +++--- .../jpa/queries/PatientAccountQuery.groovy | 89 ++++++++------ examples/springboot/kotlin/build.gradle | 2 +- .../grooves/boot/kotlin/domain/Patient.kt | 7 +- .../boot/kotlin/domain/PatientAccount.kt | 20 ++-- .../boot/kotlin/domain/PatientEvent.kt | 42 +++++-- .../kotlin/queries/PatientAccountQuery.kt | 46 ++++--- grooves-api/build.gradle | 30 +---- grooves-api/src/docs/asciidoc/index.adoc | 28 ----- grooves-docs/build.gradle | 45 +++++++ .../src/docs/asciidoc/examples/aggregate.adoc | 65 ++++++++++ .../src/docs/asciidoc/examples/baseEvent.adoc | 58 +++++++++ .../docs/asciidoc/examples/creationEvent.adoc | 50 ++++++++ .../docs/asciidoc/examples/dependencies.adoc | 79 ++++++++++++ .../src/docs/asciidoc/examples/events.adoc | 15 +++ .../src/docs/asciidoc/examples/queries.adoc | 112 ++++++++++++++++++ .../docs/asciidoc/examples/revertEvent.adoc | 44 +++++++ .../src/docs/asciidoc/examples/snapshot.adoc | 59 +++++++++ .../src/docs/asciidoc/hurdles.adoc | 2 +- grooves-docs/src/docs/asciidoc/index.adoc | 76 ++++++++++++ .../src/docs/asciidoc/intro.adoc | 1 - grooves-docs/src/docs/asciidoc/support.adoc | 7 ++ .../grooves/grails/GormQuerySupport.java | 28 +++-- grooves-types/build.gradle | 10 +- settings.gradle | 2 +- 45 files changed, 1019 insertions(+), 266 deletions(-) create mode 100644 bds_init.gradle delete mode 100644 grooves-api/src/docs/asciidoc/index.adoc create mode 100644 grooves-docs/build.gradle create mode 100644 grooves-docs/src/docs/asciidoc/examples/aggregate.adoc create mode 100644 grooves-docs/src/docs/asciidoc/examples/baseEvent.adoc create mode 100644 grooves-docs/src/docs/asciidoc/examples/creationEvent.adoc create mode 100644 grooves-docs/src/docs/asciidoc/examples/dependencies.adoc create mode 100644 grooves-docs/src/docs/asciidoc/examples/events.adoc create mode 100644 grooves-docs/src/docs/asciidoc/examples/queries.adoc create mode 100644 grooves-docs/src/docs/asciidoc/examples/revertEvent.adoc create mode 100644 grooves-docs/src/docs/asciidoc/examples/snapshot.adoc rename {grooves-api => grooves-docs}/src/docs/asciidoc/hurdles.adoc (98%) create mode 100644 grooves-docs/src/docs/asciidoc/index.adoc rename {grooves-api => grooves-docs}/src/docs/asciidoc/intro.adoc (99%) create mode 100644 grooves-docs/src/docs/asciidoc/support.adoc diff --git a/.travis.yml b/.travis.yml index 225a5138c..71da52922 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,16 @@ language: java jdk: - oraclejdk8 install: "./gradlew assemble --scan --build-cache --configure-on-demand" -script: "./travis.sh" -after_success: bash <(curl -s https://codecov.io/bash) +script: "" +after_success: + - bash <(curl -s https://codecov.io/bash) + - ./gradlew --init-script bds_init.gradle :grooves-types:buildBom :grooves-api:buildBom :grooves-groovy:buildBom :grooves-gorm:buildBom -DbdsPluginVersion=5.0.0 --scan --build-cache --configure-on-demand + - curl -s https://copilot.blackducksoftware.com/bash/travis > blackduck.sh + - chmod u+x blackduck.sh + - ./blackduck.sh ./build/blackduck/grooves-api_bdio.jsonld + - ./blackduck.sh ./build/blackduck/grooves-types_bdio.jsonld + - ./blackduck.sh ./build/blackduck/grooves-groovy_bdio.jsonld + - ./blackduck.sh ./build/blackduck/grooves-gorm_bdio.jsonld before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ @@ -20,5 +28,3 @@ env: - secure: SoiURI/HxcdYh4rHTixuQKhzIHk360joevJkAwK9k9FZeYE9rTKRvkSbYcE7kUyNv4Z/NyqfC9ofKwgXqqOR5kY31smTAsO2A3tHauE7VhQtRd/VDTifVi95X0UXkxsPiRMrpCv4iW5kflYkwBT/k7oU2RIGlDF1cxsF87gtmCTIZ99pMZseEYPllIugiWoEyOiwndEavsVv0r080a0HcvLU48ZOp7HWjbM8Ba6ZGAbu5LPnXbaSuRSQjuI5L9g1ZkNVrYQ1Ds351l+Qz9zO0O/LxB0TXBjz3XPldmpnatq5KoJuezHIC1wy5QXKTcHPK/q6eDVfjwdaSF4z4EmLnS/WAS8QoeQAyTTLrErKUgSJ6ZvbTVgXuWV1zril4lVSXuH0DbqV55+bgjUtyRQzQtM8NTL5YXzs3+ABGTA701ec0yUojY0CxxZSH1VhSyBYgskmLUcfmDIjwqmdGT8b6y8V3uERnsgS34dmEj7lA9HtDofmrvsA2oQvOGTINdzW8IephTinNkIHE2SSmLn6JuIp6HsZm6KQIXgmJb9UjpF3HKKFWZe72Q9x4fvx3HpWhydzCZjQx2fh3/wWPnb71/i/NcrY4qpAfo8Scp38XWSHzW6qVV/tXUDees9RsIMgGza2nDjt0jU+8frOFSWCE47fWotR1pYRxPc16BD/VAM= - secure: CWPefKG+DqBLKhHARn4tfDU/B604JhT4Q3ZWwamSI3+xjMsv6nqTS7ZH/yH/R/cGpEUajpSAX+62bj1HZc66ZLGtx9vedVUT54O1pMjRNzNbQnoPhHx8Ri+YZfRZ4FcVVXnlW518eTvxUVs+XhBWC36QYY5l0XNkYAi6ljhv/fFcjegjtTnqSHxA+3J9c8/U38x5KYj+Qj/0HAcga5U2ZU12UWYHmb8/V0tFAKkg7QnykiuTi8t7Zi8bCqh0gqQq17AhNFQlgFnjuzsm7wpuz3X0dXT1NxlDjnsAFjorLrFdiUqmJkwE8AEYS/ify6EC1kc4ijXNhbC2eDWBPb2qpdhEJ0TxmxUl3RVrk/0eAtP03o1k6CnUHNusTPEIif+aMJlmjSKCOFmPVHwdudK7UXI8lt5X6141aptK8ELuZaciBjI3OBHFPRnbJpgZLGWW3Rxlr54nsaE3I5hvpGGr32fqkDrBHYrcIHD01pmrSn78D6S9hOMl/MisMiKYL/M0YPyBfKsAathbEqCbITm5w94tOWP1vYkLAKrUsWx2gXivqGNwrTe8bVNQjmAru4/I1OI3BtBaGNPuDbTNChN+OCcALtGVEuQ4/z6r1t0C/+VxjWsra/ifQiEVraV9bdlRXj+8ZvzSlms6ooejSITQoA554cyWRIOCNO7kPE22sJk= - secure: F0zEnxV9Pa+j4tjPsX9Fd16PuTa48g7vT5j2dm5t3v3LK3TouebpJluwIjIWScyHfIWq4Kayv6c1X/DGZxbOti4gMqY09FnQ439nsLqzFSn83MR3EKMJEp6RHKTo3dL4VPs7OWLSMOuv3kI1jCH163uiX1AhnC8TS8tCrQbIMslCQUjWjXoCqllZ9lnwYwj1a/dd0X+miM0PRnLsgd2ozGmugVdpPafZ3wJpDb29+i0g7guqE3ApppvluSdUPy/+gcmCWMF+/8f10sBizGOAHJfVF/LYUuItoOuq7QP4InWF1Tp8iNp4sfq6LffeJan1xZUfZrHJbzS3GXfAXXomlURGece/BDWDmjwJ0ZMNLJHVPtJUFgo72aes1vGJ+ttLCk99dGChq72rNnGIpEFrtaC4kRCuC8hTF+o8QRfia41197cnPAm3LOcDOWWNav3JwVUf7P+rgQisq1FvkYD/9ZU6bss9g1kICqXZXa0l50BA5k5pZvCvsDCBVOlj+EM/eCk5JVuEjcl5Nxq43Ngu8myDvGu8lGsrvP1+Pf40XsiDf4GShIHHoTr9BO0OnQa0OWdNJGOZTCc4wPEkm5KsxxfKo9W+Z87akan75y3aWPCLJyCw3kjGj8zNAfrWhPwohA6C6V2v5mewOIL5QlYubEUn8MzAz0R+9vMrKyhHtP0= -addons: - srcclr: true diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 57bf1aece..c91aee67c 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -4,6 +4,7 @@ * Joins are now reactive too. This removes the need for reattach methods. * Examples now contains `rx-mongo`. +* Documentation now points to examples. == 0.1.1 * Improve RX support for `rx-gorm-rest`. diff --git a/README.adoc b/README.adoc index 57f33b280..40ed44685 100644 --- a/README.adoc +++ b/README.adoc @@ -1,3 +1,7 @@ +https://semaphoreci.com/rahulsom/grooves[image:https://semaphoreci.com/api/v1/rahulsom/grooves/branches/0-1-x/shields_badge.svg[Build +Status]] +https://opensource.org/licenses/Apache-2.0[image:https://img.shields.io/badge/License-Apache%202.0-blue.svg[License]] + = Grooves Event Sourcing Library for Java. @@ -13,17 +17,28 @@ https://oss.sonatype.org/#nexus-search;quick~grooves-gorm[grooves-gorm]:: This is for you if you're using GORM for persistence. It has interfaces that need to know the domain object used, and build flexible queries without forcing you to write the code to read domain objects from the database. +image::https://api.bintray.com/packages/bintray/jcenter/com.github.rahulsom%3Agrooves-gorm/images/download.svg[link="https://bintray.com/bintray/jcenter/com.github.rahulsom%3Agrooves-gorm/_latestVersion"] + + https://oss.sonatype.org/#nexus-search;quick~grooves-groovy[grooves-groovy]:: This is for you if you want to write your queries in groovy, but are not using GORM. You will have to implement your own data access, but will get support for checking completeness of queries based on groovy AST Transformations. +image::https://api.bintray.com/packages/bintray/jcenter/com.github.rahulsom%3Agrooves-groovy/images/download.svg[link="https://bintray.com/bintray/jcenter/com.github.rahulsom%3Agrooves-groovy/_latestVersion"] + + https://oss.sonatype.org/#nexus-search;quick~grooves-api[grooves-api]:: This is for you if you are not using groovy, or don't care about verifying completeness of queries. +image::https://api.bintray.com/packages/bintray/jcenter/com.github.rahulsom%3Agrooves-api/images/download.svg[link="https://bintray.com/bintray/jcenter/com.github.rahulsom%3Agrooves-api/_latestVersion"] + + https://oss.sonatype.org/#nexus-search;quick~grooves-types[grooves-types]:: This contains types that are used in `grooves-api`. It is very unlikely you'll be using this directly. +image::https://api.bintray.com/packages/bintray/jcenter/com.github.rahulsom%3Agrooves-types/images/download.svg[link="https://bintray.com/bintray/jcenter/com.github.rahulsom%3Agrooves-types/_latestVersion"] + == Examples link:examples/grails/rdbms[examples/grails/rdbms]:: diff --git a/bds_init.gradle b/bds_init.gradle new file mode 100644 index 000000000..bd4ead1c2 --- /dev/null +++ b/bds_init.gradle @@ -0,0 +1,14 @@ +initscript { + repositories { mavenCentral() } + dependencies { + classpath group:'com.blackducksoftware.integration', name: 'hub-gradle-plugin', version: System.properties['bdsPluginVersion'] + } +} + +allprojects { + apply plugin: com.blackducksoftware.integration.gradle.HubGradlePlugin + + buildBom{ + deployHubBdio false + } +} diff --git a/build.gradle b/build.gradle index 1d5e995f0..00d99c074 100644 --- a/build.gradle +++ b/build.gradle @@ -4,11 +4,11 @@ buildscript { maven { url 'https://plugins.gradle.org/m2/' } } dependencies { - classpath 'com.gradle:build-scan-plugin:1.6' + classpath 'com.gradle:build-scan-plugin:1.7.4' classpath 'me.champeau.gradle:buildscan-recipes-plugin:0.2.0' classpath 'fr.jcgay:gradle-notifier:1.1.0' classpath 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.5.3' - classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.3' + classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:+' classpath 'com.netflix.nebula:nebula-release-plugin:4.2.0' } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index e8cf5d80a..12f6c80e0 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -6,7 +6,7 @@ repositories { apply plugin: 'groovy' dependencies { - compile 'org.asciidoctor:asciidoctorj:1.5.0' + compile 'org.asciidoctor:asciidoctorj:1.5.4.1' compile 'com.github.sommeri:less4j:1.17.2' compileOnly ('org.kordamp.gipsy:gipsy:0.4.0') { exclude module: 'groovy-all' diff --git a/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/Patient.groovy b/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/Patient.groovy index eff04fb98..05ef3747c 100644 --- a/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/Patient.groovy +++ b/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/Patient.groovy @@ -1,18 +1,22 @@ package grooves.grails.mongo +// tag::documented[] import com.github.rahulsom.grooves.groovy.transformations.Aggregate import com.github.rahulsom.grooves.api.AggregateType import groovy.transform.EqualsAndHashCode +// end::documented[] /** * Represents a Patient * * @author Rahul Somasunderam */ -@Aggregate +// tag::documented[] +@Aggregate // <1> @EqualsAndHashCode(includes = ['uniqueId']) -class Patient implements AggregateType { +class Patient implements AggregateType { //<2> + // Long id // <3> String uniqueId static constraints = { } @@ -20,3 +24,4 @@ class Patient implements AggregateType { @Override String toString() { "Patient($id, $uniqueId)" } } +// end::documented[] diff --git a/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientAccount.groovy b/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientAccount.groovy index 882536eba..44c5d2c6f 100644 --- a/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientAccount.groovy +++ b/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientAccount.groovy @@ -13,13 +13,14 @@ import static rx.Observable.* */ @SuppressWarnings(['DuplicateNumberLiteral', 'DuplicateStringLiteral',]) @EqualsAndHashCode(includes = ['aggregateId', 'lastEventPosition']) -class PatientAccount implements JavaSnapshot { +// tag::documented[] +class PatientAccount implements JavaSnapshot { // <1> static mapWith = 'mongo' String id - Long lastEventPosition - Date lastEventTimestamp + Long lastEventPosition // <2> + Date lastEventTimestamp // <3> Set processingErrors = [] Long aggregateId @@ -27,14 +28,14 @@ class PatientAccount implements JavaSnapshot getAggregateObservable() { + Observable getAggregateObservable() { // <4> aggregateId ? defer { just aggregate } : empty() } void setAggregate(Patient aggregate) { this.aggregateId = aggregate.id } @Override - Observable getDeprecatedByObservable() { + Observable getDeprecatedByObservable() { // <5> deprecatedBy ? just(deprecatedBy) : empty() } Long deprecatedById @@ -44,7 +45,7 @@ class PatientAccount implements JavaSnapshot getDeprecatesObservable() { + Observable getDeprecatesObservable() { // <6> deprecatesIds ? from(deprecatesIds).flatMap { Patient.get it } : empty() } Set deprecatesIds @@ -74,3 +75,4 @@ class PatientAccount implements JavaSnapshot { +// tag::abstract[] +abstract class PatientEvent implements BaseEvent { // <1> - RevertEvent revertedBy + RevertEvent revertedBy // <2> String createdBy - Date timestamp - Long position + Date timestamp // <3> + Long position // <4> Patient aggregate - Observable getAggregateObservable() { just(aggregate) } + Observable getAggregateObservable() { just(aggregate) } // <5> - static transients = ['revertedBy'] + static transients = ['revertedBy'] // <6> static constraints = { } + // end::abstract[] String getTs() { timestamp.format('yyyy-MM-dd') } @Override String toString() { "PatientEvent($id, $aggregateId, $ts)" } +// tag::abstract[] } +// end::abstract[] -@Event(Patient) @EqualsAndHashCode -class PatientCreated extends PatientEvent { +//tag::created[] +@Event(Patient) // <1> +class PatientCreated extends PatientEvent { // <2> String name - @Override String getAudit() { new JsonBuilder([name: name]).toString() } + @Override String getAudit() { new JsonBuilder([name: name]).toString() } // <3> + //end::created[] @Override String toString() { "<${aggregateId}.$id> $ts created as ${name}" } +//tag::created[] } +//end::created[] @EqualsAndHashCode class PatientAddedToZipcode extends PatientEvent implements @@ -90,15 +98,20 @@ class PaymentMade extends PatientEvent { @Override String toString() { "<${aggregateId}.$id> $ts paid \$ $amount" } } +//tag::reverted[] @EqualsAndHashCode -class PatientEventReverted extends PatientEvent implements - RevertEvent { - Long revertedEventId +class PatientEventReverted + extends PatientEvent // <1> + implements RevertEvent { // <2> + Long revertedEventId // <3> @Override String getAudit() { new JsonBuilder([revertedEvent: revertedEventId]).toString() } + //end::reverted[] @Override String toString() { "<${aggregateId}.$id> $ts reverted #$revertedEventId" } + //tag::reverted[] } +//end::reverted[] @EqualsAndHashCode class PatientDeprecatedBy extends PatientEvent implements diff --git a/examples/grails/rdbms_mongo/src/main/groovy/grooves/grails/mongo/PatientAccountQuery.groovy b/examples/grails/rdbms_mongo/src/main/groovy/grooves/grails/mongo/PatientAccountQuery.groovy index 402927d66..b7f9d05fd 100644 --- a/examples/grails/rdbms_mongo/src/main/groovy/grooves/grails/mongo/PatientAccountQuery.groovy +++ b/examples/grails/rdbms_mongo/src/main/groovy/grooves/grails/mongo/PatientAccountQuery.groovy @@ -12,20 +12,21 @@ import static rx.Observable.just /** * Queries for the PatientAccount */ -@Query(aggregate = Patient, snapshot = PatientAccount) @GrailsCompileStatic -class PatientAccountQuery implements - GormQuerySupport { +//tag::documented[] +@Query(aggregate = Patient, snapshot = PatientAccount) // <5> +class PatientAccountQuery + implements GormQuerySupport { // <6> - final Class snapshotClass = PatientAccount - final Class eventClass = PatientEvent + final Class eventClass = PatientEvent // <7> + final Class snapshotClass = PatientAccount // <8> @Override - PatientAccount createEmptySnapshot() { new PatientAccount(deprecates: []) } + PatientAccount createEmptySnapshot() { new PatientAccount(deprecates: []) } // <9> @Override - boolean shouldEventsBeApplied(PatientAccount snapshot) { + boolean shouldEventsBeApplied(PatientAccount snapshot) { // <10> true } @@ -35,18 +36,19 @@ class PatientAccountQuery implements } @Override - Observable onException( + Observable onException( // <11> Exception e, PatientAccount snapshot, PatientEvent event) { snapshot.processingErrors << e.message just CONTINUE } - Observable applyPatientCreated( + Observable applyPatientCreated( // <12> PatientCreated event, PatientAccount snapshot) { snapshot.name = snapshot.name ?: event.name - just CONTINUE + just CONTINUE // <13> } + //end::documented[] Observable applyProcedurePerformed( ProcedurePerformed event, PatientAccount snapshot) { snapshot.balance += event.cost @@ -60,12 +62,6 @@ class PatientAccountQuery implements just CONTINUE } - @SuppressWarnings(['DuplicateStringLiteral', 'UnusedMethodParameter',]) - Observable applyPatientAddedToZipcode( - PatientAddedToZipcode event, PatientAccount snapshot) { - just CONTINUE // Ignore zip change - } - @Override PatientAccount detachSnapshot(PatientAccount snapshot) { if (snapshot.isAttached()) { @@ -74,5 +70,6 @@ class PatientAccountQuery implements } snapshot } - + //tag::documented[] } +//end::documented[] diff --git a/examples/javaee/src/main/java/grooves/example/javaee/Database.java b/examples/javaee/src/main/java/grooves/example/javaee/Database.java index b2dad68d8..5bb02bd8f 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/Database.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/Database.java @@ -6,6 +6,7 @@ import javax.ejb.Singleton; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.stream.Stream; @@ -49,4 +50,12 @@ public void addEvent(PatientEvent event) { public void addSnapshot(Object snapshot) { snapshotList.add(snapshot); } + + public static boolean isTimestampInRange( + Date lowerBoundExclusive, Date timestamp, Date upperBoundInclusive) { + return (lowerBoundExclusive == null || timestamp.compareTo(lowerBoundExclusive) > 0) + && timestamp.compareTo(upperBoundInclusive) <= 0; + } + + } diff --git a/examples/javaee/src/main/java/grooves/example/javaee/domain/Patient.java b/examples/javaee/src/main/java/grooves/example/javaee/domain/Patient.java index 86cdd54ec..9ef3d9663 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/domain/Patient.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/domain/Patient.java @@ -1,12 +1,15 @@ package grooves.example.javaee.domain; +// tag::documented[] import com.github.rahulsom.grooves.api.AggregateType; import java.io.Serializable; -public class Patient implements AggregateType, Serializable { - private Long id; +public class Patient implements AggregateType, // <1> + Serializable { + private Long id; // <2> private String uniqueId; + // end::documented[] @Override public String toString() { @@ -29,4 +32,6 @@ public String getUniqueId() { public Patient(String uniqueId) { this.uniqueId = uniqueId; } + // tag::documented[] } +// end::documented[] diff --git a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientAccount.java b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientAccount.java index 7fc98b4be..f5f19abab 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientAccount.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientAccount.java @@ -1,7 +1,7 @@ package grooves.example.javaee.domain; import com.github.rahulsom.grooves.api.snapshots.JavaSnapshot; -import com.github.rahulsom.grooves.api.snapshots.Snapshot; +import org.jetbrains.annotations.NotNull; import rx.Observable; import javax.xml.bind.annotation.XmlTransient; @@ -14,36 +14,48 @@ import static rx.Observable.from; import static rx.Observable.just; -public class PatientAccount implements - JavaSnapshot, Serializable { +// tag::documented[] +public class PatientAccount + implements JavaSnapshot, // <1> + Serializable { private Long id; private Patient aggregate; private Patient deprecatedBy; private List deprecates = new ArrayList<>(); - private Long lastEventPosition; - private Date lastEventTimestamp; + private Long lastEventPosition; // <2> + private Date lastEventTimestamp; // <3> private String name; private BigDecimal balance = new BigDecimal(0); private BigDecimal moneyMade = new BigDecimal(0); + // end::documented[] + @NotNull @Override @XmlTransient - public Observable getAggregateObservable() { + // tag::documented[] + public Observable getAggregateObservable() { // <4> return just(aggregate); } + // end::documented[] + @NotNull @Override @XmlTransient - public Observable getDeprecatedByObservable() { + // tag::documented[] + public Observable getDeprecatedByObservable() { // <5> return just(deprecatedBy); } + // end::documented[] + @NotNull @Override @XmlTransient - public Observable getDeprecatesObservable() { + // tag::documented[] + public Observable getDeprecatesObservable() { // <6> return from(deprecates); } + // end::documented[] @Override public String toString() { @@ -67,7 +79,7 @@ public Patient getAggregate() { } @Override - public void setAggregate(Patient aggregate) { + public void setAggregate(@NotNull Patient aggregate) { this.aggregate = aggregate; } @@ -76,7 +88,7 @@ public Patient getDeprecatedBy() { } @Override - public void setDeprecatedBy(Patient deprecatedBy) { + public void setDeprecatedBy(@NotNull Patient deprecatedBy) { this.deprecatedBy = deprecatedBy; } @@ -131,4 +143,6 @@ public BigDecimal getMoneyMade() { public void setMoneyMade(BigDecimal moneyMade) { this.moneyMade = moneyMade; } + // tag::documented[] } +// end::documented[] diff --git a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientCreated.java b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientCreated.java index 7324bdb9e..9c9003f9f 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientCreated.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientCreated.java @@ -1,12 +1,14 @@ package grooves.example.javaee.domain; -public class PatientCreated extends PatientEvent { +//tag::documented[] +public class PatientCreated extends PatientEvent { // <1> private String name; @Override - public String getAudit() { + public String getAudit() { // <2> return "name: " + name; } + //end::documented[] @Override public String toString() { @@ -16,8 +18,10 @@ public String toString() { public String getName() { return name; } - + //tag::documented[] + public PatientCreated(String name) { this.name = name; } } +//end::documented[] \ No newline at end of file diff --git a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEvent.java b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEvent.java index 97bb4090b..9449ac128 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEvent.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEvent.java @@ -2,26 +2,33 @@ import com.github.rahulsom.grooves.api.events.BaseEvent; import com.github.rahulsom.grooves.api.events.RevertEvent; +import org.jetbrains.annotations.NotNull; import rx.Observable; import javax.xml.bind.annotation.XmlTransient; import java.util.Date; +// tag::documented[] +import static rx.Observable.empty; import static rx.Observable.just; -public abstract class PatientEvent implements BaseEvent { +public abstract class PatientEvent implements BaseEvent { // <1> private Patient aggregate; private Long id; private String createdBy; - private RevertEvent revertedBy; - private Date timestamp; - private Long position; + private RevertEvent revertedBy; // <2> + private Date timestamp; // <3> + private Long position; // <4> - @Override + // end::documented[] @XmlTransient - public Observable getAggregateObservable() { - return just(aggregate); + // tag::documented[] + @Override + @NotNull + public Observable getAggregateObservable() { // <5> + return aggregate != null ? just(aggregate) : empty(); } + // end::documented[] public int getObjectId() { return System.identityHashCode(this); @@ -78,4 +85,6 @@ public Long getPosition() { public void setPosition(Long position) { this.position = position; } + // tag::documented[] } +// end::documented[] diff --git a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEventReverted.java b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEventReverted.java index 5e81bceaf..5046188f4 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEventReverted.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEventReverted.java @@ -1,13 +1,15 @@ package grooves.example.javaee.domain; +//tag::documented[] import com.github.rahulsom.grooves.api.events.RevertEvent; -public class PatientEventReverted extends PatientEvent implements - RevertEvent { +public class PatientEventReverted + extends PatientEvent // <1> + implements RevertEvent { // <2> private Long revertedEventId; @Override - public Long getRevertedEventId() { + public Long getRevertedEventId() { // <3> return revertedEventId; } @@ -15,6 +17,7 @@ public Long getRevertedEventId() { public String getAudit() { return "revertedEvent:" + revertedEventId; } + //end::documented[] @Override public String toString() { @@ -24,4 +27,6 @@ public String toString() { public PatientEventReverted(Long revertedEventId) { this.revertedEventId = revertedEventId; } + //tag::documented[] } +//end::documented[] \ No newline at end of file diff --git a/examples/javaee/src/main/java/grooves/example/javaee/queries/CustomQuerySupport.java b/examples/javaee/src/main/java/grooves/example/javaee/queries/CustomQuerySupport.java index 419b131bd..d95078b2e 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/queries/CustomQuerySupport.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/queries/CustomQuerySupport.java @@ -17,21 +17,27 @@ import java.util.stream.Stream; import static com.github.rahulsom.grooves.api.EventApplyOutcome.CONTINUE; +import static grooves.example.javaee.Database.isTimestampInRange; import static java.util.stream.Collectors.toList; import static rx.Observable.from; import static rx.Observable.just; +// tag::documented[] public interface CustomQuerySupport< - SnapshotT extends Snapshot & Serializable, - QueryT extends CustomQuerySupport - > extends QuerySupport { + SnapshotT extends Snapshot & Serializable, // <1> + QueryT extends CustomQuerySupport// <2> + > extends QuerySupport { // <3> + // end::documented[] Database getDatabase(); Class getSnapshotClass(); + // tag::documented[] @Override default Observable getSnapshot(long maxPosition, Patient aggregate) { + // <4> + // end::documented[] final Stream stream = getDatabase().snapshots(getSnapshotClass()); return Observable.from(stream::iterator) .flatMap(it -> Observable.just(it).zipWith(it.getAggregateObservable(), Pair::new)) @@ -42,10 +48,13 @@ default Observable getSnapshot(long maxPosition, Patient aggregate) { .takeFirst(it -> true) .filter(Objects::nonNull) .map(this::copy); + // tag::documented[] } @Override default Observable getSnapshot(Date maxTimestamp, Patient aggregate) { + // <5> + // end::documented[] final Stream stream = getDatabase().snapshots(getSnapshotClass()); return Observable.from(stream::iterator) .flatMap(it -> Observable.just(it).zipWith(it.getAggregateObservable(), Pair::new)) @@ -56,35 +65,44 @@ default Observable getSnapshot(Date maxTimestamp, Patient aggregate) .takeFirst(it -> true) .filter(Objects::nonNull) .map(this::copy); + // tag::documented[] } + // end::documented[] default SnapshotT copy(SnapshotT it) { return (SnapshotT) SerializationUtils.clone(it); } + // tag::documented[] @Override - default boolean shouldEventsBeApplied(SnapshotT snapshot) { + default boolean shouldEventsBeApplied(SnapshotT snapshot) { // <6> return true; } @Override default Observable findEventsForAggregates(List aggregates) { + // <7> + // end::documented[] final List patientEvents = getDatabase().events() .filter(x -> aggregates.contains(x.getAggregate())) .collect(toList()); return from(patientEvents); + // tag::documented[] } @Override default Observable onException( - Exception e, SnapshotT snapshot, PatientEvent event) { + Exception e, SnapshotT snapshot, PatientEvent event) { // <8> getLog().error("Error computing snapshot", e); return just(CONTINUE); + // tag::documented[] } @Override default Observable getUncomputedEvents( Patient aggregate, SnapshotT lastSnapshot, long version) { + // <9> + // end::documented[] final List patientEvents = getDatabase().events() .filter(x -> x.getAggregate().equals(aggregate) && (lastSnapshot.getLastEventPosition() == null @@ -92,23 +110,22 @@ default Observable getUncomputedEvents( && x.getPosition() <= version) .collect(toList()); return from(patientEvents); + // tag::documented[] } @Override default Observable getUncomputedEvents( Patient aggregate, SnapshotT lastSnapshot, Date snapshotTime) { + // <10> + // end::documented[] final List patientEvents = getDatabase().events() - .filter(x -> { - final Date eventTime = x.getTimestamp(); - final Date lastEventTime = lastSnapshot.getLastEventTimestamp(); - return x.getAggregate().equals(aggregate) - && (lastEventTime == null - || eventTime.compareTo(lastEventTime) > 0) - && eventTime.compareTo(snapshotTime) <= 0; - } - ) + .filter(it -> aggregate.equals(it.getAggregate()) + && isTimestampInRange( + lastSnapshot.getLastEventTimestamp(), it.getTimestamp(), snapshotTime)) .collect(toList()); return from(patientEvents); + // tag::documented[] } } +// end::documented[] diff --git a/examples/javaee/src/main/java/grooves/example/javaee/queries/PatientAccountQuery.java b/examples/javaee/src/main/java/grooves/example/javaee/queries/PatientAccountQuery.java index 124942735..4c381dd8b 100644 --- a/examples/javaee/src/main/java/grooves/example/javaee/queries/PatientAccountQuery.java +++ b/examples/javaee/src/main/java/grooves/example/javaee/queries/PatientAccountQuery.java @@ -11,9 +11,11 @@ import static com.github.rahulsom.grooves.api.EventApplyOutcome.CONTINUE; import static rx.Observable.just; -public class PatientAccountQuery implements CustomQuerySupport { +// tag::documented[] +public class PatientAccountQuery + implements CustomQuerySupport { // <11> + // end::documented[] @Inject private Database database; @@ -27,10 +29,12 @@ public Class getSnapshotClass() { return PatientAccount.class; } + // tag::documented[] @Override - public PatientAccount createEmptySnapshot() { + public PatientAccount createEmptySnapshot() { // <12> return new PatientAccount(); } + // end::documented[] @Override public void addToDeprecates(PatientAccount snapshot, Patient deprecatedAggregate) { @@ -43,20 +47,23 @@ public void addToDeprecates(PatientAccount snapshot, Patient deprecatedAggregate * @param snapshot The snapshot. * @return the result of apply */ + // tag::documented[] public Observable applyPatientCreated( - PatientCreated event, PatientAccount snapshot) { + PatientCreated event, PatientAccount snapshot) { // <13> if (snapshot.getName() == null) { snapshot.setName(event.getName()); } - return just(CONTINUE); + return just(CONTINUE); // <14> } + // end::documented[] /** * Applies procedure performed. * @param event the event. * @param snapshot The snapshot. * @return the result of apply */ + // tag::documented[] public Observable applyProcedurePerformed( ProcedurePerformed event, PatientAccount snapshot) { final double cost = event.getCost().doubleValue(); @@ -67,12 +74,14 @@ public Observable applyProcedurePerformed( return just(CONTINUE); } + // end::documented[] /** * Applies Payment made. * @param event the event. * @param snapshot The snapshot. * @return the result of apply */ + // tag::documented[] public Observable applyPaymentMade( PaymentMade event, PatientAccount snapshot) { @@ -87,3 +96,4 @@ public Observable applyPaymentMade( } } +// end::documented[] diff --git a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/Patient.groovy b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/Patient.groovy index edc56728c..3690b3d34 100644 --- a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/Patient.groovy +++ b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/Patient.groovy @@ -1,26 +1,34 @@ package grooves.boot.jpa.domain +// tag::documented[] import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.github.rahulsom.grooves.api.AggregateType +import com.github.rahulsom.grooves.groovy.transformations.Aggregate import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import javax.persistence.* +// end::documented[] /** * Domain model for Patient * * @author Rahul Somasunderam */ @SuppressWarnings(['DuplicateStringLiteral']) +// tag::documented[] @Entity @Table(uniqueConstraints = [ @UniqueConstraint(name = 'UK_PATIENT_UNIQUEID', columnNames = ['uniqueId']), ]) @ToString(includeSuperProperties = true, includeNames = true, includePackage = false) @JsonIgnoreProperties(['hibernateLazyInitializer', 'handler']) +@Aggregate // <1> +// end::documented[] @EqualsAndHashCode(includes = ['uniqueId']) -class Patient implements AggregateType { - @GeneratedValue @Id Long id +// tag::documented[] +class Patient implements AggregateType { //<2> + @GeneratedValue @Id Long id // <3> String uniqueId } +// end::documented[] diff --git a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientAccount.groovy b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientAccount.groovy index a2f56ea4a..6cb74d36a 100644 --- a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientAccount.groovy +++ b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientAccount.groovy @@ -18,14 +18,15 @@ import static rx.Observable.* @Entity @ToString(includeSuperProperties = true, includeNames = true, includePackage = false) @SuppressWarnings(['DuplicateNumberLiteral']) -class PatientAccount implements JavaSnapshot { +// tag::documented[] +class PatientAccount implements JavaSnapshot { // <1> @GeneratedValue @Id Long id - @Column(nullable = false) Long lastEventPosition + @Column(nullable = false) Long lastEventPosition // <2> @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") - @Column(nullable = false) Date lastEventTimestamp + @Column(nullable = false) Date lastEventTimestamp // <3> @OneToOne Patient deprecatedBy @OneToMany @JoinTable(name = 'PatientAccountDeprecates') Set deprecates @@ -37,15 +38,16 @@ class PatientAccount implements JavaSnapshot getAggregateObservable() { + @Override @JsonIgnore Observable getAggregateObservable() { // <4> aggregate ? just(aggregate) : empty() } - @Override @JsonIgnore Observable getDeprecatedByObservable() { + @Override @JsonIgnore Observable getDeprecatedByObservable() { // <5> deprecatedBy ? just(deprecatedBy) : empty() } - @Override @JsonIgnore Observable getDeprecatesObservable() { + @Override @JsonIgnore Observable getDeprecatesObservable() { // <6> from(deprecates.toList()) } } +// end::documented[] diff --git a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientEvent.groovy b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientEvent.groovy index c54a6a8af..bfc2762f2 100644 --- a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientEvent.groovy +++ b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientEvent.groovy @@ -11,6 +11,7 @@ import rx.Observable import javax.persistence.* +import static rx.Observable.empty import static rx.Observable.just /** @@ -18,36 +19,40 @@ import static rx.Observable.just * * @author Rahul Somasunderam */ +// tag::abstract[] @Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = 'eventType') @SuppressWarnings(['AbstractClassWithoutAbstractMethod']) -abstract class PatientEvent implements BaseEvent { +abstract class PatientEvent implements BaseEvent { // <1> @GeneratedValue @Id Long id - @Transient RevertEvent revertedBy + @Transient RevertEvent revertedBy // <2> @Column(nullable = false) String createdBy - @Column(nullable = false) Date timestamp - @Column(nullable = false) Long position + @Column(nullable = false) Date timestamp // <3> + @Column(nullable = false) Long position //<4> @OneToOne Patient aggregate - Observable getAggregateObservable() { just(aggregate) } + Observable getAggregateObservable() { aggregate ? just(aggregate) : empty() } // <5> } +// end::abstract[] -@Entity -@Event(Patient) @ToString(includeSuperProperties = true, includeNames = true, includePackage = false) -class PatientCreated extends PatientEvent { +//tag::created[] +@Entity +@Event(Patient) // <1> +class PatientCreated extends PatientEvent { // <2> String name @Override - String getAudit() { new JsonBuilder([name: name]).toString() } + String getAudit() { new JsonBuilder([name: name]).toString() } // <3> } +//end::created[] +@ToString(includeSuperProperties = true, includeNames = true, includePackage = false) @Entity @Event(Patient) -@ToString(includeSuperProperties = true, includeNames = true, includePackage = false) class ProcedurePerformed extends PatientEvent { String code BigDecimal cost @@ -56,9 +61,9 @@ class ProcedurePerformed extends PatientEvent { String getAudit() { new JsonBuilder([code: code, cost: cost]).toString() } } +@ToString(includeSuperProperties = true, includeNames = true, includePackage = false) @Entity @Event(Patient) -@ToString(includeSuperProperties = true, includeNames = true, includePackage = false) class PaymentMade extends PatientEvent { BigDecimal amount @@ -66,15 +71,18 @@ class PaymentMade extends PatientEvent { String getAudit() { new JsonBuilder([amount: amount]).toString() } } -@Entity @ToString(includeSuperProperties = true, includeNames = true, includePackage = false) -class PatientEventReverted extends PatientEvent implements - RevertEvent { - Long revertedEventId +//tag::reverted[] +@Entity +class PatientEventReverted + extends PatientEvent // <1> + implements RevertEvent { // <2> + Long revertedEventId // <3> @Override String getAudit() { new JsonBuilder([revertedEvent: revertedEventId]).toString() } } +//end::reverted[] @Entity class PatientDeprecatedBy extends PatientEvent implements diff --git a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/queries/PatientAccountQuery.groovy b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/queries/PatientAccountQuery.groovy index d62e93522..8d130a223 100644 --- a/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/queries/PatientAccountQuery.groovy +++ b/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/queries/PatientAccountQuery.groovy @@ -13,6 +13,8 @@ import rx.Observable import javax.persistence.EntityManager import static com.github.rahulsom.grooves.api.EventApplyOutcome.CONTINUE + +import static rx.Observable.from import static rx.Observable.just /** @@ -22,10 +24,13 @@ import static rx.Observable.just */ @Transactional @Component -@Query(aggregate = Patient, snapshot = PatientAccount) +//tag::documented[] +@Query(aggregate = Patient, snapshot = PatientAccount) // <1> class PatientAccountQuery implements - QuerySupport { + QuerySupport { // <2> + //end::documented[] public static final String AGGREGATE = 'aggregate' public static final String POSITION = 'position' public static final String TIMESTAMP = 'timestamp' @@ -36,32 +41,56 @@ class PatientAccountQuery implements @Autowired PatientAccountRepository patientAccountRepository + //tag::documented[] @Override - PatientAccount createEmptySnapshot() { - new PatientAccount(deprecates: []) - } - - @Override - Observable getSnapshot(long maxPosition, Patient aggregate) { + Observable getSnapshot(long maxPosition, Patient aggregate) { // <3> + //end::documented[] def snapshots = maxPosition == Long.MAX_VALUE ? patientAccountRepository.findAllByAggregateId(aggregate.id) : patientAccountRepository.findAllByAggregateIdAndLastEventPositionLessThan( aggregate.id, maxPosition) snapshots ? just(detachSnapshot(snapshots[0])) : Observable.empty() + //tag::documented[] } @Override - Observable getSnapshot(Date maxTimestamp, Patient aggregate) { + Observable getSnapshot(Date maxTimestamp, Patient aggregate) { // <4> + //end::documented[] def snapshots = maxTimestamp == null ? patientAccountRepository.findAllByAggregateId(aggregate.id) : patientAccountRepository.findAllByAggregateIdAndLastEventTimestampLessThan( aggregate.id, maxTimestamp) snapshots ? just(detachSnapshot(snapshots[0])) : Observable.empty() + //tag::documented[] + } + + @Override + boolean shouldEventsBeApplied(PatientAccount snapshot) { // <5> + true + } + + @Override + Observable findEventsForAggregates(List aggregates) { // <6> + //end::documented[] + def cb = entityManager.criteriaBuilder + def q = cb.createQuery(PatientEvent) + def root = q.from(PatientEvent) + def criteria = q.select(root).where(root.get(AGGREGATE).in(aggregates)) + from entityManager.createQuery(criteria).resultList + //tag::documented[] + } + + @Override + Observable onException( + Exception e, PatientAccount snapshot, PatientEvent event) { // <7> + snapshot.processingErrors++ + just CONTINUE } @Override Observable getUncomputedEvents( - Patient patient, PatientAccount lastSnapshot, long version) { + Patient patient, PatientAccount lastSnapshot, long version) { // <8> + //end::documented[] def cb = entityManager.criteriaBuilder def q = cb.createQuery(PatientEvent) def root = q.from(PatientEvent) @@ -70,17 +99,17 @@ class PatientAccountQuery implements cb.gt(root.get(POSITION), lastSnapshot?.lastEventPosition ?: 0L), cb.le(root.get(POSITION), version), ) - Observable.from( - entityManager + from(entityManager .createQuery(criteria) .setParameter(AGGREGATE, patient) - .resultList - ) + .resultList) + //tag::documented[] } @Override Observable getUncomputedEvents( - Patient aggregate, PatientAccount lastSnapshot, Date snapshotTime) { + Patient aggregate, PatientAccount lastSnapshot, Date snapshotTime) { // <9> + //end::documented[] def cb = entityManager.criteriaBuilder def q = cb.createQuery(PatientEvent) def root = q.from(PatientEvent) @@ -101,21 +130,13 @@ class PatientAccountQuery implements query.setParameter(FROM, lastSnapshot.lastEventTimestamp) } - Observable.from(query.resultList) - } - - @Override - boolean shouldEventsBeApplied(PatientAccount snapshot) { - true + from query.resultList + //tag::documented[] } @Override - Observable findEventsForAggregates(List aggregates) { - def cb = entityManager.criteriaBuilder - def q = cb.createQuery(PatientEvent) - def root = q.from(PatientEvent) - def criteria = q.select(root).where(root.get(AGGREGATE).in(aggregates)) - Observable.from entityManager.createQuery(criteria).resultList + PatientAccount createEmptySnapshot() { // <10> + new PatientAccount(deprecates: []) } @Override @@ -123,17 +144,10 @@ class PatientAccountQuery implements snapshot.deprecates << deprecatedAggregate } - @Override - Observable onException( - Exception e, PatientAccount snapshot, PatientEvent event) { - snapshot.processingErrors++ - just CONTINUE - } - Observable applyPatientCreated( - PatientCreated event, PatientAccount snapshot) { + PatientCreated event, PatientAccount snapshot) { // <11> snapshot.name = snapshot.name ?: event.name - just CONTINUE + just CONTINUE // <12> } Observable applyProcedurePerformed( @@ -149,6 +163,7 @@ class PatientAccountQuery implements just CONTINUE } + //end::documented[] PatientAccount detachSnapshot(PatientAccount snapshot) { def retval = new PatientAccount( lastEventPosition: snapshot.lastEventPosition, @@ -163,4 +178,6 @@ class PatientAccountQuery implements snapshot.deprecates.each { retval.deprecates.add it } retval } + //tag::documented[] } +//end::documented[] diff --git a/examples/springboot/kotlin/build.gradle b/examples/springboot/kotlin/build.gradle index dd67e0d7c..9f3fc6480 100644 --- a/examples/springboot/kotlin/build.gradle +++ b/examples/springboot/kotlin/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlinVersion = '1.1.2' + kotlinVersion = '1.1.3' springBootVersion = '2.0.0.M1' } repositories { diff --git a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/Patient.kt b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/Patient.kt index a747525db..0f67564c9 100644 --- a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/Patient.kt +++ b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/Patient.kt @@ -1,9 +1,10 @@ package grooves.boot.kotlin.domain +// tag::documented[] import com.github.rahulsom.grooves.api.AggregateType -class Patient(var uniqueId: String? = null) : AggregateType { - override var id: String? = null +class Patient(var uniqueId: String? = null) : AggregateType { // <1> + override var id: String? = null // <2> override fun toString() = "Patient(uniqueId=$uniqueId, id=$id)" - } +// end::documented[] diff --git a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientAccount.kt b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientAccount.kt index 657854cd0..a2b4fe31a 100644 --- a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientAccount.kt +++ b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientAccount.kt @@ -11,13 +11,14 @@ import rx.Observable.just import java.math.BigDecimal import java.util.* +// tag::documented[] @Configurable -class PatientAccount : Snapshot { +class PatientAccount : Snapshot { // <1> override var id: String? = null - override var lastEventPosition: Long? = null + override var lastEventPosition: Long? = null // <2> @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") - override var lastEventTimestamp: Date? = null + override var lastEventTimestamp: Date? = null // <3> val deprecatesIds: MutableList = mutableListOf() private var deprecator: Patient? = null var aggregateId: String? = null @@ -29,7 +30,7 @@ class PatientAccount : Snapshot { fun getDeprecatedBy() = deprecator @JsonIgnore - override fun getAggregateObservable() = + override fun getAggregateObservable() = // <4> aggregateId?.let { patientRepository!!.findAllById(just(it)) } ?: empty() override fun setAggregate(aggregate: Patient) { @@ -41,7 +42,7 @@ class PatientAccount : Snapshot { var patientRepository: PatientRepository? = null @JsonIgnore - override fun getDeprecatedByObservable() = + override fun getDeprecatedByObservable() = // <5> deprecator?.let { just(it) } ?: empty() override fun setDeprecatedBy(deprecatingAggregate: Patient) { @@ -49,15 +50,16 @@ class PatientAccount : Snapshot { } @JsonIgnore - override fun getDeprecatesObservable() = + override fun getDeprecatesObservable() = // <6> if (deprecatesIds.size > 0) patientRepository!!.findAllById(deprecatesIds) else empty() - + // end::documented[] override fun toString(): String { return "PatientAccount(id=$id, aggregate=$aggregateId, " + "lastEventPosition=$lastEventPosition, lastEventTimestamp=$lastEventTimestamp)" } - -} \ No newline at end of file + // tag::documented[] +} +// end::documented[] diff --git a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientEvent.kt b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientEvent.kt index a3ae6c842..1f2de4312 100644 --- a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientEvent.kt +++ b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientEvent.kt @@ -9,6 +9,7 @@ import com.github.rahulsom.grooves.api.events.RevertEvent import grooves.boot.kotlin.BeansHolder import grooves.boot.kotlin.repositories.PatientEventRepository import org.springframework.data.annotation.Id +import org.springframework.data.annotation.Transient import rx.Observable import rx.Observable.empty import rx.Observable.just @@ -16,23 +17,25 @@ import java.math.BigDecimal import java.text.SimpleDateFormat import java.util.* -sealed class PatientEvent : BaseEvent { +// tag::patientEvent[] +sealed class PatientEvent : BaseEvent { // <1><2> @Id override val id: String? = null var aggregateId: String? = null + @Transient + override var revertedBy: RevertEvent? = null // <3> + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") - override var timestamp: Date? = null + override var timestamp: Date? = null // <4> override var createdBy: String? = null - override var position: Long? = null - - override var revertedBy: RevertEvent? = null + override var position: Long? = null // <5> fun getType() = this.javaClass.simpleName @JsonIgnore - override fun getAggregateObservable(): Observable = + override fun getAggregateObservable(): Observable = // <6> aggregate?.let { just(it) } ?: empty() override var aggregate: Patient? @@ -41,14 +44,20 @@ sealed class PatientEvent : BaseEvent { set(value) { aggregateId = value!!.id } + // end::patientEvent[] fun getTs() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(timestamp) - data class Reverted(override val revertedEventId: String) : - PatientEvent(), RevertEvent { + // tag::reverted[] + data class Reverted(override val revertedEventId: String) : // <1> + PatientEvent(), // <2> + RevertEvent { // <3> override fun getAudit(): String = "$id - Revert $revertedEventId" + // end::reverted[] override fun toString() = "[${getTs()}] #$position: ${getAudit()}" + // tag::reverted[] } + // end::reverted[] data class PatientDeprecates(val deprecated: Patient) : PatientEvent(), Deprecates { @@ -91,11 +100,15 @@ sealed class PatientEvent : BaseEvent { override fun toString() = "[${getTs()}] #$position: ${getAudit()}" } - sealed class Applicable : PatientEvent() { - data class Created(val name: String) : Applicable() { - override fun getAudit(): String = "$id - Created '$name'" + // tag::created[] + sealed class Applicable : PatientEvent() { // <1> + data class Created(val name: String) : Applicable() { // <2> + override fun getAudit(): String = "$id - Created '$name'" // <3> + // end::created[] override fun toString() = "[${getTs()}] #$position: ${getAudit()}" + // tag::created[] } + // end::created[] data class ProcedurePerformed(val code: String, val cost: BigDecimal) : Applicable() { override fun getAudit(): String = "$id - Performed '$code' for $cost" @@ -106,6 +119,9 @@ sealed class PatientEvent : BaseEvent { override fun getAudit(): String = "$id - Paid $amount" override fun toString() = "[${getTs()}] #$position: ${getAudit()}" } + // tag::created[] } - -} \ No newline at end of file + // end::created[] + // tag::patientEvent[] +} +// end::patientEvent[] diff --git a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/queries/PatientAccountQuery.kt b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/queries/PatientAccountQuery.kt index 64ebce93a..2750550e9 100644 --- a/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/queries/PatientAccountQuery.kt +++ b/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/queries/PatientAccountQuery.kt @@ -17,60 +17,69 @@ import java.lang.Exception import java.util.* @Component +//tag::documented[] class PatientAccountQuery : QuerySupport, + PatientAccountQuery>, // <1> SimpleQuery { + PatientAccount, PatientAccountQuery> { // <2> override fun getExecutor() = SimpleExecutor() + PatientEvent.Applicable, String, PatientAccount, PatientAccountQuery>() // <3> @Autowired lateinit var patientEventRepository: PatientEventRepository @Autowired lateinit var patientAccountRepository: PatientAccountRepository - override fun createEmptySnapshot() = PatientAccount() + override fun createEmptySnapshot() = PatientAccount() // <4> - override fun getSnapshot(maxPosition: Long, aggregate: Patient): Observable = + override fun getSnapshot( // <5> + maxPosition: Long, aggregate: Patient): Observable = patientAccountRepository.findByAggregateIdAndLastEventPositionLessThan( aggregate.id!!, maxPosition) - override fun getSnapshot(maxTimestamp: Date, aggregate: Patient): Observable = + override fun getSnapshot( // <6> + maxTimestamp: Date, aggregate: Patient): Observable = patientAccountRepository.findByAggregateIdAndLastEventTimestampLessThan( aggregate.id!!, maxTimestamp) - override fun shouldEventsBeApplied(snapshot: PatientAccount?) = true + override fun shouldEventsBeApplied(snapshot: PatientAccount?) = true // <7> - override fun findEventsForAggregates( + override fun findEventsForAggregates( // <8> aggregates: MutableList): Observable = patientEventRepository.findAllByAggregateIdIn(aggregates.map { it.id!! }) - override fun addToDeprecates(snapshot: PatientAccount, deprecatedAggregate: Patient) { + override fun addToDeprecates( + snapshot: PatientAccount, deprecatedAggregate: Patient) { snapshot.deprecatesIds.add(deprecatedAggregate.id!!) } - override fun onException(e: Exception, snapshot: PatientAccount, event: PatientEvent) = + override fun onException( // <9> + e: Exception, snapshot: PatientAccount, event: PatientEvent) = just(CONTINUE) - override fun getUncomputedEvents( - aggregate: Patient, lastSnapshot: PatientAccount, version: Long): Observable { + override fun getUncomputedEvents( // <10> + aggregate: Patient, lastSnapshot: PatientAccount, + version: Long): Observable { return patientEventRepository. findAllByPositionRange( aggregate.id!!, lastSnapshot.lastEventPosition ?: 0, version) } - override fun getUncomputedEvents( - aggregate: Patient, lastSnapshot: PatientAccount, snapshotTime: Date): Observable { + override fun getUncomputedEvents( // <11> + aggregate: Patient, lastSnapshot: PatientAccount, + snapshotTime: Date): Observable { return lastSnapshot.lastEventTimestamp?. let { - patientEventRepository.findAllByTimestampRange(aggregate.id!!, it, snapshotTime) + patientEventRepository.findAllByTimestampRange( + aggregate.id!!, it, snapshotTime) } ?: patientEventRepository. - findAllByAggregateIdAndTimestampLessThan(aggregate.id!!, snapshotTime) + findAllByAggregateIdAndTimestampLessThan( + aggregate.id!!, snapshotTime) } override fun applyEvent(event: PatientEvent.Applicable, snapshot: PatientAccount) = - when (event) { + when (event) { // <12> is PatientEvent.Applicable.Created -> { snapshot.name = snapshot.name ?: event.name just(CONTINUE) @@ -85,4 +94,5 @@ class PatientAccountQuery : just(CONTINUE) } } -} \ No newline at end of file +} +//end::documented[] diff --git a/grooves-api/build.gradle b/grooves-api/build.gradle index 13fc450d4..dafd542dc 100644 --- a/grooves-api/build.gradle +++ b/grooves-api/build.gradle @@ -5,8 +5,6 @@ buildscript { } dependencies { classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' - classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.3' - classpath 'org.ajoberstar:gradle-git:1.6.0' classpath 'com.netflix.nebula:gradle-info-plugin:3.+' classpath "gradle.plugin.com.srcclr:gradle:2.2.3" } @@ -15,8 +13,6 @@ buildscript { apply plugin: 'com.bmuschko.nexus' apply from: '../gradle/publishing.gradle' apply plugin: 'groovy' -apply plugin: 'org.asciidoctor.convert' -apply plugin: 'org.ajoberstar.github-pages' apply from: '../gradle/jacoco.gradle' apply plugin: "com.srcclr.gradle" @@ -29,6 +25,7 @@ dependencies { compile project(':grooves-types') testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' + } srcclr { @@ -37,27 +34,4 @@ srcclr { groovydoc { exclude '**/*.java' -} - -asciidoctor { - sources { - include 'index.adoc' - } -} - -githubPages { - repoUri = 'https://github.com/rahulsom/grooves.git' - targetBranch = 'gh-pages' - pages { - from(file("$buildDir/asciidoc/html5")) { - into '.' - } - } - credentials { - username = System.getenv('GH_TOKEN') - password = '\n' - } -} - -tasks.findByName('uploadArchives').dependsOn 'publishGhPages' -tasks.findByName('publishGhPages').dependsOn 'asciidoctor' +} \ No newline at end of file diff --git a/grooves-api/src/docs/asciidoc/index.adoc b/grooves-api/src/docs/asciidoc/index.adoc deleted file mode 100644 index 50d542024..000000000 --- a/grooves-api/src/docs/asciidoc/index.adoc +++ /dev/null @@ -1,28 +0,0 @@ -= Grooves -:stem: -:toc: left -:sectnums: -:nofooter: - -Formerly, Groovy + Event Sourcing. -Now, Event Sourcing for Java. - -include::intro.adoc[] - -include::hurdles.adoc[] - -== How Grooves solves it - -Grooves assumes nothing about your persistence framework or whether you're building a webapp or a batch application. -Grooves only offers some tools to help you build your own event sourced system. - -* Interfaces for the Aggregate, Event (and its special subtypes) and Snapshot. -* A set of interfaces with default methods that help you write queries. -* _If you're using groovy_, annotations to mark these and AST Transformations to ensure all event types are covered in a Query implementation. - -This allows you to use Grooves with a lot of different frameworks. -There are examples in the repository for some options you could use. - -== Building with Grooves - -TODO \ No newline at end of file diff --git a/grooves-docs/build.gradle b/grooves-docs/build.gradle new file mode 100644 index 000000000..e9c3dae9e --- /dev/null +++ b/grooves-docs/build.gradle @@ -0,0 +1,45 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.3' + classpath 'org.ajoberstar:gradle-git:1.6.0' + } +} + +apply plugin: 'org.asciidoctor.convert' +apply plugin: 'org.ajoberstar.github-pages' + +repositories { + maven { url 'https://jitpack.io' } +} + +dependencies { + asciidoctor 'com.github.spring-io:spring-asciidoctor-extensions:v0.1.0.RELEASE' +} + +asciidoctor { + sources { + include 'index.adoc' + } + attributes 'sourcedir': rootDir.absolutePath, + 'version': project.version.toString() +} + +githubPages { + repoUri = 'https://github.com/rahulsom/grooves.git' + targetBranch = 'gh-pages' + pages { + from(file("$buildDir/asciidoc/html5")) { + into "manual/${version}" + } + } + credentials { + username = System.getenv('GH_TOKEN') + password = '\n' + } +} + +tasks.findByName('uploadArchives').dependsOn 'publishGhPages' +tasks.findByName('publishGhPages').dependsOn 'asciidoctor' diff --git a/grooves-docs/src/docs/asciidoc/examples/aggregate.adoc b/grooves-docs/src/docs/asciidoc/examples/aggregate.adoc new file mode 100644 index 000000000..904556f5b --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/aggregate.adoc @@ -0,0 +1,65 @@ +=== Aggregates + +In our examples, to begin with, we will use one aggregate - a patient. +We'll model our system such that there are events that get applied on a Patient's timeline. + +To uniquely identify a patient, we're assuming, all we need is a `uniqueId`. +No name, no address, no other information. + +The rest of the information will be brought in using events. + +[source,java,indent=0,role="primary"] +.Java +---- +include::{sourcedir}/examples/javaee/src/main/java/grooves/example/javaee/domain/Patient.java[tags=documented] +---- +<1> For grooves to use an aggregate, you must implement the `AggregateType` interface. + It accepts a type using generics that identifies the id of the aggregate. + That is typically used as the primary key in a database. +<2> The id type in this case needs to be defined. + Some persistence mechanisms have default types. + If your `id` type matches that, you can skip the declaration. + +[source,groovy,indent=0,role="secondary"] +.Groovy +---- +include::{sourcedir}/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/Patient.groovy[tags=documented] +---- +<1> This annotation will assist in checking for completeness of queries. + There are two other annotations we'll see - `@Event` and `@Query`. + Together, the three will help us ensure that our queries cover all events. + This is optional, but highly recommended. +<2> For grooves to use an aggregate, you must implement the `AggregateType` interface. + It accepts a type using generics that identifies the `id` of the aggregate. + That is typically used as the primary key in a database. +<3> The `id` type in this case needs to be defined. + Some persistence mechanisms have default types. + If your `id` type matches that, you can skip the declaration. + +[source,groovy,indent=0,role="secondary"] +.Gorm +---- +include::{sourcedir}/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/Patient.groovy[tags=documented] +---- +<1> This annotation will assist in checking for completeness of queries. + There are two other annotations we'll see - `@Event` and `@Query`. + Together, the three will help us ensure that our queries cover all events. + This is optional, but highly recommended. +<2> For grooves to use an aggregate, you must implement the `AggregateType` interface. + It accepts a type using generics that identifies the `id` of the aggregate. + That is typically used as the primary key in a database. +<3> This is commented out, because it is implicit in grails. + If it is implicit for your codebase, you do not have to declare it. + +[source,kotlin,indent=0,role="secondary"] +.Kotlin +---- +include::{sourcedir}/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/Patient.kt[tags=documented] +---- +<1> For grooves to use an aggregate, you must implement the `AggregateType` interface. + It accepts a type using generics that identifies the id of the aggregate. + That is typically used as the primary key in a database. +<2> The `id` type in this case needs to be defined. + Some persistence mechanisms have default types. + If your `id` type matches that, you can skip the declaration. + We are using mongo on spring boot here. So we are using `String`. diff --git a/grooves-docs/src/docs/asciidoc/examples/baseEvent.adoc b/grooves-docs/src/docs/asciidoc/examples/baseEvent.adoc new file mode 100644 index 000000000..a10aebb46 --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/baseEvent.adoc @@ -0,0 +1,58 @@ +Let's start with the PatientEvent first. +`PatientEvent` is the base event for all events related to `Patient`. + +[source,java,indent=0,role="primary"] +.Java +---- +// Some imports omitted +include::{sourcedir}/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEvent.java[tags=documented] +---- +<1> You'll have to extend `BaseEvent`. +<2> This is typically transient from a persistence perspective. + Since we're using collections for persistence, it doesn't make much difference. +<3> Timestamp is used for time based filtering and ordering +<4> Position is used for version based filtering and ordering. +<5> The aggregate observable allows for lazy fetching of the aggregate. + Again, since we're using collections, it doesn't matter. + +[source,groovy,indent=0,role="secondary"] +.Groovy +---- +// All imports omitted +include::{sourcedir}/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientEvent.groovy[tags=abstract] +---- +<1> You'll have to extend `BaseEvent`. +<2> This is transient from a persistence perspective. +<3> Timestamp is used for time based filtering and ordering +<4> Position is used for version based filtering and ordering. +<5> The aggregate observable allows for lazy fetching of the aggregate. + In our case, it doesn't matter much since we're using JPA, which is blocking in nature. + +[source,groovy,indent=0,role="secondary"] +.Gorm +---- +// All imports omitted +include::{sourcedir}/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientEvent.groovy[tags=abstract] +---- +<1> You'll have to extend `BaseEvent`. +<2> This is transient from a persistence perspective. Look at <6> +<3> Timestamp is used for time based filtering and ordering +<4> Position is used for version based filtering and ordering. +<5> The aggregate observable allows for lazy fetching of the aggregate. + In our case, it doesn't matter much since we're using Hibernate, which is blocking in nature. + +[source,kotlin,indent=0,role="secondary"] +.Kotlin +---- +// All imports omitted +include::{sourcedir}/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientEvent.kt[tags=patientEvent] +---- +<1> We're using sealed classes in Kotlin. + We'll add more classes inside of it as we progress. + This helps us achieve completeness without the need to customize the compilation. +<2> You'll have to extend `BaseEvent`. +<3> This is transient from a persistence perspective. +<4> Timestamp is used for time based filtering and ordering +<5> Position is used for version based filtering and ordering. +<6> The aggregate observable allows for lazy fetching of the aggregate. + In our case, it doesn't matter much since we're using Hibernate, which is blocking in nature. diff --git a/grooves-docs/src/docs/asciidoc/examples/creationEvent.adoc b/grooves-docs/src/docs/asciidoc/examples/creationEvent.adoc new file mode 100644 index 000000000..724141943 --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/creationEvent.adoc @@ -0,0 +1,50 @@ +Next up, let's look at our first event, `PatientCreated`. + +[source,java,indent=0,role="primary"] +.Java +---- +include::{sourcedir}/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientCreated.java[tags=documented] +---- +<1> For most events you're interested in as a user, you'll just have to extend `PatientEvent`. +<2> The other thing is to implement an audit message. + +[source,groovy,indent=0,role="secondary"] +.Groovy +---- +include::{sourcedir}/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientEvent.groovy[tags=created] +---- +<1> The `@Event` annotation is the second step to achieving completeness of events. + It accepts as a param, the Aggregate over which it applies. +<2> For most events you're interested in as a user, you'll just have to extend `PatientEvent`. +<3> The other thing is to implement an audit message. + +[source,groovy,indent=0,role="secondary"] +.Gorm +---- +include::{sourcedir}/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientEvent.groovy[tags=created] +---- +<1> The `@Event` annotation is the second step to achieving completeness of events. + It accepts as a param, the Aggregate over which it applies. +<2> For most events you're interested in as a user, you'll just have to extend `PatientEvent`. +<3> The other thing is to implement an audit message. + +[source,kotlin,indent=0,role="secondary"] +.Kotlin +---- +sealed class PatientEvent : BaseEvent<...> { + // ... skipping stuff already there ... +include::{sourcedir}/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientEvent.kt[tags=created] +} +---- +<1> First we create a sealed class within the `PatientEvent` called `Applicable`. + What we call it doesn't matter. + All application events will go there. +<2> For most events you're interested in as a user, you'll just have to extend `Applicable`. +<3> The other thing is to implement an audit message. + +In our example, we'll have two more business events. + +* `ProcedurePerformed` that gives you a procedure code and the cost +* `PaymentMade` which gives you the amount paid + +Please look at the source to see how they are implemented. \ No newline at end of file diff --git a/grooves-docs/src/docs/asciidoc/examples/dependencies.adoc b/grooves-docs/src/docs/asciidoc/examples/dependencies.adoc new file mode 100644 index 000000000..c0eb359f1 --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/dependencies.adoc @@ -0,0 +1,79 @@ +=== Dependencies + +The examples here use gradle to manage dependencies. +You could very well be using Maven, or ant+ivy, or leiningen, or sbt. + +[source,groovy,indent=0,role="primary",subs="attributes+"] +.Java +---- +repositories { + // ... other repositories ... + jcenter() // <1> +} +dependencies { + // ... other dependencies ... + compile 'com.github.rahulsom:grooves-api:{version}' // <2> +} +---- +<1> In this example, we're using `jcenter`. You could also be using `mavenCentral`. + As long as your private artifact repository mirrors `mavenCentral`, you can use that as well. +<2> `grooves-api` contains the interfaces for your queries. + It has a dependency on `grooves-types`. + That contains the interfaces for your aggregates, events, and snapshots. + +[source,groovy,indent=0,role="secondary",subs="attributes+"] +.Groovy +---- +repositories { + // ... other repositories ... + jcenter() // <1> +} +dependencies { + // ... other dependencies ... + compile 'com.github.rahulsom:grooves-groovy:{version}' // <2> +} +---- +<1> In this example, we're using `jcenter`. You could also be using `mavenCentral`. + As long as your private artifact repository mirrors `mavenCentral`, you can use that as well. +<2> `grooves-groovy` contains support for writing idiomatic groovy code. + It has a dependency on `grooves-api` which contains interfaces for your queries. + It also has a transitive dependency on `grooves-types`. + That contains the interfaces for your aggregates, events, and snapshots. + +[source,groovy,indent=0,role="secondary",subs="attributes+"] +.Gorm +---- +repositories { + // ... other repositories ... + jcenter() // <1> +} +dependencies { + // ... other dependencies ... + compile 'com.github.rahulsom:grooves-gorm:{version}' // <2> +} +---- +<1> In this example, we're using `jcenter`. You could also be using `mavenCentral`. + As long as your private artifact repository mirrors `mavenCentral`, you can use that as well. +<2> `grooves-gorm` contains support for simplifying access of data using gorm. + It depends on `grooves-groovy`, which contains support for writing idiomatic groovy code. + It has transitive dependencies on `grooves-api`, and `grooves-types`. + `grooves-api` contains interfaces for your queries. + `grooves-types` contains the interfaces for your aggregates, events, and snapshots. + +[source,groovy,indent=0,role="secondary",subs="attributes+"] +.Kotlin +---- +repositories { + // ... other repositories ... + jcenter() // <1> +} +dependencies { + // ... other dependencies ... + compile 'com.github.rahulsom:grooves-api:{version}' // <2> +} +---- +<1> In this example, we're using `jcenter`. You could also be using `mavenCentral`. + As long as your private artifact repository mirrors `mavenCentral`, you can use that as well. +<2> `grooves-api` contains the interfaces for your queries. + It has a dependency on `grooves-types`. + That contains the interfaces for your aggregates, events, and snapshots. \ No newline at end of file diff --git a/grooves-docs/src/docs/asciidoc/examples/events.adoc b/grooves-docs/src/docs/asciidoc/examples/events.adoc new file mode 100644 index 000000000..43599aefe --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/events.adoc @@ -0,0 +1,15 @@ +=== Events + +In our example, we're going to have a bunch of events that will modify the computed state of the `Patient`. + +==== Your Base Event +include::baseEvent.adoc[] + +==== Your business events +include::creationEvent.adoc[] + +==== Standard Grooves Events +There are some Standard Grooves Events - JoinEvent, DisjoinEvent, DeprecatedBy, Deprecates, RevertEvent. +Their implementation is similar to one another, but slightly different from your business events. + +include::revertEvent.adoc[] \ No newline at end of file diff --git a/grooves-docs/src/docs/asciidoc/examples/queries.adoc b/grooves-docs/src/docs/asciidoc/examples/queries.adoc new file mode 100644 index 000000000..4f068249c --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/queries.adoc @@ -0,0 +1,112 @@ +=== Queries + +[source,java,indent=0,role="primary"] +.Java +---- +// You don't need to have an intermediate type to manage your queries, but it does make code reuse better. +include::{sourcedir}/examples/javaee/src/main/java/grooves/example/javaee/queries/CustomQuerySupport.java[tags=documented] +// This is the implementation we need +include::{sourcedir}/examples/javaee/src/main/java/grooves/example/javaee/queries/PatientAccountQuery.java[tags=documented] +---- +<1> This is a placeholder for the snapshot we'll compute. +<2> This is a self reference. +<3> `QuerySupport` allows computing both temporal and versioned snapshots. +<4> The implementation of this method will return a snapshot that's older than the `maxPosition`. +<5> The implementation of this method will return a snapshot that's older than the `maxTimestamp`. +<6> This allows the query to have some control over when to stop applying events on a snapshot. +<7> This gets called on a merge. + The implementation must return all events for given aggregates. +<8> This is an exception handler. + You can decide to `CONTINUE` or `RETURN`. +<9> The implementation of this method must return all events for an aggregate that are more recent than the last event of `lastSnapshot`, but lead up to `version`. + Think of the range as exclusive of `lastSnapshot`, but inclusive of `version`. +<10> The implementation of this method must return all events for an aggregate that are more recent than the last event of `lastSnapshot`, but lead up to `snapshotTime`. + Think of the range as exclusive of `lastSnapshot`, but inclusive of `snapshotTime`. +<11> Here we implement our default interface +<12> This method gets called if no prior snapshot that is usable is found. +<13> We name our methods `apply`. + This is required. + It is not enforced though. +<14> Each of these methods takes the event and the snapshot as inputs and returns an `Observable` as output. + It can `CONTINUE` or `RETURN`. + +[source,groovy,indent=0,role="secondary"] +.Groovy +---- +include::{sourcedir}/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/queries/PatientAccountQuery.groovy[tags=documented] +---- +<1> This annotation is the final step in verification of completeness. + The AST Transformation checks that all `@Event`s associated with the `@Aggregate` are accounted for in the `@Query`. +<2> `QuerySupport` allows computing both temporal and versioned snapshots. +<3> The implementation of this method will return a snapshot that's older than the `maxPosition`. +<4> The implementation of this method will return a snapshot that's older than the `maxTimestamp`. +<5> This allows the query to have some control over when to stop applying events on a snapshot. +<6> This gets called on a merge. + The implementation must return all events for given aggregates. +<7> This is an exception handler. + You can decide to `CONTINUE` or `RETURN`. +<8> The implementation of this method must return all events for an aggregate that are more recent than the last event of `lastSnapshot`, but lead up to `version`. + Think of the range as exclusive of `lastSnapshot`, but inclusive of `version`. +<9> The implementation of this method must return all events for an aggregate that are more recent than the last event of `lastSnapshot`, but lead up to `snapshotTime`. + Think of the range as exclusive of `lastSnapshot`, but inclusive of `snapshotTime`. +<10> This method gets called if no prior snapshot that is usable is found. +<11> We name our methods `apply`. + This is required. + Your IDE might not point out if you don't do it right, but the compiler will. +<12> Each of these methods takes the event and the snapshot as inputs and returns an `Observable` as output. + It can `CONTINUE` or `RETURN`. + +[source,groovy,indent=0,role="secondary"] +.Gorm +---- +// You don't need to have an intermediate type to manage your queries, but it does make code reuse better. +// Also this is written in java. +include::{sourcedir}/grooves-gorm/src/main/groovy/com/github/rahulsom/grooves/grails/GormQuerySupport.java[tags=documented] +// This is the implementation we need here +include::{sourcedir}/examples/grails/rdbms_mongo/src/main/groovy/grooves/grails/mongo/PatientAccountQuery.groovy[tags=documented] +---- +<1> `QuerySupport` allows computing both temporal and versioned snapshots. +<2> `BlockingEventSource` needs an `eventClass`. + `BlockingEventSource` and `RxEventSource` greatly simplify working with events from a gorm source. +<3> `BlockingSnapshotSource` needs a `snapshotClass`. + `BlockingSnapshotSource` and `RxSnapshotSource` do the same for snapshots. +<4> This is an example of using the `BlockingEventSource`. +<5> This annotation is the final step in verification of completeness. + The AST Transformation checks that all `@Event`s associated with the `@Aggregate` are accounted for in the `@Query`. +<6> Here we'll implement the interface we just wrote. +<7> This configures the event class. +<8> This configures the snapshot class. +<9> This method gets called if no prior snapshot that is usable is found. +<10> This allows the query to have some control over when to stop applying events on a snapshot. +<11> This is an exception handler. + You can decide to `CONTINUE` or `RETURN`. +<12> We name our methods `apply`. + This is required. + Your IDE might not point out if you don't do it right, but the compiler will. +<13> Each of these methods takes the event and the snapshot as inputs and returns an `Observable` as output. + It can `CONTINUE` or `RETURN`. + +[source,kotlin,indent=0,role="secondary"] +.Kotlin +---- +include::{sourcedir}/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/queries/PatientAccountQuery.kt[tags=documented] +---- +<1> `QuerySupport` allows computing both temporal and versioned snapshots. +<2> `SimpleQuery` makes working with Kotlin better. + It calls the `applyEvent` method instead of calling `apply`. + With Kotlin `applyEvent` is sufficient, because you can use sealed classes to achieve completeness. +<3> `SimpleExecutor` is what wires up `SimpleQuery` correctly. +<4> This method gets called if no prior snapshot that is usable is found. +<5> The implementation of this method will return a snapshot that's older than the `maxPosition`. +<6> The implementation of this method will return a snapshot that's older than the `maxTimestamp`. +<7> This allows the query to have some control over when to stop applying events on a snapshot. +<8> This gets called on a merge. + The implementation must return all events for given aggregates. +<9> This is an exception handler. + You can decide to `CONTINUE` or `RETURN`. +<10> The implementation of this method must return all events for an aggregate that are more recent than the last event of `lastSnapshot`, but lead up to `version`. + Think of the range as exclusive of `lastSnapshot`, but inclusive of `version`. +<11> The implementation of this method must return all events for an aggregate that are more recent than the last event of `lastSnapshot`, but lead up to `snapshotTime`. + Think of the range as exclusive of `lastSnapshot`, but inclusive of `snapshotTime`. +<12> `applyEvent` handles all events under `Applicable`. + Kotlin will check for completeness when you use the `when` keyword as part of an `expression`. diff --git a/grooves-docs/src/docs/asciidoc/examples/revertEvent.adoc b/grooves-docs/src/docs/asciidoc/examples/revertEvent.adoc new file mode 100644 index 000000000..0aedaaea9 --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/revertEvent.adoc @@ -0,0 +1,44 @@ +We are going to walk through the `RevertEvent` over here. + +[source,java,indent=0,role="primary"] +.Java +---- +include::{sourcedir}/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientEventReverted.java[tags=documented] +---- +<1> Like your business events, you'll extend `PatientEvent`. +<2> In addition, you'll implement the `RevertEvent` interface. +<3> And you'll have to give it a property `revertedEventId`. + +[source,groovy,indent=0,role="secondary"] +.Groovy +---- +include::{sourcedir}/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientEvent.groovy[tags=reverted] +---- +<1> Like your business events, you'll extend `PatientEvent`. + You do not need to annotate it as an Event. + Even if you do, you cannot process this event. +<2> In addition, you'll implement the `RevertEvent` interface. +<3> And you'll have to give it a property `revertedEventId`. + +[source,groovy,indent=0,role="secondary"] +.Gorm +---- +include::{sourcedir}/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientEvent.groovy[tags=created] +---- +<1> Like your business events, you'll extend `PatientEvent`. + You do not need to annotate it as an Event. + Even if you do, you cannot process this event. +<2> In addition, you'll implement the `RevertEvent` interface. +<3> And you'll have to give it a property `revertedEventId`. + +[source,kotlin,indent=0,role="secondary"] +.Kotlin +---- +sealed class PatientEvent : BaseEvent<...> { + // ... skipping stuff already there ... +include::{sourcedir}/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientEvent.kt[tags=reverted] +} +---- +<1> You'll have to give it a property `revertedEventId`. +<2> Like your `Applicable` in business events, you'll extend `PatientEvent`. +<3> In addition, you'll implement the `RevertEvent` interface. diff --git a/grooves-docs/src/docs/asciidoc/examples/snapshot.adoc b/grooves-docs/src/docs/asciidoc/examples/snapshot.adoc new file mode 100644 index 000000000..a37eb15da --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/examples/snapshot.adoc @@ -0,0 +1,59 @@ +=== Snapshots + +Let's start with a snapshot that manages account balance for a patient. + +[source,java,indent=0,role="primary"] +.Java +---- +include::{sourcedir}/examples/javaee/src/main/java/grooves/example/javaee/domain/PatientAccount.java[tags=documented] +---- +<1> For most snapshots, you may implement `JavaSnapshot`. +<2> The `lastEventPosition` is useful when locating snapshots by version. +<3> The `lastEventTimestamp` is useful when locating snapshots by timestamp. +<4> The `aggregateObservable` getter points to the aggregate from the snapshot. +<5> The `deprecatedByObservable` points to the deprecator of the snapshot's aggregate. + It is empty when the aggregate is not deprecated. +<6> The `deprecatesObservable` points to the aggregates deprecated by snapshot's aggregate. + +[source,groovy,indent=0,role="secondary"] +.Groovy +---- +include::{sourcedir}/examples/springboot/jpa/src/main/groovy/grooves/boot/jpa/domain/PatientAccount.groovy[tags=documented] +---- +<1> For most snapshots, you may implement `JavaSnapshot`. +<2> The `lastEventPosition` is useful when locating snapshots by version. +<3> The `lastEventTimestamp` is useful when locating snapshots by timestamp. +<4> The `aggregateObservable` getter points to the aggregate from the snapshot. +<5> The `deprecatedByObservable` points to the deprecator of the snapshot's aggregate. + It is empty when the aggregate is not deprecated. +<6> The `deprecatesObservable` points to the aggregates deprecated by snapshot's aggregate. + +[source,groovy,indent=0,role="secondary"] +.Gorm +---- +include::{sourcedir}/examples/grails/rdbms_mongo/grails-app/domain/grooves/grails/mongo/PatientAccount.groovy[tags=documented] +---- +<1> For most snapshots, you may implement `JavaSnapshot`. +<2> The `lastEventPosition` is useful when locating snapshots by version. +<3> The `lastEventTimestamp` is useful when locating snapshots by timestamp. +<4> The `aggregateObservable` getter points to the aggregate from the snapshot. +<5> The `deprecatedByObservable` points to the deprecator of the snapshot's aggregate. + It is empty when the aggregate is not deprecated. +<6> The `deprecatesObservable` points to the aggregates deprecated by snapshot's aggregate. + +[source,kotlin,indent=0,role="secondary"] +.Kotlin +---- +include::{sourcedir}/examples/springboot/kotlin/src/main/kotlin/grooves/boot/kotlin/domain/PatientAccount.kt[tags=documented] +---- +<1> For most snapshots, you may implement `Snapshot`. +<2> The `lastEventPosition` is useful when locating snapshots by version. +<3> The `lastEventTimestamp` is useful when locating snapshots by timestamp. +<4> The `aggregateObservable` getter points to the aggregate from the snapshot. +<5> The `deprecatedByObservable` points to the deprecator of the snapshot's aggregate. + It is empty when the aggregate is not deprecated. +<6> The `deprecatesObservable` points to the aggregates deprecated by snapshot's aggregate. + +The reason for `aggregateObservable`, `deprecatedByObservable` and `deprecatesObservable` being `rx.Observable` is to allow asynchronous data access layers to work well. + +In our examples, we have one more snapshot - `PatientHealth` which lists codes for procedures and the dates they were performed. diff --git a/grooves-api/src/docs/asciidoc/hurdles.adoc b/grooves-docs/src/docs/asciidoc/hurdles.adoc similarity index 98% rename from grooves-api/src/docs/asciidoc/hurdles.adoc rename to grooves-docs/src/docs/asciidoc/hurdles.adoc index b80334c6c..fae6f0de1 100644 --- a/grooves-api/src/docs/asciidoc/hurdles.adoc +++ b/grooves-docs/src/docs/asciidoc/hurdles.adoc @@ -9,7 +9,7 @@ That is the problem that Grooves aims to solve. When applying events on a Snapshot, you need to be sure that you have handled every single event type. Let's say you've got 3 types of events for an aggregate and 2 snapshots. If you add a new event type, you need to handle it in the computation of both snapshots. -When a language has case classes, it's easier to achieve this. +When a language has case classes, (sealed classes in Kotlin), it's easier to achieve this. Alas, Groovy and Java do not have case classes. There are some special kinds of Events that require a lot of complex handling: diff --git a/grooves-docs/src/docs/asciidoc/index.adoc b/grooves-docs/src/docs/asciidoc/index.adoc new file mode 100644 index 000000000..0eb221ee9 --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/index.adoc @@ -0,0 +1,76 @@ += Grooves +:stem: +:toc: left +:sectnums: +:nofooter: +:source-highlighter: coderay +:icons: font +:linkcss: + +++++ + +++++ + +Formerly, _Groovy + Event Sourcing_. +Now, _Grooves_ is just a name. +It supports Event Sourcing on many JVM languages. +Examples and tests are included in java, kotlin, and of course, groovy. + +include::intro.adoc[] + +include::hurdles.adoc[] + +== How Grooves solves it + +Grooves assumes nothing about your persistence framework or whether you're building a webapp or a batch application. +Grooves only offers some tools to help you build your own event sourced system. + +* Interfaces for the Aggregate, Event (and its special subtypes) and Snapshot. +* A set of interfaces with default methods that help you write queries. +* _If you're using groovy_, annotations to mark these and AST Transformations to ensure all event types are covered in a Query implementation. + +This allows you to use Grooves with a lot of different frameworks. +There are examples in the repository for some options you could use. + +== Event Sourcing with Grooves + +This section contains example code with annotations that indicate what is being done, and why. +We will be using 4 different kinds of projects. + +* Java - A java project that uses a few in memory collections as it's storage mechanism. + It is written in Java 8, and uses JavaEE. + In the case of Java code, we'll be skipping getters and setters to keep examples concise. + For the code to actually work, those would be required. +* Groovy - A project that uses SpringBoot and JPA for persistence. + It's written using Groovy. +* Gorm - It's a grails project that uses Hibernate for persisting events and aggregates. + It uses Mongo for persisting snapshots. + This is the most thorough example in our codebase. + It is the only one that demonstrates joins. +* Kotlin - This is a SpringBoot Reactive Web project that uses RxMongo for persistence. + +include::examples/dependencies.adoc[] + +include::examples/aggregate.adoc[] + +include::examples/events.adoc[] + +include::examples/snapshot.adoc[] + +include::examples/queries.adoc[] + +include::support.adoc[] diff --git a/grooves-api/src/docs/asciidoc/intro.adoc b/grooves-docs/src/docs/asciidoc/intro.adoc similarity index 99% rename from grooves-api/src/docs/asciidoc/intro.adoc rename to grooves-docs/src/docs/asciidoc/intro.adoc index 5ca3072d0..05aa4d722 100644 --- a/grooves-api/src/docs/asciidoc/intro.adoc +++ b/grooves-docs/src/docs/asciidoc/intro.adoc @@ -114,4 +114,3 @@ When the time of two events is exactly the same, then they are displayed like th + 4 2016-01-04 performed ANNUALPHYSICAL for $ 140.23 + 5 2016-01-05 paid $ 100.25 .... - diff --git a/grooves-docs/src/docs/asciidoc/support.adoc b/grooves-docs/src/docs/asciidoc/support.adoc new file mode 100644 index 000000000..0a35aa966 --- /dev/null +++ b/grooves-docs/src/docs/asciidoc/support.adoc @@ -0,0 +1,7 @@ +== Support + +If you're having trouble using this, head over to https://github.com/rahulsom/grooves/issues[Github Issues] and raise a question. +Our aim is to document things here rather than answering individual questions on the issue tracker. + +If you run into a bug, feel free to create a Pull Request complete with tests, or open an issue with a detailed test description. +If you feel that the design can be enhanced, please open a ticket and start a discussion before spending time on that. diff --git a/grooves-gorm/src/main/groovy/com/github/rahulsom/grooves/grails/GormQuerySupport.java b/grooves-gorm/src/main/groovy/com/github/rahulsom/grooves/grails/GormQuerySupport.java index 0a956ea4a..c353d5f0e 100644 --- a/grooves-gorm/src/main/groovy/com/github/rahulsom/grooves/grails/GormQuerySupport.java +++ b/grooves-gorm/src/main/groovy/com/github/rahulsom/grooves/grails/GormQuerySupport.java @@ -26,6 +26,7 @@ * @deprecated Use {@link BlockingEventSource} and {@link BlockingSnapshotSource} instead */ @Deprecated +// tag::documented[] public interface GormQuerySupport< AggregateIdT, AggregateT extends AggregateType & GormEntity, @@ -38,42 +39,47 @@ public interface GormQuerySupport< QueryT extends BaseQuery > extends QuerySupport, + SnapshotT, QueryT>, //<1> BlockingEventSource, BlockingSnapshotSource { + Class getEventClass(); // <2> + + Class getSnapshotClass(); // <3> + @Override default Observable getSnapshot(long maxPosition, AggregateT aggregate) { - return BlockingSnapshotSource.super.getSnapshot(maxPosition, aggregate); + return BlockingSnapshotSource.super + .getSnapshot(maxPosition, aggregate); // <4> } @Override default Observable getSnapshot(Date maxTimestamp, AggregateT aggregate) { - return BlockingSnapshotSource.super.getSnapshot(maxTimestamp, aggregate); + return BlockingSnapshotSource.super + .getSnapshot(maxTimestamp, aggregate); } @Override default Observable getUncomputedEvents( AggregateT aggregate, SnapshotT lastSnapshot, long version) { - return BlockingEventSource.super.getUncomputedEvents(aggregate, lastSnapshot, version); + return BlockingEventSource.super + .getUncomputedEvents(aggregate, lastSnapshot, version); } @Override default Observable getUncomputedEvents( AggregateT aggregate, SnapshotT lastSnapshot, Date snapshotTime) { - return BlockingEventSource.super.getUncomputedEvents(aggregate, lastSnapshot, - snapshotTime); + return BlockingEventSource.super + .getUncomputedEvents(aggregate, lastSnapshot, snapshotTime); } @Override default Observable findEventsForAggregates(List aggregates) { - return BlockingEventSource.super.findEventsForAggregates(aggregates); + return BlockingEventSource.super + .findEventsForAggregates(aggregates); } - Class getEventClass(); - - Class getSnapshotClass(); - } +// end::documented[] diff --git a/grooves-types/build.gradle b/grooves-types/build.gradle index d51584a8f..9376979b9 100644 --- a/grooves-types/build.gradle +++ b/grooves-types/build.gradle @@ -1,5 +1,7 @@ buildscript { - ext.kotlin_version = '1.1.2' + ext { + kotlinVersion = '1.1.3' + } repositories { jcenter() maven { url "https://plugins.gradle.org/m2/" } @@ -7,7 +9,7 @@ buildscript { dependencies { classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' classpath 'com.netflix.nebula:gradle-info-plugin:3.+' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:1.0.3" classpath "gradle.plugin.com.srcclr:gradle:2.2.3" } @@ -23,8 +25,8 @@ apply plugin: "com.srcclr.gradle" dependencies { compileOnly 'org.codehaus.groovy:groovy:2.4.8' - compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" + compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlinVersion" compile 'org.slf4j:slf4j-api:1.7.22' compile 'io.reactivex:rxjava:1.2.9' diff --git a/settings.gradle b/settings.gradle index ad833782f..4b9ba1d72 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ rootProject.name = 'grooves' -def projects = ['api', 'groovy', 'gorm', 'types'] +def projects = ['api', 'groovy', 'gorm', 'types', 'docs'] projects.each { include "grooves-${it}" } include 'grooves-example-test'