├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml ├── settings.gradle └── src ├── main ├── java │ └── example │ │ ├── bootstrap │ │ ├── BootstrapEvent.java │ │ └── BootstrapListener.java │ │ ├── ddd │ │ ├── AggregateRoot.java │ │ ├── ApplicationService.java │ │ ├── Event.java │ │ ├── EventHandler.java │ │ ├── EventPublisher.java │ │ ├── Repository.java │ │ └── ValueObject.java │ │ └── scrumboard │ │ ├── application │ │ ├── ScrumBoardApplicationConfig.java │ │ ├── api │ │ │ ├── ProductService.java │ │ │ ├── ReleaseService.java │ │ │ ├── SprintService.java │ │ │ ├── TaskService.java │ │ │ └── commands │ │ │ │ ├── CreateProductCommand.groovy │ │ │ │ ├── CreateTaskCommand.groovy │ │ │ │ ├── PlanBacklogItemCommand.groovy │ │ │ │ ├── ReorderBacklogItemsCommand.groovy │ │ │ │ ├── ScheduleReleaseCommand.groovy │ │ │ │ └── ScheduleSprintCommand.groovy │ │ ├── bootstrap │ │ │ └── ScrumBoardBootstrap.groovy │ │ ├── handlers │ │ │ └── SampleEventHandler.java │ │ ├── impl │ │ │ ├── BacklogItemServiceImpl.java │ │ │ ├── ProductServiceImpl.java │ │ │ ├── ReleaseServiceImpl.java │ │ │ ├── SprintServiceImpl.java │ │ │ └── TaskServiceImpl.java │ │ ├── system │ │ │ ├── DateProvider.java │ │ │ └── UserProvider.java │ │ └── tasks │ │ │ └── SampleTask.java │ │ ├── config │ │ ├── ScrumBoardConfig.java │ │ └── ScrumBoardInitializer.java │ │ ├── domain │ │ ├── ScrumBoardDomainConfig.java │ │ ├── backlogitem │ │ │ ├── BacklogItem.java │ │ │ ├── BacklogItemCommited.groovy │ │ │ ├── BacklogItemFactory.java │ │ │ ├── BacklogItemId.java │ │ │ ├── BacklogItemRepository.java │ │ │ ├── BacklogItemUncommited.groovy │ │ │ ├── Priority.java │ │ │ ├── StoryPoints.java │ │ │ └── task │ │ │ │ ├── DoneTaskState.java │ │ │ │ ├── IllegalTaskStateException.java │ │ │ │ ├── InProgressTaskState.java │ │ │ │ ├── RemainingAmendment.java │ │ │ │ ├── Task.java │ │ │ │ ├── TaskFactory.java │ │ │ │ ├── TaskId.java │ │ │ │ ├── TaskRepository.java │ │ │ │ ├── TaskState.java │ │ │ │ ├── TaskStateAdapter.java │ │ │ │ ├── TaskStatus.java │ │ │ │ └── TodoTaskState.java │ │ ├── product │ │ │ ├── BacklogItemPlannedEvent.groovy │ │ │ ├── Product.java │ │ │ ├── ProductBacklogItem.java │ │ │ ├── ProductFactory.java │ │ │ ├── ProductId.java │ │ │ └── ProductRepository.java │ │ ├── release │ │ │ ├── Release.java │ │ │ ├── ReleaseFactory.java │ │ │ ├── ReleaseId.java │ │ │ ├── ReleaseRepository.java │ │ │ └── ScheduledBacklogItem.java │ │ └── sprint │ │ │ ├── CommitedBacklogItem.java │ │ │ ├── Sprint.java │ │ │ ├── SprintFactory.java │ │ │ ├── SprintId.java │ │ │ └── SprintRepository.java │ │ ├── infrastructure │ │ ├── bootstrap │ │ │ ├── BootstrapEventPublisher.java │ │ │ └── ScrumBoardInfrastructureBootstrapConfig.java │ │ ├── events │ │ │ ├── ScrumBoardInfrastructureEventsConfig.java │ │ │ └── SpringIntegrationEventPublisher.java │ │ ├── jpa │ │ │ ├── ScrumBoardInfrastructureJpaConfig.java │ │ │ ├── hibernate │ │ │ │ └── FixedPrefixNamingStrategy.java │ │ │ ├── repositories │ │ │ │ ├── GenericJpaRepository.java │ │ │ │ ├── JpaBacklogItemRepository.java │ │ │ │ ├── JpaProductRepository.java │ │ │ │ ├── JpaReleaseRepository.java │ │ │ │ ├── JpaRepository.java │ │ │ │ ├── JpaSprintRepository.java │ │ │ │ └── JpaTaskRepository.java │ │ │ └── spring │ │ │ │ ├── EventsPublishingAspect.java │ │ │ │ └── RepositoryAutowiringAspect.java │ │ ├── rest │ │ │ ├── ProductIdConverter.java │ │ │ └── ScrumBoardInfrastructureRestConfig.java │ │ └── shared │ │ │ ├── ScrumBoardInfrastructureAsyncConfig.java │ │ │ ├── ScrumBoardInfrastructureContextConfig.java │ │ │ ├── ScrumBoardInfrastructureTaskConfig.java │ │ │ └── ScrumBoardInfrastructureTransactionConfig.java │ │ └── rest │ │ ├── commands │ │ ├── ScrumBoardRestCommandsConfig.java │ │ └── product │ │ │ └── ProductCommandController.java │ │ └── queries │ │ ├── ScrumBoardRestQueriesConfig.java │ │ └── product │ │ ├── ProductQueryController.groovy │ │ └── dtos │ │ ├── ProductBacklogItemDto.groovy │ │ └── ProductDto.groovy ├── resources │ ├── local.properties │ ├── logback.xml │ └── remote.properties └── webapp │ └── index.html └── test ├── java └── example │ ├── ddd │ └── domain │ │ ├── AggregateRootAssert.java │ │ └── DddAssertions.java │ └── scrumboard │ ├── TestGroups.java │ ├── domain │ ├── ScrumBoardBuilders.java │ ├── backlogitem │ │ ├── BacklogItemAssert.java │ │ ├── BacklogItemBuilder.java │ │ └── BacklogItemTest.java │ ├── product │ │ ├── ProductAssert.java │ │ ├── ProductBuilder.java │ │ └── ProductTest.java │ └── sprint │ │ └── SprintBuilder.java │ ├── infrastructure │ └── jpa │ │ ├── JpaTest.java │ │ ├── JpaTestConfig.java │ │ └── repositories │ │ ├── AbstractJpaRepositoryTest.java │ │ └── JpaProductRepositoryTest.java │ └── rest │ ├── AbstractControllerTest.java │ ├── commands │ ├── RestCommandTest.java │ ├── RestCommandTestConfig.java │ └── product │ │ └── ProductCommandControllerTest.java │ └── queries │ ├── RestQueryTest.java │ ├── RestQueryTestConfig.java │ └── product │ └── ProductQueryControllerTest.java └── resources ├── logback-test.xml ├── sample.sql └── test.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # Maven 2 | /target 3 | 4 | # Gradle 5 | .gradle 6 | /build 7 | 8 | # TestNG 9 | /test-output 10 | 11 | # STS 12 | .classpath 13 | .groovy 14 | .project 15 | .settings 16 | .springBeans 17 | 18 | # IntelliJ 19 | .idea 20 | *.iml 21 | *.iws 22 | *.ipr 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | - java 3 | 4 | jdk: 5 | - openjdk7 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | before_install: true 12 | install: true 13 | 14 | before_script: true 15 | script: mvn --quiet test 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/mkuthan/example-ddd-cqrs-server.png)](https://travis-ci.org/mkuthan/example-ddd-cqrs-server) 2 | 3 | [Presentation](https://docs.google.com/presentation/d/1PlKF4OW5ARqUbqSUwL4D1syEwxw-PmX4KLJObPeYQyI/pub?start=false&loop=false&delayms=3000) 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'idea' 3 | apply plugin: 'jetty' 4 | apply plugin: 'war' 5 | 6 | repositories { 7 | mavenCentral() 8 | maven { url "http://maven.springframework.org/milestone/" } 9 | } 10 | 11 | sourceSets { 12 | main { 13 | java { 14 | srcDirs = [] 15 | } 16 | groovy { 17 | srcDirs = ['src/java'] 18 | } 19 | } 20 | 21 | test { 22 | java { 23 | srcDirs = [] 24 | } 25 | groovy { 26 | srcDirs = ['test/java'] 27 | } 28 | } 29 | } 30 | 31 | idea { 32 | project { 33 | jdkName = '1.7' 34 | languageLevel = '1.7' 35 | } 36 | } 37 | 38 | ext { 39 | springVersion = "4.0.3.RELEASE" 40 | } 41 | 42 | configurations.all { 43 | exclude group: 'commons-logging' 44 | } 45 | 46 | dependencies { 47 | providedCompile 'javax.servlet:javax.servlet-api:3.0.1' 48 | 49 | compile "org.codehaus.groovy:groovy-all:2.1.8" 50 | 51 | compile "org.springframework:spring-aop:$springVersion" 52 | compile "org.springframework:spring-aspects:$springVersion" 53 | compile "org.springframework:spring-context:$springVersion" 54 | compile "org.springframework:spring-context-support:$springVersion" 55 | compile "org.springframework:spring-orm:$springVersion" 56 | testCompile "org.springframework:spring-test:$springVersion" 57 | compile "org.springframework:spring-web:$springVersion" 58 | compile "org.springframework:spring-webmvc:$springVersion" 59 | compile 'org.springframework.data:spring-data-commons:1.7.1.RELEASE' 60 | compile "org.springframework.integration:spring-integration-core:4.0.0.RC1" 61 | 62 | compile "org.hibernate:hibernate-entitymanager:4.3.5.Final" 63 | 64 | compile 'com.h2database:h2:1.4.177' 65 | 66 | compile 'org.slf4j:slf4j-api:1.7.7' 67 | runtime 'org.slf4j:jcl-over-slf4j:1.7.7' 68 | runtime 'org.slf4j:jul-to-slf4j:1.7.7' 69 | runtime 'ch.qos.logback:logback-classic:1.1.2' 70 | 71 | 72 | compile 'org.apache.commons:commons-lang3:3.3.2' 73 | compile 'com.google.guava:guava:16.0.1' 74 | compile 'joda-time:joda-time:2.3' 75 | compile 'com.fasterxml.jackson.core:jackson-databind:2.3.3' 76 | 77 | testCompile("org.testng:testng:6.8.8") { 78 | exclude(module: 'junit') 79 | exclude(module: 'bsh') 80 | exclude(module: 'snakeyaml') 81 | } 82 | 83 | testCompile "org.mockito:mockito-core:1.9.5" 84 | testCompile "org.assertj:assertj-core:1.6.0" 85 | testCompile "com.googlecode.catch-exception:catch-exception:1.2.0" 86 | testCompile "com.jayway.jsonpath:json-path:0.9.1" 87 | } 88 | 89 | task wrapper(type: Wrapper) { 90 | gradleVersion = '1.11' 91 | } 92 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkuthan/example-ddd-cqrs-server/3f4b0136bf816bd5d358c6cc3dfd9501fac78084/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 23 20:52:25 CEST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | example 5 | example-ddd-cqrs-server 6 | 1.0-SNAPSHOT 7 | war 8 | 9 | 10 | UTF-8 11 | 12 | 1.7 13 | 1.7 14 | 15 | false 16 | 17 | 3.3.2 18 | 16.0.1 19 | 1.4.177 20 | 4.3.5.Final 21 | 2.3.3 22 | 2.3 23 | 1.1.2 24 | 1.7.7 25 | 4.0.3.RELEASE 26 | 4.0.0.RC1 27 | 1.7.1.RELEASE 28 | 29 | 2.1.8 30 | 2.8.0-01 31 | 2.1.8-01 32 | 33 | 1.6.0 34 | 1.2.0 35 | 0.9.1 36 | 1.9.5 37 | 6.8.8 38 | 39 | 40 | 41 | 42 | spring-milestone 43 | http://maven.springframework.org/milestone/ 44 | 45 | 46 | 47 | 48 | 3.0.5 49 | 50 | 51 | 52 | 53 | 54 | 55 | maven-compiler-plugin 56 | 3.1 57 | 58 | groovy-eclipse-compiler 59 | 60 | 61 | 62 | org.codehaus.groovy 63 | groovy-eclipse-compiler 64 | ${groovy-eclipse-compiler.version} 65 | 66 | 67 | org.codehaus.groovy 68 | groovy-eclipse-batch 69 | ${groovy-eclipse-batch.version} 70 | 71 | 72 | 73 | 74 | 75 | maven-surefire-plugin 76 | 2.17 77 | 78 | 79 | default-test 80 | test 81 | 82 | test 83 | 84 | 85 | unit 86 | -Xmx64m 87 | classes 88 | 89 | 90 | 91 | integration-test 92 | test 93 | 94 | test 95 | 96 | 97 | integration 98 | -Xmx256m 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.zeroturnaround 106 | jrebel-maven-plugin 107 | 1.1.5 108 | 109 | 110 | generate-rebel-xml 111 | process-resources 112 | 113 | generate 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.apache.tomcat.maven 121 | tomcat7-maven-plugin 122 | 2.2 123 | 124 | 125 | org.apache.juli.ClassLoaderLogManager 126 | JNDI 127 | local 128 | 129 | 130 | 131 | 132 | 133 | org.slf4j 134 | jcl-over-slf4j 135 | ${slf4j.version} 136 | 137 | 138 | org.slf4j 139 | slf4j-api 140 | ${slf4j.version} 141 | 142 | 143 | org.slf4j 144 | jul-to-slf4j 145 | ${slf4j.version} 146 | 147 | 148 | ch.qos.logback 149 | logback-classic 150 | ${logback.version} 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | org.zeroturnaround 160 | jrebel-maven-plugin 161 | 162 | 163 | 164 | org.apache.tomcat.maven 165 | tomcat7-maven-plugin 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | org.springframework 174 | spring-framework-bom 175 | ${spring.version} 176 | pom 177 | import 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | javax.servlet 186 | javax.servlet-api 187 | 3.0.1 188 | provided 189 | 190 | 191 | 192 | 193 | org.springframework 194 | spring-aop 195 | 196 | 197 | org.springframework 198 | spring-aspects 199 | 200 | 201 | org.springframework 202 | spring-context 203 | 204 | 205 | org.springframework 206 | spring-context-support 207 | 208 | 209 | org.springframework 210 | spring-core 211 | 212 | 213 | commons-logging 214 | commons-logging 215 | 216 | 217 | 218 | 219 | org.springframework 220 | spring-jms 221 | 222 | 223 | org.springframework 224 | spring-orm 225 | 226 | 227 | org.springframework 228 | spring-web 229 | 230 | 231 | org.springframework 232 | spring-webmvc 233 | 234 | 235 | 236 | 237 | org.springframework.integration 238 | spring-integration-core 239 | ${spring-integration.version} 240 | 241 | 242 | 243 | 244 | org.springframework.data 245 | spring-data-commons 246 | ${spring-data-commons.version} 247 | 248 | 249 | 250 | 251 | org.hibernate 252 | hibernate-entitymanager 253 | ${hibernate.version} 254 | 255 | 256 | xml-apis 257 | xml-apis 258 | 259 | 260 | 261 | 262 | 263 | 264 | org.codehaus.groovy 265 | groovy-all 266 | ${groovy.version} 267 | 268 | 269 | 270 | 271 | org.slf4j 272 | slf4j-api 273 | ${slf4j.version} 274 | 275 | 276 | 277 | org.slf4j 278 | jcl-over-slf4j 279 | ${slf4j.version} 280 | runtime 281 | 282 | 283 | 284 | org.slf4j 285 | jul-to-slf4j 286 | ${slf4j.version} 287 | runtime 288 | 289 | 290 | 291 | ch.qos.logback 292 | logback-classic 293 | ${logback.version} 294 | runtime 295 | 296 | 297 | 298 | 299 | org.apache.commons 300 | commons-lang3 301 | ${commons-lang.version} 302 | 303 | 304 | 305 | com.google.guava 306 | guava 307 | ${guava.version} 308 | 309 | 310 | 311 | joda-time 312 | joda-time 313 | ${jodatime.version} 314 | 315 | 316 | 317 | com.fasterxml.jackson.core 318 | jackson-databind 319 | ${jackson.version} 320 | 321 | 322 | 323 | 324 | com.h2database 325 | h2 326 | ${h2.version} 327 | 328 | 329 | 330 | 331 | org.springframework 332 | spring-test 333 | test 334 | 335 | 336 | 337 | org.testng 338 | testng 339 | ${testng.version} 340 | test 341 | 342 | 343 | junit 344 | junit 345 | 346 | 347 | bsh 348 | org.beanshell 349 | 350 | 351 | snakeyaml 352 | org.yaml 353 | 354 | 355 | 356 | 357 | 358 | org.mockito 359 | mockito-core 360 | ${mockito.version} 361 | test 362 | 363 | 364 | 365 | org.assertj 366 | assertj-core 367 | ${assertj.version} 368 | test 369 | 370 | 371 | 372 | com.googlecode.catch-exception 373 | catch-exception 374 | ${catchexception.version} 375 | test 376 | 377 | 378 | 379 | com.jayway.jsonpath 380 | json-path 381 | ${jsonpath.version} 382 | test 383 | 384 | 385 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'example-ddd-cqrs-server' 2 | -------------------------------------------------------------------------------- /src/main/java/example/bootstrap/BootstrapEvent.java: -------------------------------------------------------------------------------- 1 | package example.bootstrap; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.event.ApplicationContextEvent; 5 | 6 | public class BootstrapEvent extends ApplicationContextEvent { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | public BootstrapEvent(ApplicationContext source) { 11 | super(source); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/bootstrap/BootstrapListener.java: -------------------------------------------------------------------------------- 1 | package example.bootstrap; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | @Target({ ElementType.TYPE, ElementType.METHOD }) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface BootstrapListener { 14 | } -------------------------------------------------------------------------------- /src/main/java/example/ddd/AggregateRoot.java: -------------------------------------------------------------------------------- 1 | package example.ddd; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Objects; 8 | 9 | import javax.persistence.Embedded; 10 | import javax.persistence.Id; 11 | import javax.persistence.MappedSuperclass; 12 | import javax.persistence.Transient; 13 | import javax.persistence.Version; 14 | 15 | @MappedSuperclass 16 | public abstract class AggregateRoot { 17 | 18 | @Id 19 | @Embedded 20 | private ID id; 21 | 22 | @Version 23 | private Integer version; 24 | 25 | @Transient 26 | private List pendingEvents = new ArrayList<>(); 27 | 28 | protected AggregateRoot() { 29 | } 30 | 31 | protected AggregateRoot(ID id) { 32 | this.id = requireNonNull(id); 33 | this.version = 0; 34 | } 35 | 36 | public ID getId() { 37 | return id; 38 | } 39 | 40 | public Integer getVersion() { 41 | return version; 42 | } 43 | 44 | public Iterable getPendingEvents() { 45 | return pendingEvents; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(id); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object obj) { 55 | if (this == obj) { 56 | return true; 57 | } 58 | 59 | if (obj == null) { 60 | return false; 61 | } 62 | 63 | if (getClass() != obj.getClass()) { 64 | return false; 65 | } 66 | 67 | @SuppressWarnings("unchecked") 68 | AggregateRoot that = (AggregateRoot) obj; 69 | 70 | return Objects.equals(this.id, that.id); 71 | } 72 | 73 | protected void register(Event event) { 74 | requireNonNull(event); 75 | pendingEvents.add(event); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/example/ddd/ApplicationService.java: -------------------------------------------------------------------------------- 1 | package example.ddd; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.transaction.annotation.Propagation; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | @Component 13 | @Transactional(readOnly = false, propagation = Propagation.REQUIRED) 14 | @Target({ ElementType.TYPE, ElementType.METHOD }) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface ApplicationService { 17 | } -------------------------------------------------------------------------------- /src/main/java/example/ddd/Event.java: -------------------------------------------------------------------------------- 1 | package example.ddd; 2 | 3 | import java.io.Serializable; 4 | 5 | public interface Event extends Serializable { 6 | } -------------------------------------------------------------------------------- /src/main/java/example/ddd/EventHandler.java: -------------------------------------------------------------------------------- 1 | package example.ddd; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.integration.annotation.ServiceActivator; 9 | 10 | @Target({ ElementType.METHOD }) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @ServiceActivator(inputChannel = "eventBus") 13 | public @interface EventHandler { 14 | } -------------------------------------------------------------------------------- /src/main/java/example/ddd/EventPublisher.java: -------------------------------------------------------------------------------- 1 | package example.ddd; 2 | 3 | public interface EventPublisher { 4 | 5 | void publish(Event event); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/example/ddd/Repository.java: -------------------------------------------------------------------------------- 1 | package example.ddd; 2 | 3 | public interface Repository, K> { 4 | 5 | E load(K entityId); 6 | 7 | void save(E entity); 8 | 9 | void delete(E entity); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/example/ddd/ValueObject.java: -------------------------------------------------------------------------------- 1 | package example.ddd; 2 | 3 | import java.io.Serializable; 4 | 5 | import org.apache.commons.lang3.builder.EqualsBuilder; 6 | import org.apache.commons.lang3.builder.HashCodeBuilder; 7 | import org.apache.commons.lang3.builder.ToStringBuilder; 8 | import org.apache.commons.lang3.builder.ToStringStyle; 9 | 10 | public abstract class ValueObject implements Serializable { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | @Override 15 | public boolean equals(Object that) { 16 | return EqualsBuilder.reflectionEquals(this, that); 17 | } 18 | 19 | @Override 20 | public int hashCode() { 21 | return HashCodeBuilder.reflectionHashCode(this); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/ScrumBoardApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan 8 | public class ScrumBoardApplicationConfig { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/ProductService.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api; 2 | 3 | import example.scrumboard.application.api.commands.CreateProductCommand; 4 | import example.scrumboard.application.api.commands.PlanBacklogItemCommand; 5 | import example.scrumboard.application.api.commands.ReorderBacklogItemsCommand; 6 | import example.scrumboard.application.api.commands.ScheduleReleaseCommand; 7 | import example.scrumboard.application.api.commands.ScheduleSprintCommand; 8 | import example.scrumboard.domain.backlogitem.BacklogItemId; 9 | import example.scrumboard.domain.product.ProductId; 10 | import example.scrumboard.domain.release.ReleaseId; 11 | import example.scrumboard.domain.sprint.SprintId; 12 | 13 | public interface ProductService { 14 | 15 | ProductId createProduct(CreateProductCommand command); 16 | 17 | BacklogItemId planBacklogItem(ProductId productId, PlanBacklogItemCommand command); 18 | 19 | void reorderBacklogItems(ProductId productId, ReorderBacklogItemsCommand command); 20 | 21 | ReleaseId scheduleRelease(ScheduleReleaseCommand command); 22 | 23 | SprintId scheduleSprint(ScheduleSprintCommand command); 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/ReleaseService.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api; 2 | 3 | import example.scrumboard.domain.backlogitem.BacklogItemId; 4 | import example.scrumboard.domain.release.ReleaseId; 5 | 6 | public interface ReleaseService { 7 | 8 | void scheduleBacklogItem(ReleaseId releaseId, BacklogItemId backlogItemId); 9 | 10 | void unscheduleBacklogItem(ReleaseId releaseId, BacklogItemId backlogItemId); 11 | 12 | void archive(ReleaseId releaseId); 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/SprintService.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api; 2 | 3 | import example.scrumboard.domain.backlogitem.BacklogItemId; 4 | import example.scrumboard.domain.sprint.SprintId; 5 | 6 | public interface SprintService { 7 | 8 | void commitBacklogItem(SprintId sprintId, BacklogItemId backlogItemId); 9 | 10 | void uncommitBacklogItem(SprintId sprintId, BacklogItemId backlogItemId); 11 | 12 | void beginSprint(SprintId sprintId); 13 | 14 | void finishSprint(SprintId sprintId); 15 | 16 | void captureRetrospective(SprintId sprintId, String retrospective); 17 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/TaskService.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api; 2 | 3 | import example.scrumboard.application.api.commands.CreateTaskCommand; 4 | import example.scrumboard.domain.backlogitem.task.TaskId; 5 | 6 | public interface TaskService { 7 | 8 | TaskId createTask(CreateTaskCommand command); 9 | 10 | void beginTask(TaskId taskId); 11 | 12 | void finishTask(TaskId taskId); 13 | 14 | void amendHoursRemaining(TaskId taskId, Integer hoursRemaing); 15 | 16 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/commands/CreateProductCommand.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api.commands 2 | 3 | import groovy.transform.Canonical 4 | 5 | 6 | @Canonical 7 | class CreateProductCommand { 8 | String productName 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/commands/CreateTaskCommand.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api.commands 2 | 3 | import example.scrumboard.domain.backlogitem.BacklogItemId 4 | import groovy.transform.Canonical 5 | 6 | 7 | @Canonical 8 | class CreateTaskCommand { 9 | BacklogItemId backlogItemId 10 | String taskName 11 | String taskDescription 12 | Integer hoursRemaining 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/commands/PlanBacklogItemCommand.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api.commands 2 | 3 | import groovy.transform.Canonical 4 | 5 | 6 | @Canonical 7 | class PlanBacklogItemCommand { 8 | String backlogItemStory 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/commands/ReorderBacklogItemsCommand.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api.commands 2 | 3 | import example.scrumboard.domain.backlogitem.BacklogItemId 4 | import groovy.transform.Canonical 5 | 6 | 7 | @Canonical 8 | class ReorderBacklogItemsCommand { 9 | BacklogItemId[] backlogItemIds 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/commands/ScheduleReleaseCommand.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api.commands 2 | 3 | import example.scrumboard.domain.product.ProductId 4 | import groovy.transform.Canonical 5 | 6 | 7 | @Canonical 8 | class ScheduleReleaseCommand { 9 | ProductId productId 10 | String releaseName 11 | Date releaseDate 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/api/commands/ScheduleSprintCommand.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.api.commands 2 | 3 | import example.scrumboard.domain.product.ProductId 4 | import groovy.transform.Canonical 5 | 6 | 7 | @Canonical 8 | class ScheduleSprintCommand { 9 | ProductId productId 10 | String sprintName 11 | Date sprintBeginDate 12 | Date sprintEndDate 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/bootstrap/ScrumBoardBootstrap.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.bootstrap 2 | 3 | import org.joda.time.format.DateTimeFormat 4 | import org.joda.time.format.DateTimeFormatter 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.context.ApplicationListener 7 | 8 | import example.bootstrap.BootstrapEvent 9 | import example.bootstrap.BootstrapListener 10 | import example.scrumboard.application.api.ProductService 11 | import example.scrumboard.application.api.commands.CreateProductCommand 12 | import example.scrumboard.application.api.commands.PlanBacklogItemCommand 13 | import example.scrumboard.application.api.commands.ScheduleReleaseCommand 14 | import example.scrumboard.application.api.commands.ScheduleSprintCommand 15 | import example.scrumboard.domain.product.ProductId 16 | import groovy.sql.Sql 17 | 18 | @BootstrapListener 19 | class ScrumBoardBootstrap implements ApplicationListener { 20 | 21 | static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("YYYY-MM-DD") 22 | 23 | @Autowired 24 | ProductService productService 25 | 26 | @Autowired 27 | Sql sql 28 | 29 | @Override 30 | void onApplicationEvent(BootstrapEvent event) { 31 | if (sql.firstRow("SELECT count(*) count FROM t_product").count > 0) { 32 | return 33 | } 34 | 35 | /* 36 | * Product1 37 | */ 38 | ProductId productId1 = productService.createProduct( 39 | new CreateProductCommand(productName: "Example DDD/CQRS server") 40 | ) 41 | 42 | // backlog items 43 | productService.planBacklogItem(productId1, 44 | new PlanBacklogItemCommand(backlogItemStory: "Write documentation") 45 | ) 46 | productService.planBacklogItem(productId1, 47 | new PlanBacklogItemCommand(backlogItemStory: "Add more unit tests") 48 | ) 49 | 50 | // sprints 51 | productService.scheduleSprint( 52 | new ScheduleSprintCommand(productId: productId1, sprintName: "Sprint 1", sprintBeginDate: toDate("2011-01-01"), sprintEndDate: toDate("2011-01-14")) 53 | ) 54 | productService.scheduleSprint( 55 | new ScheduleSprintCommand(productId: productId1, sprintName: "Sprint 2", sprintBeginDate: toDate("2011-01-15"), sprintEndDate: toDate("2011-01-29")) 56 | ) 57 | 58 | // releases 59 | productService.scheduleRelease( 60 | new ScheduleReleaseCommand(productId: productId1, releaseName: "Release 1", releaseDate: toDate("2011-01-14")) 61 | ) 62 | 63 | productService.scheduleRelease( 64 | new ScheduleReleaseCommand(productId: productId1, releaseName: "Release 2", releaseDate: toDate("2011-01-29")) 65 | ) 66 | 67 | 68 | /* 69 | * Product2 70 | */ 71 | ProductId productId2 = productService.createProduct( 72 | new CreateProductCommand(productName: "Example DDD/CQRS client") 73 | ) 74 | 75 | // backlog items 76 | productService.planBacklogItem(productId2, 77 | new PlanBacklogItemCommand(backlogItemStory: "Apply Twitter Bootstrap") 78 | ) 79 | // backlog items 80 | productService.planBacklogItem(productId2, 81 | new PlanBacklogItemCommand(backlogItemStory: "Create Angular controllers") 82 | ) 83 | 84 | // no sprints 85 | 86 | /* 87 | * Product3 88 | */ 89 | productService.createProduct( 90 | new CreateProductCommand(productName: "Product with no backlog items") 91 | ) 92 | 93 | // no backlog items 94 | 95 | // no sprints 96 | } 97 | 98 | Date toDate(String date) { 99 | DATE_TIME_FORMATTER.parseDateTime(date).toDate() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/handlers/SampleEventHandler.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.handlers; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.integration.annotation.MessageEndpoint; 6 | 7 | import example.ddd.Event; 8 | import example.ddd.EventHandler; 9 | 10 | @MessageEndpoint 11 | public class SampleEventHandler { 12 | 13 | private static final Logger LOGGER = LoggerFactory.getLogger(SampleEventHandler.class); 14 | 15 | @EventHandler 16 | public void log(Event event) { 17 | LOGGER.info("Event handled " + event); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/impl/BacklogItemServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | import example.ddd.ApplicationService; 6 | import example.scrumboard.domain.backlogitem.BacklogItem; 7 | import example.scrumboard.domain.backlogitem.BacklogItemFactory; 8 | import example.scrumboard.domain.backlogitem.BacklogItemId; 9 | import example.scrumboard.domain.backlogitem.Priority; 10 | import example.scrumboard.domain.backlogitem.BacklogItemRepository; 11 | import example.scrumboard.domain.backlogitem.StoryPoints; 12 | 13 | @ApplicationService 14 | public class BacklogItemServiceImpl { 15 | 16 | @Autowired 17 | private BacklogItemFactory backlogItemFactory; 18 | 19 | @Autowired 20 | private BacklogItemRepository backlogItemRepository; 21 | 22 | public void assignStoryPoints(BacklogItemId backlogItemId, StoryPoints storyPoints) { 23 | BacklogItem backlogItem = backlogItemRepository.load(backlogItemId); 24 | 25 | backlogItem.assignStoryPoints(storyPoints); 26 | 27 | backlogItemRepository.save(backlogItem); 28 | } 29 | 30 | public void assignPriority(BacklogItemId backlogItemId, Priority priority) { 31 | BacklogItem backlogItem = backlogItemRepository.load(backlogItemId); 32 | 33 | backlogItem.assignPriority(priority); 34 | 35 | backlogItemRepository.save(backlogItem); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/impl/ProductServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | import example.ddd.ApplicationService; 6 | import example.scrumboard.application.api.ProductService; 7 | import example.scrumboard.application.api.commands.CreateProductCommand; 8 | import example.scrumboard.application.api.commands.PlanBacklogItemCommand; 9 | import example.scrumboard.application.api.commands.ReorderBacklogItemsCommand; 10 | import example.scrumboard.application.api.commands.ScheduleReleaseCommand; 11 | import example.scrumboard.application.api.commands.ScheduleSprintCommand; 12 | import example.scrumboard.domain.backlogitem.BacklogItem; 13 | import example.scrumboard.domain.backlogitem.BacklogItemFactory; 14 | import example.scrumboard.domain.backlogitem.BacklogItemId; 15 | import example.scrumboard.domain.backlogitem.BacklogItemRepository; 16 | import example.scrumboard.domain.product.Product; 17 | import example.scrumboard.domain.product.ProductFactory; 18 | import example.scrumboard.domain.product.ProductId; 19 | import example.scrumboard.domain.product.ProductRepository; 20 | import example.scrumboard.domain.release.Release; 21 | import example.scrumboard.domain.release.ReleaseFactory; 22 | import example.scrumboard.domain.release.ReleaseId; 23 | import example.scrumboard.domain.release.ReleaseRepository; 24 | import example.scrumboard.domain.sprint.Sprint; 25 | import example.scrumboard.domain.sprint.SprintFactory; 26 | import example.scrumboard.domain.sprint.SprintId; 27 | import example.scrumboard.domain.sprint.SprintRepository; 28 | 29 | @ApplicationService 30 | public class ProductServiceImpl implements ProductService { 31 | 32 | @Autowired 33 | private ProductFactory productFactory; 34 | 35 | @Autowired 36 | private ProductRepository productRepository; 37 | 38 | @Autowired 39 | private BacklogItemFactory backlogItemFactory; 40 | 41 | @Autowired 42 | private BacklogItemRepository backlogItemRepository; 43 | 44 | @Autowired 45 | private ReleaseFactory releaseFactory; 46 | 47 | @Autowired 48 | private ReleaseRepository releaseRepository; 49 | 50 | @Autowired 51 | private SprintFactory sprintFactory; 52 | 53 | @Autowired 54 | private SprintRepository sprintRepository; 55 | 56 | @Override 57 | public ProductId createProduct(CreateProductCommand command) { 58 | Product product = productFactory.create(command.getProductName()); 59 | productRepository.save(product); 60 | 61 | return product.getId(); 62 | } 63 | 64 | @Override 65 | public BacklogItemId planBacklogItem(ProductId productId, PlanBacklogItemCommand command) { 66 | Product product = productRepository.load(productId); 67 | 68 | BacklogItem backlogItem = backlogItemFactory.create(product, command.getBacklogItemStory()); 69 | backlogItemRepository.save(backlogItem); 70 | 71 | product.planBacklogItem(backlogItem); 72 | productRepository.save(product); 73 | 74 | return backlogItem.getId(); 75 | } 76 | 77 | @Override 78 | public void reorderBacklogItems(ProductId productId, ReorderBacklogItemsCommand command) { 79 | Product product = productRepository.load(productId); 80 | product.reorderBacklogItems(command.getBacklogItemIds()); 81 | 82 | productRepository.save(product); 83 | } 84 | 85 | @Override 86 | public ReleaseId scheduleRelease(ScheduleReleaseCommand command) { 87 | Product product = productRepository.load(command.getProductId()); 88 | 89 | Release release = releaseFactory.create(product, command.getReleaseName(), command.getReleaseDate()); 90 | releaseRepository.save(release); 91 | 92 | return release.getId(); 93 | } 94 | 95 | @Override 96 | public SprintId scheduleSprint(ScheduleSprintCommand command) { 97 | Product product = productRepository.load(command.getProductId()); 98 | 99 | Sprint sprint = sprintFactory.create(product, command.getSprintName(), command.getSprintBeginDate(), 100 | command.getSprintEndDate()); 101 | sprintRepository.save(sprint); 102 | 103 | return sprint.getId(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/impl/ReleaseServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | import example.ddd.ApplicationService; 6 | import example.scrumboard.application.api.ReleaseService; 7 | import example.scrumboard.domain.backlogitem.BacklogItem; 8 | import example.scrumboard.domain.backlogitem.BacklogItemId; 9 | import example.scrumboard.domain.backlogitem.BacklogItemRepository; 10 | import example.scrumboard.domain.release.Release; 11 | import example.scrumboard.domain.release.ReleaseId; 12 | import example.scrumboard.domain.release.ReleaseRepository; 13 | 14 | @ApplicationService 15 | public class ReleaseServiceImpl implements ReleaseService { 16 | 17 | @Autowired 18 | private ReleaseRepository releaseRepository; 19 | 20 | @Autowired 21 | private BacklogItemRepository backlogItemRepository; 22 | 23 | @Override 24 | public void scheduleBacklogItem(ReleaseId releaseId, BacklogItemId backlogItemId) { 25 | Release release = releaseRepository.load(releaseId); 26 | 27 | BacklogItem backlogItem = backlogItemRepository.load(backlogItemId); 28 | backlogItem.scheduleToRelease(release); 29 | 30 | release.scheduleBacklogItem(backlogItem); 31 | 32 | releaseRepository.save(release); 33 | } 34 | 35 | @Override 36 | public void unscheduleBacklogItem(ReleaseId releaseId, BacklogItemId backlogItemId) { 37 | Release release = releaseRepository.load(releaseId); 38 | 39 | BacklogItem backlogItem = backlogItemRepository.load(backlogItemId); 40 | backlogItem.unscheduleFromRelease(release); 41 | 42 | release.unscheduleBacklogItem(backlogItem); 43 | 44 | releaseRepository.save(release); 45 | } 46 | 47 | @Override 48 | public void archive(ReleaseId releaseId) { 49 | // TODO Auto-generated method stub 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/impl/SprintServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | import example.ddd.ApplicationService; 6 | import example.scrumboard.application.api.SprintService; 7 | import example.scrumboard.domain.backlogitem.BacklogItem; 8 | import example.scrumboard.domain.backlogitem.BacklogItemId; 9 | import example.scrumboard.domain.backlogitem.BacklogItemRepository; 10 | import example.scrumboard.domain.sprint.Sprint; 11 | import example.scrumboard.domain.sprint.SprintId; 12 | import example.scrumboard.domain.sprint.SprintRepository; 13 | 14 | @ApplicationService 15 | public class SprintServiceImpl implements SprintService { 16 | 17 | @Autowired 18 | private SprintRepository sprintRepository; 19 | 20 | @Autowired 21 | private BacklogItemRepository backlogItemRepository; 22 | 23 | @Override 24 | public void commitBacklogItem(SprintId sprintId, BacklogItemId backlogItemId) { 25 | Sprint sprint = sprintRepository.load(sprintId); 26 | 27 | BacklogItem backlogItem = backlogItemRepository.load(backlogItemId); 28 | backlogItem.commitToSprint(sprint); 29 | 30 | sprint.commitBacklogItem(backlogItem); 31 | 32 | backlogItemRepository.save(backlogItem); 33 | sprintRepository.save(sprint); 34 | } 35 | 36 | @Override 37 | public void uncommitBacklogItem(SprintId sprintId, BacklogItemId backlogItemId) { 38 | Sprint sprint = sprintRepository.load(sprintId); 39 | 40 | BacklogItem backlogItem = backlogItemRepository.load(backlogItemId); 41 | backlogItem.uncommitFromSprint(sprint); 42 | 43 | sprint.uncommitBacklogItem(backlogItem); 44 | 45 | backlogItemRepository.save(backlogItem); 46 | sprintRepository.save(sprint); 47 | } 48 | 49 | @Override 50 | public void beginSprint(SprintId sprintId) { 51 | // TODO Auto-generated method stub 52 | } 53 | 54 | @Override 55 | public void finishSprint(SprintId sprintId) { 56 | // TODO Auto-generated method stub 57 | } 58 | 59 | @Override 60 | public void captureRetrospective(SprintId sprintId, String retrospective) { 61 | // TODO Auto-generated method stub 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/impl/TaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | import example.ddd.ApplicationService; 6 | import example.scrumboard.application.api.TaskService; 7 | import example.scrumboard.application.api.commands.CreateTaskCommand; 8 | import example.scrumboard.application.system.DateProvider; 9 | import example.scrumboard.domain.backlogitem.BacklogItem; 10 | import example.scrumboard.domain.backlogitem.BacklogItemRepository; 11 | import example.scrumboard.domain.backlogitem.task.Task; 12 | import example.scrumboard.domain.backlogitem.task.TaskFactory; 13 | import example.scrumboard.domain.backlogitem.task.TaskId; 14 | import example.scrumboard.domain.backlogitem.task.TaskRepository; 15 | 16 | @ApplicationService 17 | public class TaskServiceImpl implements TaskService { 18 | 19 | @Autowired 20 | private TaskFactory taskFactory; 21 | 22 | @Autowired 23 | private TaskRepository taskRepository; 24 | 25 | @Autowired 26 | private BacklogItemRepository backlogItemRepository; 27 | 28 | @Autowired 29 | private DateProvider dateProvider; 30 | 31 | @Override 32 | public TaskId createTask(CreateTaskCommand command) { 33 | BacklogItem backlogItem = backlogItemRepository.load(command.getBacklogItemId()); 34 | 35 | Task task = taskFactory.create(backlogItem, command.getTaskName(), command.getTaskDescription(), 36 | command.getHoursRemaining()); 37 | taskRepository.save(task); 38 | 39 | return task.getId(); 40 | } 41 | 42 | @Override 43 | public void beginTask(TaskId taskId) { 44 | Task task = taskRepository.load(taskId); 45 | task.begin(); 46 | 47 | taskRepository.save(task); 48 | } 49 | 50 | @Override 51 | public void finishTask(TaskId taskId) { 52 | Task task = taskRepository.load(taskId); 53 | task.finish(); 54 | 55 | taskRepository.save(task); 56 | } 57 | 58 | @Override 59 | public void amendHoursRemaining(TaskId taskId, Integer hoursRemaing) { 60 | Task task = taskRepository.load(taskId); 61 | task.amendHoursRemaining(dateProvider.currentDate(), hoursRemaing); 62 | 63 | taskRepository.save(task); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/system/DateProvider.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.system; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class DateProvider { 9 | 10 | public Date currentDate() { 11 | return new Date(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/system/UserProvider.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.system; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class UserProvider { 7 | 8 | String currentUser() { 9 | return "mickey mouse"; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/application/tasks/SampleTask.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.application.tasks; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class SampleTask { 10 | 11 | private static final Logger LOGGER = LoggerFactory.getLogger(SampleTask.class); 12 | 13 | @Scheduled(fixedRate = 60000) 14 | public void log() { 15 | LOGGER.info("Task performed"); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/config/ScrumBoardConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.config; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Import; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.context.annotation.PropertySource; 10 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 11 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 12 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 13 | 14 | import example.scrumboard.application.ScrumBoardApplicationConfig; 15 | import example.scrumboard.domain.ScrumBoardDomainConfig; 16 | import example.scrumboard.infrastructure.bootstrap.ScrumBoardInfrastructureBootstrapConfig; 17 | import example.scrumboard.infrastructure.events.ScrumBoardInfrastructureEventsConfig; 18 | import example.scrumboard.infrastructure.jpa.ScrumBoardInfrastructureJpaConfig; 19 | import example.scrumboard.infrastructure.rest.ScrumBoardInfrastructureRestConfig; 20 | import example.scrumboard.infrastructure.shared.ScrumBoardInfrastructureAsyncConfig; 21 | import example.scrumboard.infrastructure.shared.ScrumBoardInfrastructureContextConfig; 22 | import example.scrumboard.infrastructure.shared.ScrumBoardInfrastructureTaskConfig; 23 | import example.scrumboard.infrastructure.shared.ScrumBoardInfrastructureTransactionConfig; 24 | import example.scrumboard.rest.commands.ScrumBoardRestCommandsConfig; 25 | import example.scrumboard.rest.queries.ScrumBoardRestQueriesConfig; 26 | 27 | @Configuration 28 | //@formatter:off 29 | @Import({ 30 | // infrastructure shared modules 31 | ScrumBoardInfrastructureAsyncConfig.class, 32 | ScrumBoardInfrastructureContextConfig.class, 33 | ScrumBoardInfrastructureTaskConfig.class, 34 | ScrumBoardInfrastructureTransactionConfig.class, 35 | // infrastructure modules 36 | ScrumBoardInfrastructureBootstrapConfig.class, 37 | ScrumBoardInfrastructureEventsConfig.class, 38 | ScrumBoardInfrastructureJpaConfig.class, 39 | ScrumBoardInfrastructureRestConfig.class, 40 | // core modules 41 | ScrumBoardApplicationConfig.class, 42 | ScrumBoardDomainConfig.class, 43 | ScrumBoardRestCommandsConfig.class, 44 | ScrumBoardRestQueriesConfig.class, 45 | }) 46 | //@formatter:on 47 | public class ScrumBoardConfig { 48 | 49 | @Bean 50 | public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { 51 | return new PropertySourcesPlaceholderConfigurer(); 52 | } 53 | 54 | @Configuration 55 | @PropertySource("classpath:/local.properties") 56 | @Profile(Local.PROFILE) 57 | public static class Local { 58 | 59 | public static final String PROFILE = "local"; 60 | 61 | @Bean 62 | public DataSource dataSource() { 63 | return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build(); 64 | } 65 | 66 | } 67 | 68 | @Configuration 69 | @PropertySource("classpath:/remote.properties") 70 | @Profile(Remote.PROFILE) 71 | public static class Remote { 72 | 73 | public static final String PROFILE = "remote"; 74 | 75 | @Bean 76 | public DataSource dataSource() { 77 | // TODO: JNDI lookup 78 | return null; 79 | } 80 | 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/config/ScrumBoardInitializer.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.config; 2 | 3 | import java.util.EnumSet; 4 | 5 | import javax.servlet.DispatcherType; 6 | import javax.servlet.FilterRegistration; 7 | import javax.servlet.ServletContext; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.ServletRegistration; 10 | 11 | import org.h2.server.web.WebServlet; 12 | import org.springframework.web.WebApplicationInitializer; 13 | import org.springframework.web.context.ContextLoaderListener; 14 | import org.springframework.web.context.WebApplicationContext; 15 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; 16 | import org.springframework.web.filter.HttpPutFormContentFilter; 17 | import org.springframework.web.servlet.DispatcherServlet; 18 | 19 | public class ScrumBoardInitializer implements WebApplicationInitializer { 20 | 21 | private static final String DISPATCHER_SERVLET_NAME = "rest"; 22 | private static final String H2_SERVLET_NAME = "h2"; 23 | 24 | @Override 25 | public void onStartup(ServletContext servletContext) throws ServletException { 26 | AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); 27 | rootContext.register(ScrumBoardConfig.class); 28 | 29 | servletContext.addListener(new ContextLoaderListener(rootContext)); 30 | 31 | registerDispatcherServlet(servletContext, rootContext); 32 | registerH2WebServlet(servletContext); 33 | 34 | registerHttpPutContentFilter(servletContext); 35 | } 36 | 37 | private void registerDispatcherServlet(ServletContext servletContext, WebApplicationContext rootContext) { 38 | ServletRegistration.Dynamic rest = servletContext.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet( 39 | rootContext)); 40 | rest.setLoadOnStartup(1); 41 | rest.addMapping("/rest/*"); 42 | } 43 | 44 | private void registerH2WebServlet(ServletContext servletContext) { 45 | ServletRegistration.Dynamic h2 = servletContext.addServlet(H2_SERVLET_NAME, new WebServlet()); 46 | h2.setLoadOnStartup(2); 47 | h2.addMapping("/h2/*"); 48 | } 49 | 50 | private void registerHttpPutContentFilter(ServletContext servletContext) { 51 | FilterRegistration.Dynamic registration = servletContext.addFilter("httpPutFormContentFilter", 52 | HttpPutFormContentFilter.class); 53 | registration.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), false, 54 | DISPATCHER_SERVLET_NAME); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/ScrumBoardDomainConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan 8 | public class ScrumBoardDomainConfig { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/BacklogItem.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.AttributeOverride; 6 | import javax.persistence.Column; 7 | import javax.persistence.Embedded; 8 | import javax.persistence.Entity; 9 | 10 | import example.ddd.AggregateRoot; 11 | import example.scrumboard.domain.product.Product; 12 | import example.scrumboard.domain.product.ProductId; 13 | import example.scrumboard.domain.release.Release; 14 | import example.scrumboard.domain.sprint.Sprint; 15 | import example.scrumboard.domain.sprint.SprintId; 16 | 17 | @Entity 18 | public class BacklogItem extends AggregateRoot { 19 | 20 | @Embedded 21 | private ProductId productId; 22 | 23 | @Column(nullable = false) 24 | private String story; 25 | 26 | @Embedded 27 | @AttributeOverride(name = "id", column = @Column(nullable = true)) 28 | private SprintId sprintId; 29 | 30 | BacklogItem() { 31 | } 32 | 33 | BacklogItem(BacklogItemId id, Product product, String story) { 34 | super(id); 35 | this.productId = requireNonNull(product).getId(); 36 | this.story = requireNonNull(story); 37 | } 38 | 39 | BacklogItem(BacklogItemId id, Product product, String story, Sprint sprint) { 40 | this(id, product, story); 41 | if (sprint != null) { 42 | this.sprintId = sprint.getId(); 43 | } 44 | } 45 | 46 | public void commitToSprint(Sprint sprint) { 47 | requireNonNull(sprint); 48 | checkProduct(sprint.getProductId()); 49 | 50 | if (isCommited()) { 51 | if (isEqualTo(sprint)) { 52 | // do nothing 53 | return; 54 | } else { 55 | uncommitFromSprint(); 56 | } 57 | } 58 | 59 | this.sprintId = sprint.getId(); 60 | register(new BacklogItemCommited(getId(), sprint.getId())); 61 | } 62 | 63 | public void uncommitFromSprint(Sprint sprint) { 64 | requireNonNull(sprint); 65 | checkProduct(sprint.getProductId()); 66 | 67 | if (!isCommited()) { 68 | throw new IllegalArgumentException("Backlog item " + getId() + " is not commited."); 69 | } 70 | 71 | if (!isEqualTo(sprint)) { 72 | throw new IllegalArgumentException("Backlog item " + getId() + " is commited to " + sprintId 73 | + " but expected " + sprint.getId() + "."); 74 | } 75 | 76 | uncommitFromSprint(); 77 | } 78 | 79 | public void scheduleToRelease(Release release) { 80 | // TODO Auto-generated method stub 81 | } 82 | 83 | public void unscheduleFromRelease(Release release) { 84 | // TODO Auto-generated method stub 85 | } 86 | 87 | public void assignStoryPoints(StoryPoints storyPoints) { 88 | // TODO Auto-generated method stub 89 | } 90 | 91 | public void assignPriority(Priority priority) { 92 | // TODO Auto-generated method stub 93 | } 94 | 95 | public void checkProduct(ProductId productId) { 96 | if (!this.productId.equals(productId)) { 97 | throw new IllegalArgumentException("Products do not match, product was " + productId + " but expected " 98 | + this.productId + "."); 99 | } 100 | } 101 | 102 | ProductId getProductId() { 103 | return productId; 104 | } 105 | 106 | SprintId getSprintId() { 107 | return sprintId; 108 | } 109 | 110 | private boolean isCommited() { 111 | return sprintId != null; 112 | } 113 | 114 | private boolean isEqualTo(Sprint sprint) { 115 | return sprintId.equals(sprint.getId()); 116 | } 117 | 118 | private void uncommitFromSprint() { 119 | SprintId sprintId = this.sprintId; 120 | this.sprintId = null; 121 | register(new BacklogItemUncommited(getId(), sprintId)); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/BacklogItemCommited.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem 2 | 3 | import static java.util.Objects.requireNonNull 4 | import example.ddd.Event 5 | import example.scrumboard.domain.sprint.SprintId 6 | import groovy.transform.Immutable 7 | import groovy.transform.TypeChecked 8 | 9 | @Immutable(knownImmutableClasses = [BacklogItemId.class, SprintId.class]) 10 | @TypeChecked 11 | class BacklogItemCommited implements Event { 12 | BacklogItemId backlogItemId 13 | SprintId sprintId 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/BacklogItemFactory.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import java.util.UUID; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import example.scrumboard.domain.product.Product; 8 | 9 | @Component 10 | public class BacklogItemFactory { 11 | 12 | public BacklogItem create(Product product, String story) { 13 | BacklogItemId id = new BacklogItemId(UUID.randomUUID().toString()); 14 | return new BacklogItem(id, product, story); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/BacklogItemId.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Embeddable; 7 | 8 | import example.ddd.ValueObject; 9 | 10 | @Embeddable 11 | public class BacklogItemId extends ValueObject { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Column(name = "backlog_item_id", nullable = false) 16 | private String id; 17 | 18 | BacklogItemId() { 19 | } 20 | 21 | public BacklogItemId(String id) { 22 | this.id = requireNonNull(id); 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/BacklogItemRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import example.ddd.Repository; 4 | 5 | public interface BacklogItemRepository extends Repository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/BacklogItemUncommited.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import static java.util.Objects.requireNonNull 4 | import example.ddd.Event 5 | import example.scrumboard.domain.sprint.SprintId 6 | import groovy.transform.Immutable 7 | import groovy.transform.TypeChecked 8 | 9 | @Immutable(knownImmutableClasses = [BacklogItemId.class, SprintId.class]) 10 | @TypeChecked 11 | class BacklogItemUncommited implements Event { 12 | BacklogItemId backlogItemId; 13 | SprintId sprintId; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/Priority.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | public class Priority { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/StoryPoints.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | public enum StoryPoints { 4 | 5 | ZERO(0), ONE(1), TWO(2), THREE(3), FIVE(5), EIGHT(8), THIRTEEN(13); 6 | 7 | private int value; 8 | 9 | private StoryPoints(int value) { 10 | this.value = value; 11 | } 12 | 13 | public int getValue() { 14 | return value; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/DoneTaskState.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | public class DoneTaskState extends TaskStateAdapter { 4 | 5 | @Override 6 | public boolean isDone() { 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/IllegalTaskStateException.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | public class IllegalTaskStateException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | private Task task; 8 | 9 | private String operation; 10 | 11 | public IllegalTaskStateException(Task task, String operation) { 12 | this.task = task; 13 | this.operation = operation; 14 | } 15 | 16 | @Override 17 | public String getMessage() { 18 | return "Cannot " + operation + " on " + task + "."; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/InProgressTaskState.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import java.util.Date; 4 | 5 | public class InProgressTaskState extends TaskStateAdapter { 6 | 7 | @Override 8 | public boolean isInProgress() { 9 | return true; 10 | } 11 | 12 | @Override 13 | public void finish(Task task) { 14 | task.doFinish(); 15 | } 16 | 17 | @Override 18 | public void amendHoursRemaining(Task task, Date effectiveDate, Integer hoursRemaing) { 19 | task.doAmendHoursRemaining(effectiveDate, hoursRemaing); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/RemainingAmendment.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.util.Date; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | 12 | @Entity 13 | public class RemainingAmendment { 14 | 15 | @Id 16 | @GeneratedValue 17 | private Long entityId; 18 | 19 | @Column(nullable = false) 20 | private Date effectiveDate; 21 | 22 | @Column(nullable = false) 23 | private Integer hoursRemaining; 24 | 25 | RemainingAmendment() { 26 | } 27 | 28 | RemainingAmendment(Date effectiveDate, Integer hoursRemaining) { 29 | this.effectiveDate = requireNonNull(effectiveDate); 30 | this.hoursRemaining = requireNonNull(hoursRemaining); 31 | 32 | if (hoursRemaining < 0) { 33 | throw new IllegalArgumentException("Remaining hours must be positive but was " + hoursRemaining); 34 | } 35 | } 36 | 37 | Date getEffectiveDate() { 38 | return effectiveDate; 39 | } 40 | 41 | Integer getHoursRemaining() { 42 | return hoursRemaining; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/Task.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import javax.persistence.CascadeType; 9 | import javax.persistence.Column; 10 | import javax.persistence.Embedded; 11 | import javax.persistence.Entity; 12 | import javax.persistence.EnumType; 13 | import javax.persistence.Enumerated; 14 | import javax.persistence.FetchType; 15 | import javax.persistence.JoinColumn; 16 | import javax.persistence.OneToMany; 17 | import javax.persistence.OrderColumn; 18 | 19 | import example.ddd.AggregateRoot; 20 | import example.scrumboard.domain.backlogitem.BacklogItem; 21 | import example.scrumboard.domain.backlogitem.BacklogItemId; 22 | 23 | @Entity 24 | public class Task extends AggregateRoot { 25 | 26 | @Embedded 27 | private BacklogItemId backlogItemId; 28 | 29 | @Enumerated(EnumType.STRING) 30 | private TaskStatus status; 31 | 32 | @Column(nullable = false) 33 | private String name; 34 | 35 | @Column(nullable = false) 36 | private String description; 37 | 38 | @Column(nullable = false) 39 | private Integer hoursRemaining; 40 | 41 | @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) 42 | @JoinColumn(name = "task_id", nullable = false, insertable = false, updatable = false) 43 | @OrderColumn 44 | private List remainingAmendments; 45 | 46 | Task() { 47 | } 48 | 49 | Task(TaskId id, BacklogItem backlogItem, TaskStatus status, String name, String description, 50 | Integer hoursRemaining, List remainingAmendments) { 51 | super(id); 52 | this.backlogItemId = requireNonNull(backlogItem).getId(); 53 | this.status = requireNonNull(status); 54 | this.name = requireNonNull(name); 55 | this.description = requireNonNull(description); 56 | this.hoursRemaining = requireNonNull(hoursRemaining); 57 | this.remainingAmendments = requireNonNull(remainingAmendments); 58 | } 59 | 60 | public void begin() { 61 | status.begin(this); 62 | } 63 | 64 | void doBegin() { 65 | status = TaskStatus.IN_PROGRESS; 66 | } 67 | 68 | public void finish() { 69 | status.finish(this); 70 | } 71 | 72 | void doFinish() { 73 | status = TaskStatus.DONE; 74 | } 75 | 76 | public void amendHoursRemaining(Date effectiveDate, Integer hoursRemaing) { 77 | status.amendHoursRemaining(this, effectiveDate, hoursRemaing); 78 | } 79 | 80 | void doAmendHoursRemaining(Date effectiveDate, Integer hoursRemaing) { 81 | RemainingAmendment remainingAmendment = new RemainingAmendment(effectiveDate, hoursRemaing); 82 | remainingAmendments.add(remainingAmendment); 83 | } 84 | 85 | BacklogItemId getBacklogItemId() { 86 | return backlogItemId; 87 | } 88 | 89 | TaskStatus getStatus() { 90 | return status; 91 | } 92 | 93 | String getName() { 94 | return name; 95 | } 96 | 97 | String getDescription() { 98 | return description; 99 | } 100 | 101 | Integer getHoursRemaining() { 102 | return hoursRemaining; 103 | } 104 | 105 | List getRemainingAmendments() { 106 | return remainingAmendments; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/TaskFactory.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.UUID; 6 | 7 | import org.springframework.stereotype.Component; 8 | 9 | import example.scrumboard.domain.backlogitem.BacklogItem; 10 | 11 | @Component 12 | public class TaskFactory { 13 | 14 | public Task create(BacklogItem backlogItem, String name, String description, Integer hoursRemaining) { 15 | TaskId id = new TaskId(UUID.randomUUID().toString()); 16 | TaskStatus status = TaskStatus.TODO; 17 | List remainingAmendments = new ArrayList<>(); 18 | 19 | return new Task(id, backlogItem, status, name, description, hoursRemaining, remainingAmendments); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/TaskId.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Embeddable; 7 | 8 | import example.ddd.ValueObject; 9 | 10 | @Embeddable 11 | public class TaskId extends ValueObject { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Column(name = "task_id", nullable = false) 16 | private String id; 17 | 18 | TaskId() { 19 | } 20 | 21 | public TaskId(String id) { 22 | this.id = requireNonNull(id); 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/TaskRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import example.ddd.Repository; 4 | 5 | public interface TaskRepository extends Repository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/TaskState.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import java.util.Date; 4 | 5 | public interface TaskState { 6 | 7 | boolean isTodo(); 8 | 9 | boolean isInProgress(); 10 | 11 | boolean isDone(); 12 | 13 | void begin(Task task); 14 | 15 | void finish(Task task); 16 | 17 | void amendHoursRemaining(Task task, Date effectiveDate, Integer hoursRemaing); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/TaskStateAdapter.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import java.util.Date; 4 | 5 | public class TaskStateAdapter implements TaskState { 6 | 7 | @Override 8 | public boolean isTodo() { 9 | return false; 10 | } 11 | 12 | @Override 13 | public boolean isInProgress() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public boolean isDone() { 19 | return false; 20 | } 21 | 22 | @Override 23 | public void begin(Task task) { 24 | throw new IllegalTaskStateException(task, "start"); 25 | } 26 | 27 | @Override 28 | public void finish(Task task) { 29 | throw new IllegalTaskStateException(task, "finish"); 30 | } 31 | 32 | @Override 33 | public void amendHoursRemaining(Task task, Date effectiveDate, Integer hoursRemaing) { 34 | throw new IllegalTaskStateException(task, "amend hours remaining"); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/TaskStatus.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | import java.util.Date; 4 | 5 | public enum TaskStatus implements TaskState { 6 | 7 | // @formatter:off 8 | TODO(new TodoTaskState()), 9 | IN_PROGRESS(new InProgressTaskState()), 10 | DONE(new DoneTaskState()); 11 | // @formatter:on 12 | 13 | private TaskState state; 14 | 15 | private TaskStatus(TaskState state) { 16 | this.state = state; 17 | } 18 | 19 | @Override 20 | public boolean isTodo() { 21 | return state.isTodo(); 22 | } 23 | 24 | @Override 25 | public boolean isInProgress() { 26 | return state.isInProgress(); 27 | } 28 | 29 | @Override 30 | public boolean isDone() { 31 | return state.isDone(); 32 | } 33 | 34 | @Override 35 | public void begin(Task task) { 36 | state.begin(task); 37 | } 38 | 39 | @Override 40 | public void finish(Task task) { 41 | state.finish(task); 42 | } 43 | 44 | @Override 45 | public void amendHoursRemaining(Task task, Date effectiveDate, Integer hoursRemaing) { 46 | state.amendHoursRemaining(task, effectiveDate, hoursRemaing); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/backlogitem/task/TodoTaskState.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem.task; 2 | 3 | public class TodoTaskState extends TaskStateAdapter { 4 | 5 | @Override 6 | public boolean isTodo() { 7 | return true; 8 | } 9 | 10 | @Override 11 | public void begin(Task task) { 12 | task.doBegin(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/product/BacklogItemPlannedEvent.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product 2 | 3 | import static java.util.Objects.requireNonNull 4 | import example.ddd.Event 5 | import example.scrumboard.domain.backlogitem.BacklogItemId 6 | import groovy.transform.Immutable 7 | import groovy.transform.TypeChecked 8 | 9 | @Immutable(knownImmutableClasses = [ProductId.class, BacklogItemId.class]) 10 | @TypeChecked 11 | class BacklogItemPlannedEvent implements Event { 12 | ProductId productId 13 | BacklogItemId backlogItemId 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/product/Product.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import static com.google.common.collect.Lists.newArrayList; 4 | import static java.util.Objects.requireNonNull; 5 | 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | import javax.persistence.CascadeType; 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.FetchType; 13 | import javax.persistence.JoinColumn; 14 | import javax.persistence.OneToMany; 15 | 16 | import com.google.common.collect.Iterables; 17 | 18 | import example.ddd.AggregateRoot; 19 | import example.scrumboard.domain.backlogitem.BacklogItem; 20 | import example.scrumboard.domain.backlogitem.BacklogItemId; 21 | 22 | @Entity 23 | public class Product extends AggregateRoot { 24 | 25 | @Column(nullable = false, unique = true) 26 | private String name; 27 | 28 | @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) 29 | @JoinColumn(name = "product_id", nullable = false, insertable = false, updatable = false) 30 | private Set backlogItems; 31 | 32 | Product() { 33 | } 34 | 35 | Product(ProductId id, String name, Set backlogItems) { 36 | super(id); 37 | this.name = requireNonNull(name); 38 | this.backlogItems = requireNonNull(backlogItems); 39 | } 40 | 41 | public void planBacklogItem(BacklogItem backlogItem) { 42 | requireNonNull(backlogItem); 43 | backlogItem.checkProduct(getId()); 44 | 45 | BacklogItemId backlogItemId = backlogItem.getId(); 46 | if (Iterables.any(backlogItems, ProductBacklogItem.hasId(backlogItemId))) { 47 | throw new IllegalArgumentException("Backlog item is already planned " + backlogItemId); 48 | } 49 | 50 | int position = backlogItems.size(); 51 | backlogItems.add(new ProductBacklogItem(backlogItemId, position)); 52 | 53 | register(new BacklogItemPlannedEvent(getId(), backlogItemId)); 54 | } 55 | 56 | public void reorderBacklogItems(List backlogItemIds) { 57 | requireNonNull(backlogItemIds); 58 | 59 | for (ProductBacklogItem backlogItem : backlogItems) { 60 | BacklogItemId backlogItemId = backlogItem.getId(); 61 | 62 | int newPosition = backlogItemIds.indexOf(backlogItemId); 63 | if (newPosition == -1) { 64 | throw new IllegalArgumentException("Backlog item not found " + backlogItemId); 65 | } 66 | 67 | backlogItem.setPosition(newPosition); 68 | } 69 | 70 | } 71 | 72 | public void reorderBacklogItems(BacklogItemId... backlogItemIds) { 73 | requireNonNull(backlogItemIds); 74 | reorderBacklogItems(newArrayList(backlogItemIds)); 75 | } 76 | 77 | String getName() { 78 | return name; 79 | } 80 | 81 | Set getBacklogItems() { 82 | return backlogItems; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/product/ProductBacklogItem.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Embedded; 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | 11 | import com.google.common.base.Predicate; 12 | 13 | import example.scrumboard.domain.backlogitem.BacklogItemId; 14 | 15 | @Entity 16 | public class ProductBacklogItem { 17 | 18 | static Predicate hasId(final BacklogItemId id) { 19 | return new Predicate() { 20 | @Override 21 | public boolean apply(ProductBacklogItem input) { 22 | return input.getId().equals(id); 23 | } 24 | }; 25 | } 26 | 27 | @Id 28 | @GeneratedValue 29 | private Long entityId; 30 | 31 | @Embedded 32 | private BacklogItemId id; 33 | 34 | @Column(nullable = false) 35 | private Integer position; 36 | 37 | ProductBacklogItem() { 38 | } 39 | 40 | ProductBacklogItem(BacklogItemId id, Integer position) { 41 | this.id = requireNonNull(id); 42 | this.position = requireNonNull(position); 43 | } 44 | 45 | BacklogItemId getId() { 46 | return id; 47 | } 48 | 49 | Integer getPosition() { 50 | return position; 51 | } 52 | 53 | void setPosition(Integer position) { 54 | this.position = position; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/product/ProductFactory.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.UUID; 6 | 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class ProductFactory { 11 | 12 | public Product create(String name) { 13 | ProductId id = new ProductId(UUID.randomUUID().toString()); 14 | Set backlogItems = new HashSet<>(); 15 | 16 | return new Product(id, name, backlogItems); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/product/ProductId.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Embeddable; 7 | 8 | import example.ddd.ValueObject; 9 | 10 | @Embeddable 11 | public class ProductId extends ValueObject { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Column(name = "product_id", nullable = false) 16 | private String id; 17 | 18 | ProductId() { 19 | } 20 | 21 | public ProductId(String id) { 22 | this.id = requireNonNull(id); 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/product/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import example.ddd.Repository; 4 | 5 | public interface ProductRepository extends Repository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/release/Release.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.release; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | 8 | import javax.persistence.CascadeType; 9 | import javax.persistence.Column; 10 | import javax.persistence.Embedded; 11 | import javax.persistence.Entity; 12 | import javax.persistence.FetchType; 13 | import javax.persistence.JoinColumn; 14 | import javax.persistence.OneToMany; 15 | 16 | import example.ddd.AggregateRoot; 17 | import example.scrumboard.domain.backlogitem.BacklogItem; 18 | import example.scrumboard.domain.product.Product; 19 | import example.scrumboard.domain.product.ProductId; 20 | 21 | @Entity 22 | public class Release extends AggregateRoot { 23 | 24 | @Embedded 25 | private ProductId productId; 26 | 27 | @Column(nullable = false) 28 | private String name; 29 | 30 | @Column(nullable = false) 31 | private Date releaseDate; 32 | 33 | @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) 34 | @JoinColumn(name = "release_id", nullable = false, insertable = false, updatable = false) 35 | private Set backlogItems; 36 | 37 | Release() { 38 | } 39 | 40 | Release(ReleaseId id, Product product, String name, Date releaseDate, Set backlogItems) { 41 | super(id); 42 | this.productId = requireNonNull(product).getId(); 43 | this.name = requireNonNull(name); 44 | this.releaseDate = requireNonNull(releaseDate); 45 | this.backlogItems = requireNonNull(backlogItems); 46 | } 47 | 48 | public ProductId getProductId() { 49 | return productId; 50 | } 51 | 52 | public void scheduleBacklogItem(BacklogItem backlogItem) { 53 | // TODO Auto-generated method stub 54 | } 55 | 56 | public void unscheduleBacklogItem(BacklogItem backlogItem) { 57 | // TODO Auto-generated method stub 58 | } 59 | 60 | String getName() { 61 | return name; 62 | } 63 | 64 | Date getReleaseDate() { 65 | return releaseDate; 66 | } 67 | 68 | Set getBacklogItems() { 69 | return backlogItems; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/release/ReleaseFactory.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.release; 2 | 3 | import java.util.Date; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | import java.util.UUID; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | import example.scrumboard.domain.product.Product; 11 | 12 | @Component 13 | public class ReleaseFactory { 14 | 15 | public Release create(Product product, String name, Date releaseDate) { 16 | ReleaseId id = new ReleaseId(UUID.randomUUID().toString()); 17 | Set backlogItems = new HashSet<>(); 18 | 19 | return new Release(id, product, name, releaseDate, backlogItems); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/release/ReleaseId.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.release; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Embeddable; 7 | 8 | import example.ddd.ValueObject; 9 | 10 | @Embeddable 11 | public class ReleaseId extends ValueObject { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Column(name = "release_id", nullable = false) 16 | private String id; 17 | 18 | ReleaseId() { 19 | } 20 | 21 | public ReleaseId(String id) { 22 | this.id = requireNonNull(id); 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/release/ReleaseRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.release; 2 | 3 | import example.ddd.Repository; 4 | 5 | public interface ReleaseRepository extends Repository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/release/ScheduledBacklogItem.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.release; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Embedded; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.Id; 9 | 10 | import com.google.common.base.Predicate; 11 | 12 | import example.scrumboard.domain.backlogitem.BacklogItemId; 13 | 14 | @Entity 15 | public class ScheduledBacklogItem { 16 | 17 | static Predicate hasId(final BacklogItemId id) { 18 | return new Predicate() { 19 | @Override 20 | public boolean apply(ScheduledBacklogItem input) { 21 | return input.getId().equals(id); 22 | } 23 | }; 24 | } 25 | 26 | @Id 27 | @GeneratedValue 28 | private Long entityId; 29 | 30 | @Embedded 31 | private BacklogItemId id; 32 | 33 | ScheduledBacklogItem() { 34 | } 35 | 36 | ScheduledBacklogItem(BacklogItemId id) { 37 | this.id = requireNonNull(id); 38 | } 39 | 40 | BacklogItemId getId() { 41 | return id; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/sprint/CommitedBacklogItem.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.sprint; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Embedded; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.Id; 9 | 10 | import com.google.common.base.Predicate; 11 | 12 | import example.scrumboard.domain.backlogitem.BacklogItemId; 13 | 14 | @Entity 15 | public class CommitedBacklogItem { 16 | 17 | static Predicate hasId(final BacklogItemId id) { 18 | return new Predicate() { 19 | @Override 20 | public boolean apply(CommitedBacklogItem input) { 21 | return input.getId().equals(id); 22 | } 23 | }; 24 | } 25 | 26 | @Id 27 | @GeneratedValue 28 | private Long entityId; 29 | 30 | @Embedded 31 | private BacklogItemId id; 32 | 33 | CommitedBacklogItem() { 34 | } 35 | 36 | CommitedBacklogItem(BacklogItemId id) { 37 | this.id = requireNonNull(id); 38 | } 39 | 40 | BacklogItemId getId() { 41 | return id; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/sprint/Sprint.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.sprint; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.util.Date; 6 | import java.util.Set; 7 | 8 | import javax.persistence.CascadeType; 9 | import javax.persistence.Column; 10 | import javax.persistence.Embedded; 11 | import javax.persistence.Entity; 12 | import javax.persistence.FetchType; 13 | import javax.persistence.JoinColumn; 14 | import javax.persistence.OneToMany; 15 | 16 | import com.google.common.collect.Iterables; 17 | 18 | import example.ddd.AggregateRoot; 19 | import example.scrumboard.domain.backlogitem.BacklogItem; 20 | import example.scrumboard.domain.backlogitem.BacklogItemId; 21 | import example.scrumboard.domain.product.Product; 22 | import example.scrumboard.domain.product.ProductId; 23 | 24 | @Entity 25 | public class Sprint extends AggregateRoot { 26 | 27 | @Embedded 28 | private ProductId productId; 29 | 30 | @Column(nullable = false) 31 | private String name; 32 | 33 | @Column(nullable = false) 34 | private Date beginDate; 35 | 36 | @Column(nullable = false) 37 | private Date endDate; 38 | 39 | @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) 40 | @JoinColumn(name = "sprint_id", nullable = false, insertable = false, updatable = false) 41 | private Set backlogItems; 42 | 43 | Sprint(SprintId id, Product product, String name, Date beginDate, Date endDate, 44 | Set backlogItems) { 45 | super(id); 46 | this.productId = requireNonNull(product).getId(); 47 | this.name = requireNonNull(name); 48 | this.beginDate = requireNonNull(beginDate); 49 | this.endDate = requireNonNull(endDate); 50 | this.backlogItems = requireNonNull(backlogItems); 51 | 52 | if (beginDate.after(endDate)) { 53 | throw new IllegalArgumentException("Begin date must be before end date " + endDate + " but was " 54 | + beginDate + "."); 55 | } 56 | } 57 | 58 | public ProductId getProductId() { 59 | return productId; 60 | } 61 | 62 | public void commitBacklogItem(BacklogItem backlogItem) { 63 | requireNonNull(backlogItem); 64 | backlogItem.checkProduct(productId); 65 | 66 | BacklogItemId backlogItemId = backlogItem.getId(); 67 | if (Iterables.any(backlogItems, CommitedBacklogItem.hasId(backlogItemId))) { 68 | throw new IllegalArgumentException("Backlog item is already commited " + backlogItemId); 69 | } 70 | 71 | backlogItems.add(new CommitedBacklogItem(backlogItemId)); 72 | } 73 | 74 | public void uncommitBacklogItem(BacklogItem backlogItem) { 75 | requireNonNull(backlogItem); 76 | backlogItem.checkProduct(productId); 77 | 78 | BacklogItemId backlogItemId = backlogItem.getId(); 79 | boolean removed = Iterables.removeIf(backlogItems, CommitedBacklogItem.hasId(backlogItemId)); 80 | if (!removed) { 81 | throw new IllegalArgumentException("Backlog item is not commited " + backlogItemId); 82 | } 83 | } 84 | 85 | String getName() { 86 | return name; 87 | } 88 | 89 | Date getBeginDate() { 90 | return beginDate; 91 | } 92 | 93 | Date getEndDate() { 94 | return endDate; 95 | } 96 | 97 | Set getBacklogItems() { 98 | return backlogItems; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/sprint/SprintFactory.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.sprint; 2 | 3 | import java.util.Date; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | import java.util.UUID; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | import example.scrumboard.domain.product.Product; 11 | 12 | @Component 13 | public class SprintFactory { 14 | 15 | public Sprint create(Product product, String name, Date beginDate, Date endDate) { 16 | SprintId id = new SprintId(UUID.randomUUID().toString()); 17 | Set backlogItems = new HashSet<>(); 18 | 19 | return new Sprint(id, product, name, beginDate, endDate, backlogItems); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/sprint/SprintId.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.sprint; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Embeddable; 7 | 8 | import example.ddd.ValueObject; 9 | 10 | @Embeddable 11 | public class SprintId extends ValueObject { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Column(name = "sprint_id", nullable = false) 16 | private String id; 17 | 18 | SprintId() { 19 | } 20 | 21 | public SprintId(String id) { 22 | this.id = requireNonNull(id); 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/domain/sprint/SprintRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.sprint; 2 | 3 | import example.ddd.Repository; 4 | 5 | public interface SprintRepository extends Repository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/bootstrap/BootstrapEventPublisher.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.bootstrap; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.ApplicationListener; 5 | import org.springframework.context.event.ContextRefreshedEvent; 6 | import org.springframework.scheduling.annotation.Async; 7 | import org.springframework.stereotype.Component; 8 | 9 | import example.bootstrap.BootstrapEvent; 10 | 11 | @Component 12 | public class BootstrapEventPublisher implements ApplicationListener { 13 | 14 | @Override 15 | @Async 16 | public void onApplicationEvent(ContextRefreshedEvent event) { 17 | ApplicationContext applicationContext = event.getApplicationContext(); 18 | 19 | if (isRootApplicationContext(applicationContext)) { 20 | applicationContext.publishEvent(new BootstrapEvent(applicationContext)); 21 | } 22 | } 23 | 24 | private boolean isRootApplicationContext(ApplicationContext applicationContext) { 25 | return applicationContext.getParent() == null; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/bootstrap/ScrumBoardInfrastructureBootstrapConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.bootstrap; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan 8 | public class ScrumBoardInfrastructureBootstrapConfig { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/events/ScrumBoardInfrastructureEventsConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.events; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.integration.channel.PublishSubscribeChannel; 7 | import org.springframework.integration.config.EnableIntegration; 8 | import org.springframework.integration.core.MessagingTemplate; 9 | 10 | @Configuration 11 | @EnableIntegration 12 | @ComponentScan 13 | public class ScrumBoardInfrastructureEventsConfig { 14 | 15 | @Bean 16 | public PublishSubscribeChannel eventBus() { 17 | return new PublishSubscribeChannel(); 18 | } 19 | 20 | @Bean 21 | public MessagingTemplate messagingTemplate(PublishSubscribeChannel eventBus) { 22 | MessagingTemplate messagingTemplate = new MessagingTemplate(); 23 | messagingTemplate.setDefaultChannel(eventBus); 24 | 25 | return messagingTemplate; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/events/SpringIntegrationEventPublisher.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.events; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.integration.core.MessagingTemplate; 5 | import org.springframework.integration.support.MessageBuilder; 6 | import org.springframework.messaging.Message; 7 | import org.springframework.stereotype.Component; 8 | 9 | import example.ddd.Event; 10 | import example.ddd.EventPublisher; 11 | 12 | @Component 13 | public class SpringIntegrationEventPublisher implements EventPublisher { 14 | 15 | @Autowired 16 | private MessagingTemplate messagingTemplate; 17 | 18 | @Override 19 | public void publish(Event event) { 20 | Message message = MessageBuilder.withPayload(event).build(); 21 | messagingTemplate.send(message); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/ScrumBoardInfrastructureJpaConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import javax.sql.DataSource; 7 | 8 | import org.hibernate.jpa.AvailableSettings; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.ComponentScan; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.env.Environment; 14 | import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; 15 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 16 | import org.springframework.orm.jpa.vendor.Database; 17 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 18 | 19 | import example.scrumboard.domain.ScrumBoardDomainConfig; 20 | import example.scrumboard.infrastructure.jpa.hibernate.FixedPrefixNamingStrategy; 21 | 22 | @Configuration 23 | @ComponentScan 24 | public class ScrumBoardInfrastructureJpaConfig { 25 | 26 | @Autowired 27 | private Environment environment; 28 | 29 | @Autowired 30 | private DataSource dataSource; 31 | 32 | @Bean 33 | public LocalContainerEntityManagerFactoryBean entityManagerFactory() { 34 | HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(); 35 | 36 | jpaVendorAdapter.setDatabase(Database.H2); 37 | jpaVendorAdapter.setGenerateDdl(environment.getRequiredProperty("jpa.generateDdl", Boolean.class)); 38 | jpaVendorAdapter.setShowSql(environment.getRequiredProperty("jpa.showSql", Boolean.class)); 39 | 40 | Map jpaProperties = new HashMap<>(); 41 | jpaProperties.put(AvailableSettings.NAMING_STRATEGY, FixedPrefixNamingStrategy.class.getName()); 42 | 43 | LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); 44 | 45 | entityManagerFactoryBean.setDataSource(dataSource); 46 | entityManagerFactoryBean.setPackagesToScan(ScrumBoardDomainConfig.class.getPackage().getName()); 47 | entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter); 48 | entityManagerFactoryBean.setJpaPropertyMap(jpaProperties); 49 | 50 | return entityManagerFactoryBean; 51 | } 52 | 53 | @Bean 54 | public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { 55 | return new PersistenceExceptionTranslationPostProcessor(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/hibernate/FixedPrefixNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.hibernate; 2 | 3 | import org.hibernate.cfg.ImprovedNamingStrategy; 4 | 5 | import com.google.common.base.Strings; 6 | 7 | public class FixedPrefixNamingStrategy extends ImprovedNamingStrategy { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | private static final String TABLE_PREFIX = "t_"; 12 | 13 | private static final String COLUMN_PREFIX = "c_"; 14 | 15 | @Override 16 | public String classToTableName(String className) { 17 | return TABLE_PREFIX + super.classToTableName(className); 18 | } 19 | 20 | @Override 21 | public String collectionTableName(String ownerEntity, String ownerEntityTable, String associatedEntity, 22 | String associatedEntityTable, String propertyName) { 23 | if (!Strings.isNullOrEmpty(associatedEntity) && !Strings.isNullOrEmpty(associatedEntityTable)) { 24 | return TABLE_PREFIX + tableName(ownerEntityTable) + "_" + super.tableName(associatedEntityTable); 25 | } else { 26 | return TABLE_PREFIX + tableName(ownerEntityTable) + "_" + super.propertyToColumnName(propertyName); 27 | } 28 | } 29 | 30 | @Override 31 | public String propertyToColumnName(String propertyName) { 32 | return COLUMN_PREFIX + super.propertyToColumnName(propertyName); 33 | } 34 | 35 | @Override 36 | public String joinKeyColumnName(String joinedColumn, String joinedTable) { 37 | return COLUMN_PREFIX + super.joinKeyColumnName(joinedColumn, joinedTable); 38 | } 39 | 40 | @Override 41 | public String columnName(String columnName) { 42 | return COLUMN_PREFIX + super.columnName(columnName); 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/repositories/GenericJpaRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import java.lang.reflect.ParameterizedType; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.LockModeType; 9 | import javax.persistence.PersistenceContext; 10 | 11 | import org.springframework.orm.ObjectRetrievalFailureException; 12 | 13 | import example.ddd.AggregateRoot; 14 | import example.ddd.Repository; 15 | 16 | public class GenericJpaRepository, K> implements Repository { 17 | 18 | @PersistenceContext 19 | private EntityManager entityManager; 20 | 21 | private Class entityClass; 22 | 23 | @SuppressWarnings("unchecked") 24 | public GenericJpaRepository() { 25 | this.entityClass = ((Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]); 26 | } 27 | 28 | @Override 29 | public E load(K id) { 30 | requireNonNull(id); 31 | 32 | E entity = entityManager.find(entityClass, id, LockModeType.OPTIMISTIC); 33 | 34 | if (entity == null) { 35 | throw new ObjectRetrievalFailureException(entityClass, id); 36 | } 37 | 38 | return entity; 39 | } 40 | 41 | @Override 42 | public void save(E entity) { 43 | requireNonNull(entity); 44 | 45 | if (entityManager.contains(entity)) { 46 | entityManager.lock(entity, LockModeType.OPTIMISTIC_FORCE_INCREMENT); 47 | } else { 48 | entityManager.persist(entity); 49 | } 50 | 51 | entityManager.flush(); 52 | } 53 | 54 | @Override 55 | public void delete(E entity) { 56 | requireNonNull(entity); 57 | 58 | entityManager.remove(entity); 59 | entityManager.flush(); 60 | } 61 | 62 | protected Class getEntityClass() { 63 | return entityClass; 64 | } 65 | 66 | protected EntityManager getEntityManager() { 67 | return entityManager; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/repositories/JpaBacklogItemRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import example.scrumboard.domain.backlogitem.BacklogItem; 4 | import example.scrumboard.domain.backlogitem.BacklogItemId; 5 | import example.scrumboard.domain.backlogitem.BacklogItemRepository; 6 | 7 | @JpaRepository 8 | public class JpaBacklogItemRepository extends GenericJpaRepository implements 9 | BacklogItemRepository { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/repositories/JpaProductRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import example.scrumboard.domain.product.Product; 4 | import example.scrumboard.domain.product.ProductId; 5 | import example.scrumboard.domain.product.ProductRepository; 6 | 7 | @JpaRepository 8 | public class JpaProductRepository extends GenericJpaRepository implements ProductRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/repositories/JpaReleaseRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import example.scrumboard.domain.release.Release; 4 | import example.scrumboard.domain.release.ReleaseId; 5 | import example.scrumboard.domain.release.ReleaseRepository; 6 | 7 | @JpaRepository 8 | public class JpaReleaseRepository extends GenericJpaRepository implements ReleaseRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/repositories/JpaRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.stereotype.Repository; 9 | import org.springframework.transaction.annotation.Propagation; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | @Repository 13 | @Transactional(readOnly = false, propagation = Propagation.MANDATORY) 14 | @Target({ ElementType.TYPE, ElementType.METHOD }) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface JpaRepository { 17 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/repositories/JpaSprintRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import example.scrumboard.domain.sprint.Sprint; 4 | import example.scrumboard.domain.sprint.SprintId; 5 | import example.scrumboard.domain.sprint.SprintRepository; 6 | 7 | @JpaRepository 8 | public class JpaSprintRepository extends GenericJpaRepository implements SprintRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/repositories/JpaTaskRepository.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import example.scrumboard.domain.backlogitem.task.Task; 4 | import example.scrumboard.domain.backlogitem.task.TaskId; 5 | import example.scrumboard.domain.backlogitem.task.TaskRepository; 6 | 7 | @JpaRepository 8 | public class JpaTaskRepository extends GenericJpaRepository implements TaskRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/spring/EventsPublishingAspect.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.spring; 2 | 3 | import org.aspectj.lang.annotation.AfterReturning; 4 | import org.aspectj.lang.annotation.Aspect; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import example.ddd.AggregateRoot; 9 | import example.ddd.Event; 10 | import example.ddd.EventPublisher; 11 | 12 | @Component 13 | @Aspect 14 | public class EventsPublishingAspect { 15 | 16 | @Autowired 17 | private EventPublisher eventPublisher; 18 | 19 | @AfterReturning(pointcut = "execution(public * example.ddd.Repository+.save(..)) && args(aggregateRoot)") 20 | public void publishPendingEvents(AggregateRoot aggregateRoot) { 21 | for (Event event : aggregateRoot.getPendingEvents()) { 22 | eventPublisher.publish(event); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/jpa/spring/RepositoryAutowiringAspect.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.spring; 2 | 3 | import org.aspectj.lang.annotation.AfterReturning; 4 | import org.aspectj.lang.annotation.Aspect; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @Aspect 11 | public class RepositoryAutowiringAspect { 12 | 13 | @Autowired 14 | private AutowireCapableBeanFactory beanFactory; 15 | 16 | @AfterReturning(pointcut = "execution(public * example.ddd.Repository+.load(..))", returning = "aggregateRoot") 17 | public void autowireLoadedEntity(Object aggregateRoot) { 18 | beanFactory.autowireBean(aggregateRoot); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/rest/ProductIdConverter.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.rest; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | import org.springframework.stereotype.Component; 5 | 6 | import example.scrumboard.domain.product.ProductId; 7 | 8 | @Component 9 | public class ProductIdConverter implements Converter { 10 | 11 | @Override 12 | public ProductId convert(String source) { 13 | return new ProductId(source); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/rest/ScrumBoardInfrastructureRestConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.rest; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.data.web.config.EnableSpringDataWebSupport; 9 | import org.springframework.http.converter.HttpMessageConverter; 10 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 11 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 12 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 13 | 14 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 15 | import com.fasterxml.jackson.databind.ObjectMapper; 16 | 17 | @Configuration 18 | @ComponentScan 19 | @EnableWebMvc 20 | @EnableSpringDataWebSupport 21 | public class ScrumBoardInfrastructureRestConfig extends WebMvcConfigurerAdapter { 22 | 23 | @Bean 24 | public ObjectMapper objectMapper() { 25 | ObjectMapper objectMapper = new ObjectMapper(); 26 | 27 | objectMapper.setSerializationInclusion(Include.NON_NULL); 28 | 29 | return objectMapper; 30 | } 31 | 32 | @Bean 33 | public MappingJackson2HttpMessageConverter mappingJacksonMessageConverter() { 34 | MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); 35 | converter.setObjectMapper(objectMapper()); 36 | return converter; 37 | } 38 | 39 | @Override 40 | public void configureMessageConverters(List> converters) { 41 | converters.add(mappingJacksonMessageConverter()); 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/shared/ScrumBoardInfrastructureAsyncConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.shared; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Profile; 10 | import org.springframework.core.env.Environment; 11 | import org.springframework.scheduling.annotation.AsyncConfigurer; 12 | import org.springframework.scheduling.annotation.EnableAsync; 13 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 14 | 15 | import example.scrumboard.config.ScrumBoardConfig; 16 | 17 | @Configuration 18 | @Profile({ ScrumBoardConfig.Local.PROFILE, ScrumBoardConfig.Remote.PROFILE }) 19 | @EnableAsync 20 | public class ScrumBoardInfrastructureAsyncConfig implements AsyncConfigurer { 21 | 22 | @Autowired 23 | private Environment environment; 24 | 25 | @Override 26 | public Executor getAsyncExecutor() { 27 | return asyncExecutor(); 28 | } 29 | 30 | @Bean 31 | @Qualifier("asyncExecutor") 32 | public Executor asyncExecutor() { 33 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 34 | executor.setCorePoolSize(environment.getRequiredProperty("scheduling.asyncExecutorCorePoolSize", Integer.class)); 35 | executor.setMaxPoolSize(environment.getRequiredProperty("scheduling.asyncExecutorMaxPoolSize", Integer.class)); 36 | executor.setQueueCapacity(environment.getRequiredProperty("scheduling.asyncQueueCapacity", Integer.class)); 37 | executor.setThreadNamePrefix("ExampleAsyncExecutor-"); 38 | return executor; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/shared/ScrumBoardInfrastructureContextConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.shared; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 5 | 6 | @Configuration 7 | @EnableAspectJAutoProxy 8 | public class ScrumBoardInfrastructureContextConfig { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/shared/ScrumBoardInfrastructureTaskConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.shared; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.ThreadFactory; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Profile; 12 | import org.springframework.core.env.Environment; 13 | import org.springframework.scheduling.annotation.EnableScheduling; 14 | import org.springframework.scheduling.annotation.SchedulingConfigurer; 15 | import org.springframework.scheduling.config.ScheduledTaskRegistrar; 16 | 17 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 18 | 19 | import example.scrumboard.config.ScrumBoardConfig; 20 | 21 | @Configuration 22 | @Profile({ ScrumBoardConfig.Local.PROFILE, ScrumBoardConfig.Remote.PROFILE }) 23 | @EnableScheduling 24 | public class ScrumBoardInfrastructureTaskConfig implements SchedulingConfigurer { 25 | 26 | @Autowired 27 | private Environment environment; 28 | 29 | @Override 30 | public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { 31 | taskRegistrar.setScheduler(taskExecutor()); 32 | } 33 | 34 | @Bean(destroyMethod = "shutdown") 35 | @Qualifier("taskExecutor") 36 | public Executor taskExecutor() { 37 | ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("ExampleTaskExecutor-%d").build(); 38 | return Executors.newScheduledThreadPool( 39 | environment.getRequiredProperty("scheduling.taskExecutorCorePoolSize", Integer.class), threadFactory); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/infrastructure/shared/ScrumBoardInfrastructureTransactionConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.shared; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.orm.jpa.JpaTransactionManager; 7 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 8 | import org.springframework.transaction.PlatformTransactionManager; 9 | import org.springframework.transaction.annotation.EnableTransactionManagement; 10 | import org.springframework.transaction.annotation.TransactionManagementConfigurer; 11 | 12 | @Configuration 13 | @EnableTransactionManagement 14 | public class ScrumBoardInfrastructureTransactionConfig implements TransactionManagementConfigurer { 15 | 16 | @Autowired 17 | private LocalContainerEntityManagerFactoryBean entityManagerFactoryBean; 18 | 19 | @Override 20 | public PlatformTransactionManager annotationDrivenTransactionManager() { 21 | return transactionManager(); 22 | } 23 | 24 | @Bean 25 | @Autowired 26 | public PlatformTransactionManager transactionManager() { 27 | JpaTransactionManager transactionManager = new JpaTransactionManager(); 28 | 29 | transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject()); 30 | 31 | return transactionManager; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/rest/commands/ScrumBoardRestCommandsConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.commands; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan 8 | public class ScrumBoardRestCommandsConfig { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/rest/commands/product/ProductCommandController.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.commands.product; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.RequestBody; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import example.scrumboard.application.api.ProductService; 13 | import example.scrumboard.application.api.commands.CreateProductCommand; 14 | import example.scrumboard.application.api.commands.PlanBacklogItemCommand; 15 | import example.scrumboard.application.api.commands.ReorderBacklogItemsCommand; 16 | import example.scrumboard.domain.backlogitem.BacklogItemId; 17 | import example.scrumboard.domain.product.ProductId; 18 | 19 | @RestController 20 | public class ProductCommandController { 21 | 22 | @Autowired 23 | private ProductService productService; 24 | 25 | @RequestMapping(value = "/products", method = RequestMethod.POST) 26 | @ResponseStatus(HttpStatus.CREATED) 27 | public ProductId create(@RequestBody CreateProductCommand command) { 28 | return productService.createProduct(command); 29 | } 30 | 31 | @RequestMapping(value = "/products/{productId}/backlogItems", method = RequestMethod.POST) 32 | @ResponseStatus(HttpStatus.CREATED) 33 | public BacklogItemId planBacklogItem(@PathVariable("productId") ProductId productId, 34 | @RequestBody PlanBacklogItemCommand command) { 35 | return productService.planBacklogItem(productId, command); 36 | } 37 | 38 | @RequestMapping(value = "/products/{productId}/reorderBacklogItems", method = RequestMethod.POST) 39 | public void reorderBacklogItems(@PathVariable("productId") ProductId productId, 40 | @RequestBody ReorderBacklogItemsCommand command) { 41 | productService.reorderBacklogItems(productId, command); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/rest/queries/ScrumBoardRestQueriesConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.queries; 2 | 3 | import groovy.sql.Sql; 4 | 5 | import javax.sql.DataSource; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.ComponentScan; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | @Configuration 13 | @ComponentScan 14 | public class ScrumBoardRestQueriesConfig { 15 | 16 | @Bean(destroyMethod = "close") 17 | @Autowired 18 | public Sql sql(DataSource dataSource) { 19 | return new Sql(dataSource); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/rest/queries/product/ProductQueryController.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.queries.product 2 | 3 | import org.springframework.beans.factory.annotation.Autowired 4 | import org.springframework.data.domain.PageImpl 5 | import org.springframework.data.domain.Pageable 6 | import org.springframework.web.bind.annotation.PathVariable 7 | import org.springframework.web.bind.annotation.RequestMapping 8 | import org.springframework.web.bind.annotation.RequestMethod 9 | import org.springframework.web.bind.annotation.RestController 10 | 11 | import example.scrumboard.domain.product.ProductId 12 | import example.scrumboard.rest.queries.product.dtos.ProductBacklogItemDto 13 | import example.scrumboard.rest.queries.product.dtos.ProductDto 14 | import groovy.sql.Sql 15 | 16 | 17 | @RestController 18 | class ProductQueryController { 19 | 20 | @Autowired 21 | Sql sql 22 | 23 | @RequestMapping(value = "/products", method = RequestMethod.GET) 24 | def products(Pageable page) { 25 | new PageImpl(productsContent(page), page, productsCount()) 26 | } 27 | 28 | @RequestMapping(value = "/products/{productId}", method = RequestMethod.GET) 29 | def product(@PathVariable("productId") ProductId productId) { 30 | def query = """ 31 | SELECT 32 | p.c_product_id, 33 | p.c_name, 34 | COUNT(pbi.c_product_id) count 35 | FROM 36 | t_product p 37 | LEFT OUTER JOIN t_product_backlog_item pbi ON 38 | p.c_product_id = pbi.c_product_id 39 | WHERE 40 | p.c_product_id = ? 41 | GROUP BY 42 | p.c_product_id, p.c_name 43 | """ 44 | 45 | sql.rows(query, [productId.id], 0, 1).collect { row -> 46 | new ProductDto( 47 | productId: row.c_product_id, 48 | productName: row.c_name, 49 | backlogItemsCount: row.count 50 | ) 51 | }.first() 52 | } 53 | 54 | @RequestMapping(value = "/products/{productId}/backlogItems", method = RequestMethod.GET) 55 | def backlogItems(@PathVariable("productId") ProductId productId) { 56 | def query = """ 57 | SELECT 58 | bi.c_backlog_item_id, 59 | bi.c_story, 60 | pbi.c_position 61 | FROM 62 | t_product_backlog_item pbi 63 | RIGHT OUTER JOIN t_product p ON 64 | pbi.c_product_id = p.c_product_id 65 | RIGHT OUTER JOIN t_backlog_item bi ON 66 | pbi.c_backlog_item_id = bi.c_backlog_item_id 67 | WHERE 68 | p.c_product_id = ? 69 | ORDER BY pbi.c_position 70 | """ 71 | 72 | sql.rows(query, [productId.id]).collect { row -> 73 | new ProductBacklogItemDto( 74 | backlogItemId: row.c_backlog_item_id, 75 | backlogItemStory: row.c_story, 76 | backlogItemPosition: row.c_position 77 | ) 78 | } 79 | } 80 | 81 | private List productsContent(Pageable page) { 82 | def query = """ 83 | SELECT 84 | p.c_product_id, 85 | p.c_name, 86 | COUNT(pbi.c_product_id) count 87 | FROM 88 | t_product p 89 | LEFT OUTER JOIN t_product_backlog_item pbi ON 90 | p.c_product_id = pbi.c_product_id 91 | GROUP BY 92 | p.c_product_id, 93 | p.c_name 94 | ORDER BY 95 | p.c_name 96 | """ 97 | 98 | sql.rows(query, page.offset, page.pageSize).collect { row -> 99 | new ProductDto( 100 | productId: row.c_product_id, 101 | productName: row.c_name, 102 | backlogItemsCount: row.count 103 | ) 104 | } 105 | } 106 | 107 | private Long productsCount() { 108 | def query = """ 109 | SELECT 110 | count(*) count 111 | FROM 112 | t_product 113 | """ 114 | 115 | sql.firstRow(query).count 116 | } 117 | } -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/rest/queries/product/dtos/ProductBacklogItemDto.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.queries.product.dtos 2 | 3 | import groovy.transform.Immutable 4 | 5 | @Immutable 6 | class ProductBacklogItemDto { 7 | String backlogItemId 8 | String backlogItemStory 9 | int backlogItemPosition 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/example/scrumboard/rest/queries/product/dtos/ProductDto.groovy: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.queries.product.dtos 2 | 3 | import groovy.transform.Immutable 4 | 5 | @Immutable 6 | class ProductDto { 7 | String productId 8 | String productName 9 | int backlogItemsCount 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/local.properties: -------------------------------------------------------------------------------- 1 | jpa.generateDdl=true 2 | jpa.showSql=false 3 | 4 | scheduling.asyncExecutorCorePoolSize=5 5 | scheduling.asyncExecutorMaxPoolSize=10 6 | scheduling.asyncQueueCapacity=100 7 | scheduling.taskExecutorCorePoolSize=3 8 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/remote.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkuthan/example-ddd-cqrs-server/3f4b0136bf816bd5d358c6cc3dfd9501fac78084/src/main/resources/remote.properties -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Insert title here 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/java/example/ddd/domain/AggregateRootAssert.java: -------------------------------------------------------------------------------- 1 | package example.ddd.domain; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.assertj.core.api.AbstractAssert; 6 | 7 | import example.ddd.AggregateRoot; 8 | import example.ddd.Event; 9 | 10 | public class AggregateRootAssert extends AbstractAssert> { 11 | 12 | public AggregateRootAssert(AggregateRoot actual) { 13 | super(actual, AggregateRootAssert.class); 14 | } 15 | 16 | public AggregateRootAssert published(Event event) { 17 | assertThat(actual.getPendingEvents()).contains(event); 18 | return this; 19 | } 20 | 21 | public AggregateRootAssert notPublished() { 22 | assertThat(actual.getPendingEvents()).isEmpty(); 23 | return this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/example/ddd/domain/DddAssertions.java: -------------------------------------------------------------------------------- 1 | package example.ddd.domain; 2 | 3 | import example.ddd.AggregateRoot; 4 | 5 | public class DddAssertions { 6 | 7 | public static AggregateRootAssert assertThat(AggregateRoot actual) { 8 | return new AggregateRootAssert(actual); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/TestGroups.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard; 2 | 3 | public interface TestGroups { 4 | 5 | String UNIT = "unit"; 6 | String INTEGRATION = "integration"; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/ScrumBoardBuilders.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain; 2 | 3 | import example.scrumboard.domain.backlogitem.BacklogItemBuilder; 4 | import example.scrumboard.domain.sprint.SprintBuilder; 5 | 6 | public class ScrumBoardBuilders { 7 | 8 | public static BacklogItemBuilder givenBacklogItem() { 9 | return new BacklogItemBuilder(); 10 | } 11 | 12 | public static SprintBuilder givenSprint() { 13 | return new SprintBuilder(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/backlogitem/BacklogItemAssert.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import static example.ddd.domain.DddAssertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.assertj.core.api.AbstractAssert; 7 | 8 | import example.scrumboard.domain.sprint.SprintId; 9 | 10 | public class BacklogItemAssert extends AbstractAssert { 11 | 12 | public BacklogItemAssert(BacklogItem actual) { 13 | super(actual, BacklogItemAssert.class); 14 | } 15 | 16 | public BacklogItemAssert isCommitedToSprint(SprintId sprintId) { 17 | assertThat(actual.getSprintId()).isEqualTo(sprintId); 18 | return this; 19 | } 20 | 21 | public BacklogItemAssert isNotCommited() { 22 | assertThat(actual.getSprintId()).isNull(); 23 | return this; 24 | } 25 | 26 | public BacklogItemAssert backlogItemCommitedPublished(SprintId sprintId) { 27 | assertThat(actual).published(new BacklogItemCommited(actual.getId(), sprintId)); 28 | return this; 29 | } 30 | 31 | public BacklogItemAssert backlogItemUncommitedPublished(SprintId sprintId) { 32 | assertThat(actual).published(new BacklogItemUncommited(actual.getId(), sprintId)); 33 | return this; 34 | } 35 | 36 | public BacklogItemAssert eventNotPublished() { 37 | assertThat(actual).notPublished(); 38 | return this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/backlogitem/BacklogItemBuilder.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import example.scrumboard.domain.product.Product; 4 | import example.scrumboard.domain.product.ProductBuilder; 5 | import example.scrumboard.domain.sprint.Sprint; 6 | 7 | public class BacklogItemBuilder { 8 | 9 | private BacklogItemId id = new BacklogItemId("any id"); 10 | 11 | private Product product = new ProductBuilder().build(); 12 | 13 | private String name = "any name"; 14 | 15 | private Sprint sprint; 16 | 17 | public BacklogItemBuilder withId(BacklogItemId id) { 18 | this.id = id; 19 | return this; 20 | } 21 | 22 | public BacklogItemBuilder withProduct(Product product) { 23 | this.product = product; 24 | return this; 25 | } 26 | 27 | public BacklogItemBuilder withName(String name) { 28 | this.name = name; 29 | return this; 30 | } 31 | 32 | public BacklogItemBuilder commitedToSprint(Sprint sprint) { 33 | this.sprint = sprint; 34 | return this; 35 | } 36 | 37 | public BacklogItem build() { 38 | return new BacklogItem(id, product, name, sprint); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/backlogitem/BacklogItemTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.backlogitem; 2 | 3 | import static com.googlecode.catchexception.CatchException.catchException; 4 | import static com.googlecode.catchexception.CatchException.caughtException; 5 | import static example.scrumboard.domain.ScrumBoardBuilders.givenSprint; 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | import org.testng.annotations.Test; 9 | 10 | import example.scrumboard.TestGroups; 11 | import example.scrumboard.domain.sprint.Sprint; 12 | import example.scrumboard.domain.sprint.SprintId; 13 | 14 | @Test(groups = TestGroups.UNIT) 15 | public class BacklogItemTest { 16 | 17 | private BacklogItem backlogItem; 18 | 19 | private BacklogItemBuilder backlogItemBuilder; 20 | 21 | public void shouldCommitToSprint() { 22 | SprintId sprintId = new SprintId("id"); 23 | Sprint sprint = givenSprint().withId(sprintId).build(); 24 | 25 | BacklogItemId backlogItemId = new BacklogItemId("id"); 26 | givenBacklogItem().withId(backlogItemId); 27 | 28 | whenBacklogItem().commitToSprint(sprint); 29 | 30 | // @formatter:off 31 | thenBacklogItem() 32 | .isCommitedToSprint(sprintId) 33 | .backlogItemCommitedPublished(sprintId); 34 | // @formatter:on 35 | } 36 | 37 | public void shouldNotCommitToSprintWhenNoChanges() { 38 | SprintId sprintId = new SprintId("id"); 39 | Sprint sprint = givenSprint().withId(sprintId).build(); 40 | 41 | BacklogItemId backlogItemId = new BacklogItemId("id"); 42 | givenBacklogItem().withId(backlogItemId).commitedToSprint(sprint); 43 | 44 | whenBacklogItem().commitToSprint(sprint); 45 | 46 | // @formatter:off 47 | thenBacklogItem() 48 | .isCommitedToSprint(sprint.getId()) 49 | .eventNotPublished(); 50 | // @formatter:on 51 | } 52 | 53 | public void shouldCommitToSprintWhenCommited() { 54 | SprintId oldSprintId = new SprintId("old"); 55 | Sprint oldSprint = givenSprint().withId(oldSprintId).build(); 56 | 57 | SprintId newSprintId = new SprintId("new"); 58 | Sprint newSprint = givenSprint().withId(newSprintId).build(); 59 | 60 | BacklogItemId backlogItemId = new BacklogItemId("id"); 61 | givenBacklogItem().withId(backlogItemId).commitedToSprint(oldSprint); 62 | 63 | whenBacklogItem().commitToSprint(newSprint); 64 | 65 | // @formatter:off 66 | thenBacklogItem() 67 | .isCommitedToSprint(newSprintId) 68 | .backlogItemUncommitedPublished(oldSprintId) 69 | .backlogItemCommitedPublished(newSprintId); 70 | // @formatter:on 71 | } 72 | 73 | public void shouldUncommitFromSprint() { 74 | SprintId sprintId = new SprintId("id"); 75 | Sprint sprint = givenSprint().withId(sprintId).build(); 76 | 77 | BacklogItemId backlogItemId = new BacklogItemId("id"); 78 | givenBacklogItem().withId(backlogItemId).commitedToSprint(sprint); 79 | 80 | whenBacklogItem().uncommitFromSprint(sprint); 81 | 82 | // @formatter:off 83 | thenBacklogItem() 84 | .isNotCommited() 85 | .backlogItemUncommitedPublished(sprintId); 86 | // @formatter:on 87 | } 88 | 89 | public void shouldNotUncommitFromSprintWhenNotCommited() { 90 | SprintId sprintId = new SprintId("id"); 91 | Sprint sprint = givenSprint().withId(sprintId).build(); 92 | 93 | BacklogItemId backlogItemId = new BacklogItemId("id"); 94 | givenBacklogItem().withId(backlogItemId); 95 | 96 | catchException(whenBacklogItem()).uncommitFromSprint(sprint); 97 | 98 | assertThat(caughtException()).isInstanceOf(IllegalArgumentException.class); 99 | thenBacklogItem().eventNotPublished(); 100 | } 101 | 102 | public void shouldNotUncommitFromSprintWhenNotCommitedToGivenSprint() { 103 | SprintId oldSprintId = new SprintId("old"); 104 | Sprint oldSprint = givenSprint().withId(oldSprintId).build(); 105 | 106 | SprintId newSprintId = new SprintId("new"); 107 | Sprint newSprint = givenSprint().withId(newSprintId).build(); 108 | 109 | BacklogItemId backlogItemId = new BacklogItemId("id"); 110 | givenBacklogItem().withId(backlogItemId).commitedToSprint(oldSprint); 111 | 112 | catchException(whenBacklogItem()).uncommitFromSprint(newSprint); 113 | 114 | assertThat(caughtException()).isInstanceOf(IllegalArgumentException.class); 115 | thenBacklogItem().eventNotPublished(); 116 | } 117 | 118 | private BacklogItemBuilder givenBacklogItem() { 119 | backlogItemBuilder = new BacklogItemBuilder(); 120 | return backlogItemBuilder; 121 | } 122 | 123 | private BacklogItem whenBacklogItem() { 124 | backlogItem = backlogItemBuilder.build(); 125 | return backlogItem; 126 | } 127 | 128 | private BacklogItemAssert thenBacklogItem() { 129 | return new BacklogItemAssert(backlogItem); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/product/ProductAssert.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import static example.ddd.domain.DddAssertions.assertThat; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import org.assertj.core.api.AbstractAssert; 7 | 8 | import com.google.common.base.Optional; 9 | import com.google.common.collect.Iterables; 10 | 11 | import example.scrumboard.domain.backlogitem.BacklogItemId; 12 | 13 | public class ProductAssert extends AbstractAssert { 14 | 15 | public ProductAssert(Product actual) { 16 | super(actual, ProductAssert.class); 17 | } 18 | 19 | public ProductAssert hasName(String name) { 20 | assertThat(actual.getName()).isEqualTo(name); 21 | return this; 22 | } 23 | 24 | public ProductAssert hasBacklogItem(BacklogItemId backlogItemId, int position) { 25 | Optional backlogItem = Iterables.tryFind(actual.getBacklogItems(), 26 | ProductBacklogItem.hasId(backlogItemId)); 27 | assertThat(backlogItem.orNull()).overridingErrorMessage("Backlog item %s not found", backlogItemId).isNotNull(); 28 | 29 | int actualPosition = backlogItem.get().getPosition(); 30 | assertThat(actualPosition).overridingErrorMessage( 31 | "Expected backlog item '%s' at position %s, but was at position %s", backlogItemId, position, 32 | actualPosition).isEqualTo(position); 33 | 34 | return this; 35 | } 36 | 37 | public ProductAssert backlogItemPlannedEventPublished(BacklogItemId backlogItemId) { 38 | assertThat(actual).published(new BacklogItemPlannedEvent(actual.getId(), backlogItemId)); 39 | return this; 40 | } 41 | 42 | public ProductAssert eventNotPublished() { 43 | assertThat(actual).notPublished(); 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/product/ProductBuilder.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | 7 | import example.scrumboard.domain.backlogitem.BacklogItemId; 8 | 9 | public class ProductBuilder { 10 | 11 | private ProductId id = new ProductId("any id"); 12 | 13 | private String name = "any name"; 14 | 15 | private List backlogItems = new ArrayList<>(); 16 | 17 | public ProductBuilder withId(ProductId id) { 18 | this.id = id; 19 | return this; 20 | } 21 | 22 | public ProductBuilder withName(String name) { 23 | this.name = name; 24 | return this; 25 | } 26 | 27 | public ProductBuilder addBacklogItem(BacklogItemId id) { 28 | int position = backlogItems.size(); 29 | backlogItems.add(new ProductBacklogItem(id, position)); 30 | return this; 31 | } 32 | 33 | public Product build() { 34 | return new Product(id, name, new HashSet<>(backlogItems)); 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/product/ProductTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.product; 2 | 3 | import static com.googlecode.catchexception.CatchException.catchException; 4 | import static com.googlecode.catchexception.CatchException.caughtException; 5 | import static example.scrumboard.domain.ScrumBoardBuilders.givenBacklogItem; 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | import org.testng.annotations.Test; 9 | 10 | import example.scrumboard.TestGroups; 11 | import example.scrumboard.domain.backlogitem.BacklogItem; 12 | import example.scrumboard.domain.backlogitem.BacklogItemId; 13 | 14 | @Test(groups = TestGroups.UNIT) 15 | public class ProductTest { 16 | 17 | private Product product; 18 | 19 | private ProductBuilder productBuilder; 20 | 21 | public void shouldAssignFirstBacklogItem() { 22 | BacklogItemId backlogItemId = new BacklogItemId("id"); 23 | BacklogItem backlogItem = givenBacklogItem().withId(backlogItemId).build(); 24 | 25 | givenProduct(); 26 | 27 | whenProduct().planBacklogItem(backlogItem); 28 | 29 | // @formatter:off 30 | thenProduct() 31 | .hasBacklogItem(backlogItemId, 0) 32 | .backlogItemPlannedEventPublished(backlogItemId); 33 | // @formatter:on 34 | } 35 | 36 | public void shouldAssignSecondBacklogItem() { 37 | BacklogItemId backlogItemId = new BacklogItemId("id"); 38 | BacklogItem backlogItem = givenBacklogItem().withId(backlogItemId).build(); 39 | 40 | givenProduct().addBacklogItem(new BacklogItemId("existing id")); 41 | 42 | whenProduct().planBacklogItem(backlogItem); 43 | 44 | // @formatter:off 45 | thenProduct() 46 | .hasBacklogItem(backlogItemId, 1) 47 | .backlogItemPlannedEventPublished(backlogItemId); 48 | // @formatter:on 49 | } 50 | 51 | public void shouldNotAssignExistingBacklogItem() { 52 | BacklogItemId backlogItemId = new BacklogItemId("id"); 53 | BacklogItem backlogItem = givenBacklogItem().withId(backlogItemId).build(); 54 | 55 | givenProduct().addBacklogItem(backlogItemId); 56 | 57 | catchException(whenProduct()).planBacklogItem(backlogItem); 58 | 59 | assertThat(caughtException()).isInstanceOf(IllegalArgumentException.class); 60 | thenProduct().eventNotPublished(); 61 | } 62 | 63 | public void shouldReorder() { 64 | BacklogItemId backlogItemId0 = new BacklogItemId("0"); 65 | BacklogItemId backlogItemId1 = new BacklogItemId("1"); 66 | BacklogItemId backlogItemId2 = new BacklogItemId("2"); 67 | 68 | // @formatter:off 69 | givenProduct() 70 | .addBacklogItem(backlogItemId0) 71 | .addBacklogItem(backlogItemId1) 72 | .addBacklogItem(backlogItemId2); 73 | // @formatter:on 74 | 75 | whenProduct().reorderBacklogItems(backlogItemId2, backlogItemId1, backlogItemId0); 76 | 77 | // @formatter:off 78 | thenProduct() 79 | .hasBacklogItem(backlogItemId0, 2) 80 | .hasBacklogItem(backlogItemId1, 1) 81 | .hasBacklogItem(backlogItemId2, 0) 82 | .eventNotPublished(); 83 | // @formatter:on 84 | } 85 | 86 | public void shouldNotReorderWhenNoChanges() { 87 | BacklogItemId backlogItemId0 = new BacklogItemId("0"); 88 | BacklogItemId backlogItemId1 = new BacklogItemId("1"); 89 | BacklogItemId backlogItemId2 = new BacklogItemId("2"); 90 | 91 | // @formatter:off 92 | givenProduct() 93 | .addBacklogItem(backlogItemId0) 94 | .addBacklogItem(backlogItemId1) 95 | .addBacklogItem(backlogItemId2); 96 | // @formatter:on 97 | 98 | whenProduct().reorderBacklogItems(backlogItemId0, backlogItemId1, backlogItemId2); 99 | 100 | // @formatter:off 101 | thenProduct() 102 | .hasBacklogItem(backlogItemId0, 0) 103 | .hasBacklogItem(backlogItemId1, 1) 104 | .hasBacklogItem(backlogItemId2, 2) 105 | .eventNotPublished(); 106 | // @formatter:on 107 | } 108 | 109 | public void shouldNotReorderNonExistingBacklogItem() { 110 | BacklogItemId backlogItemId0 = new BacklogItemId("0"); 111 | BacklogItemId backlogItemId1 = new BacklogItemId("1"); 112 | 113 | givenProduct().addBacklogItem(backlogItemId0); 114 | 115 | catchException(whenProduct()).reorderBacklogItems(backlogItemId1); 116 | assertThat(caughtException()).isInstanceOf(IllegalArgumentException.class); 117 | 118 | thenProduct().eventNotPublished(); 119 | } 120 | 121 | private ProductBuilder givenProduct() { 122 | productBuilder = new ProductBuilder(); 123 | return productBuilder; 124 | } 125 | 126 | private Product whenProduct() { 127 | this.product = productBuilder.build(); 128 | return product; 129 | } 130 | 131 | private ProductAssert thenProduct() { 132 | return new ProductAssert(product); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/domain/sprint/SprintBuilder.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.domain.sprint; 2 | 3 | import java.util.Date; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import org.joda.time.format.DateTimeFormat; 8 | import org.joda.time.format.DateTimeFormatter; 9 | 10 | import example.scrumboard.domain.product.Product; 11 | import example.scrumboard.domain.product.ProductBuilder; 12 | 13 | public class SprintBuilder { 14 | 15 | private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("YYYY-MM-DD"); 16 | 17 | private SprintId id = new SprintId("any id"); 18 | 19 | private Product product = new ProductBuilder().build(); 20 | 21 | private String name = "any name"; 22 | 23 | private Date beginDate = DATE_TIME_FORMATTER.parseDateTime("2010-01-01").toDate(); 24 | 25 | private Date endDate = DATE_TIME_FORMATTER.parseDateTime("2010-01-14").toDate(); 26 | 27 | private Set backlogItems = new HashSet<>(); 28 | 29 | public SprintBuilder withId(SprintId id) { 30 | this.id = id; 31 | return this; 32 | } 33 | 34 | public SprintBuilder withProduct(Product product) { 35 | this.product = product; 36 | return this; 37 | } 38 | 39 | public SprintBuilder withName(String name) { 40 | this.name = name; 41 | return this; 42 | } 43 | 44 | public SprintBuilder withBeginDate(String beginDate) { 45 | this.beginDate = DATE_TIME_FORMATTER.parseDateTime(beginDate).toDate(); 46 | return this; 47 | } 48 | 49 | public SprintBuilder withEndDate(String endDate) { 50 | this.endDate = DATE_TIME_FORMATTER.parseDateTime(endDate).toDate(); 51 | return this; 52 | } 53 | 54 | public SprintBuilder addBacklogItem(CommitedBacklogItem backlogItem) { 55 | this.backlogItems.add(backlogItem); 56 | return this; 57 | } 58 | 59 | public Sprint build() { 60 | return new Sprint(id, product, name, beginDate, endDate, backlogItems); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/infrastructure/jpa/JpaTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.test.context.ContextConfiguration; 9 | 10 | @ContextConfiguration(classes = JpaTestConfig.class) 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface JpaTest { 14 | } -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/infrastructure/jpa/JpaTestConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.mockito.Mockito; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Import; 8 | import org.springframework.context.annotation.PropertySource; 9 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 10 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 11 | 12 | import example.ddd.EventPublisher; 13 | import example.scrumboard.infrastructure.shared.ScrumBoardInfrastructureTransactionConfig; 14 | 15 | @Import({ ScrumBoardInfrastructureJpaConfig.class, ScrumBoardInfrastructureTransactionConfig.class }) 16 | @PropertySource("classpath:/test.properties") 17 | public class JpaTestConfig { 18 | 19 | @Bean 20 | public DataSource dataSource() { 21 | return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2) 22 | .setName(JpaTestConfig.class.getSimpleName()).build(); 23 | } 24 | 25 | @Bean 26 | public EventPublisher PublishSubscribeChannel() { 27 | return Mockito.mock(EventPublisher.class); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/infrastructure/jpa/repositories/AbstractJpaRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import javax.persistence.EntityManager; 4 | import javax.persistence.PersistenceContext; 5 | import javax.persistence.TypedQuery; 6 | 7 | import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; 8 | import org.testng.annotations.Test; 9 | 10 | import example.scrumboard.TestGroups; 11 | 12 | @Test(groups = TestGroups.INTEGRATION) 13 | public abstract class AbstractJpaRepositoryTest extends AbstractTransactionalTestNGSpringContextTests { 14 | 15 | @PersistenceContext 16 | private EntityManager entityManager; 17 | 18 | protected void clear() { 19 | entityManager.clear(); 20 | } 21 | 22 | protected Long countEntities(Class entityClass) { 23 | StringBuffer queryString = new StringBuffer("SELECT count(e) from ") // 24 | .append(entityClass.getSimpleName()) // 25 | .append(" e"); 26 | 27 | TypedQuery query = entityManager.createQuery(queryString.toString(), Long.class); 28 | return query.getSingleResult(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/infrastructure/jpa/repositories/JpaProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.infrastructure.jpa.repositories; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.dao.DataIntegrityViolationException; 7 | import org.testng.annotations.Test; 8 | 9 | import example.scrumboard.domain.backlogitem.BacklogItemId; 10 | import example.scrumboard.domain.product.Product; 11 | import example.scrumboard.domain.product.ProductAssert; 12 | import example.scrumboard.domain.product.ProductBacklogItem; 13 | import example.scrumboard.domain.product.ProductBuilder; 14 | import example.scrumboard.domain.product.ProductId; 15 | import example.scrumboard.domain.product.ProductRepository; 16 | import example.scrumboard.infrastructure.jpa.JpaTest; 17 | 18 | @JpaTest 19 | public class JpaProductRepositoryTest extends AbstractJpaRepositoryTest { 20 | 21 | @Autowired 22 | private ProductRepository repository; 23 | 24 | public void shouldSaveProduct() { 25 | // given 26 | ProductId id = new ProductId("id"); 27 | String name = "name"; 28 | BacklogItemId backlogItemId1 = new BacklogItemId("id1"); 29 | BacklogItemId backlogItemId2 = new BacklogItemId("id2"); 30 | 31 | // @formatter:off 32 | Product product = givenProduct() 33 | .withId(id) 34 | .withName(name) 35 | .addBacklogItem(backlogItemId1) 36 | .addBacklogItem(backlogItemId2) 37 | .build(); 38 | // @formatter:on 39 | 40 | // when 41 | repository.save(product); 42 | 43 | // @formatter:off 44 | thenProduct(id) 45 | .hasName(name) 46 | .hasBacklogItem(backlogItemId1, 0) 47 | .hasBacklogItem(backlogItemId2, 1); 48 | // @formatter:on 49 | } 50 | 51 | @Test(expectedExceptions = DataIntegrityViolationException.class) 52 | public void shouldNotSaveProductWithDuplicatedName() { 53 | String name = "name"; 54 | Product product1 = givenProduct().withId(new ProductId("id1")).withName(name).build(); 55 | Product product2 = givenProduct().withId(new ProductId("id2")).withName(name).build(); 56 | 57 | // when 58 | repository.save(product1); 59 | repository.save(product2); 60 | } 61 | 62 | public void shouldDeleteProduct() { 63 | ProductId id = new ProductId("id"); 64 | 65 | // @formatter:off 66 | Product product = givenProduct() 67 | .withId(id) 68 | .addBacklogItem(new BacklogItemId("id1")) 69 | .addBacklogItem(new BacklogItemId("id2")) 70 | .build(); 71 | // @formatter:on 72 | 73 | repository.save(product); 74 | repository.delete(product); 75 | 76 | assertThat(countEntities(Product.class)).isEqualTo(0); 77 | assertThat(countEntities(ProductBacklogItem.class)).isEqualTo(0); 78 | } 79 | 80 | private ProductBuilder givenProduct() { 81 | return new ProductBuilder(); 82 | } 83 | 84 | private ProductAssert thenProduct(ProductId id) { 85 | clear(); 86 | return new ProductAssert(repository.load(id)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/rest/AbstractControllerTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; 5 | import org.springframework.test.web.servlet.MockMvc; 6 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 7 | import org.springframework.web.context.WebApplicationContext; 8 | import org.testng.annotations.BeforeMethod; 9 | import org.testng.annotations.Test; 10 | 11 | import com.fasterxml.jackson.core.JsonProcessingException; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | 14 | import example.scrumboard.TestGroups; 15 | 16 | @Test(groups = TestGroups.INTEGRATION) 17 | public abstract class AbstractControllerTest extends AbstractTestNGSpringContextTests { 18 | 19 | @Autowired 20 | private WebApplicationContext webApplicationContext; 21 | 22 | @Autowired 23 | private ObjectMapper objectMapper; 24 | 25 | private MockMvc mockMvc; 26 | 27 | protected MockMvc getMockMvc() { 28 | return mockMvc; 29 | } 30 | 31 | protected String toJson(Object object) { 32 | try { 33 | return objectMapper.writeValueAsString(object); 34 | } catch (JsonProcessingException e) { 35 | throw new RuntimeException("Could not create JSON representation for " + object + ".", e); 36 | } 37 | } 38 | 39 | @BeforeMethod 40 | protected void setup() { 41 | mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/rest/commands/RestCommandTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.commands; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.web.WebAppConfiguration; 10 | 11 | @WebAppConfiguration 12 | @ContextConfiguration(classes = RestCommandTestConfig.class) 13 | @Target(ElementType.TYPE) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface RestCommandTest { 16 | } -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/rest/commands/RestCommandTestConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.commands; 2 | 3 | import org.mockito.Mockito; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Import; 6 | import org.springframework.context.annotation.PropertySource; 7 | 8 | import example.scrumboard.application.api.ProductService; 9 | import example.scrumboard.application.api.ReleaseService; 10 | import example.scrumboard.application.api.SprintService; 11 | import example.scrumboard.infrastructure.rest.ScrumBoardInfrastructureRestConfig; 12 | 13 | @Import({ ScrumBoardInfrastructureRestConfig.class, ScrumBoardRestCommandsConfig.class }) 14 | @PropertySource("classpath:/test.properties") 15 | public class RestCommandTestConfig { 16 | 17 | @Bean 18 | public ProductService productService() { 19 | return Mockito.mock(ProductService.class); 20 | } 21 | 22 | @Bean 23 | public ReleaseService releaseService() { 24 | return Mockito.mock(ReleaseService.class); 25 | } 26 | 27 | @Bean 28 | public SprintService sprintService() { 29 | return Mockito.mock(SprintService.class); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/rest/commands/product/ProductCommandControllerTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.commands.product; 2 | 3 | import static org.mockito.Matchers.eq; 4 | import static org.mockito.Mockito.verify; 5 | import static org.mockito.Mockito.when; 6 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 8 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 9 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 10 | 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.MediaType; 13 | 14 | import example.scrumboard.application.api.ProductService; 15 | import example.scrumboard.application.api.commands.CreateProductCommand; 16 | import example.scrumboard.application.api.commands.PlanBacklogItemCommand; 17 | import example.scrumboard.application.api.commands.ReorderBacklogItemsCommand; 18 | import example.scrumboard.domain.backlogitem.BacklogItemId; 19 | import example.scrumboard.domain.product.ProductId; 20 | import example.scrumboard.rest.AbstractControllerTest; 21 | import example.scrumboard.rest.commands.RestCommandTest; 22 | 23 | @RestCommandTest 24 | public class ProductCommandControllerTest extends AbstractControllerTest { 25 | 26 | private static final ProductId ANY_PRODUCT_ID = new ProductId("any product id"); 27 | 28 | private static final BacklogItemId ANY_BACKLOG_ITEM_ID = new BacklogItemId("any backlog item id"); 29 | 30 | @Autowired 31 | private ProductService productService; 32 | 33 | public void createProduct() throws Exception { 34 | CreateProductCommand command = givenCreateProductCommand(); 35 | 36 | when(productService.createProduct(eq(command))).thenReturn(ANY_PRODUCT_ID); 37 | 38 | // @formatter:off 39 | getMockMvc().perform(post("/products") 40 | .content(toJson(command)).contentType(MediaType.APPLICATION_JSON) 41 | .accept(MediaType.APPLICATION_JSON)) 42 | //.andDo(print()) 43 | .andExpect(status().isCreated()) 44 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) 45 | .andExpect(jsonPath("$id").value(ANY_PRODUCT_ID.getId())); 46 | // @formatter:on 47 | } 48 | 49 | public void planBacklogItem() throws Exception { 50 | PlanBacklogItemCommand command = givenPlanBacklogItemCommand(); 51 | 52 | when(productService.planBacklogItem(eq(ANY_PRODUCT_ID), eq(command))).thenReturn(ANY_BACKLOG_ITEM_ID); 53 | 54 | // @formatter:off 55 | getMockMvc().perform(post("/products/{productId}/backlogItems", ANY_PRODUCT_ID.getId()) 56 | .content(toJson(command)).contentType(MediaType.APPLICATION_JSON) 57 | .accept(MediaType.APPLICATION_JSON)) 58 | //.andDo(print()) 59 | .andExpect(status().isCreated()) 60 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) 61 | .andExpect(jsonPath("$id").value(ANY_BACKLOG_ITEM_ID.getId())); 62 | // @formatter:on 63 | } 64 | 65 | public void reorderBacklogItems() throws Exception { 66 | ReorderBacklogItemsCommand command = givenReorderBacklogItemsCommand(); 67 | 68 | // @formatter:off 69 | getMockMvc().perform(post("/products/{productId}/reorderBacklogItems", ANY_PRODUCT_ID.getId()) 70 | .content(toJson(command)).contentType(MediaType.APPLICATION_JSON) 71 | .accept(MediaType.APPLICATION_JSON)) 72 | //.andDo(print()) 73 | .andExpect(status().isOk()); 74 | // @formatter:on 75 | 76 | verify(productService).reorderBacklogItems(eq(ANY_PRODUCT_ID), eq(command)); 77 | } 78 | 79 | private CreateProductCommand givenCreateProductCommand() { 80 | CreateProductCommand command = new CreateProductCommand(); 81 | command.setProductName("any product name"); 82 | return command; 83 | } 84 | 85 | private PlanBacklogItemCommand givenPlanBacklogItemCommand() { 86 | PlanBacklogItemCommand command = new PlanBacklogItemCommand(); 87 | command.setBacklogItemStory("any backlog item story"); 88 | return command; 89 | } 90 | 91 | private ReorderBacklogItemsCommand givenReorderBacklogItemsCommand() { 92 | ReorderBacklogItemsCommand command = new ReorderBacklogItemsCommand(); 93 | return command; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/rest/queries/RestQueryTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.queries; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.web.WebAppConfiguration; 10 | 11 | @WebAppConfiguration 12 | @ContextConfiguration(classes = RestQueryTestConfig.class) 13 | @Target(ElementType.TYPE) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface RestQueryTest { 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/rest/queries/RestQueryTestConfig.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.queries; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Import; 7 | import org.springframework.context.annotation.PropertySource; 8 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 9 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 10 | 11 | import example.scrumboard.infrastructure.rest.ScrumBoardInfrastructureRestConfig; 12 | 13 | @Import({ ScrumBoardInfrastructureRestConfig.class, ScrumBoardRestQueriesConfig.class }) 14 | @PropertySource("classpath:/test.properties") 15 | public class RestQueryTestConfig { 16 | 17 | @Bean 18 | public DataSource dataSource() { 19 | return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2) 20 | .setName(RestQueryTestConfig.class.getSimpleName()).addScript("sample.sql").build(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/example/scrumboard/rest/queries/product/ProductQueryControllerTest.java: -------------------------------------------------------------------------------- 1 | package example.scrumboard.rest.queries.product; 2 | 3 | import static com.jayway.jsonpath.Criteria.where; 4 | import static com.jayway.jsonpath.Filter.filter; 5 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 6 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 8 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 9 | 10 | import org.springframework.http.MediaType; 11 | import org.testng.annotations.Test; 12 | 13 | import com.jayway.jsonpath.JsonPath; 14 | 15 | import example.scrumboard.rest.AbstractControllerTest; 16 | import example.scrumboard.rest.queries.RestQueryTest; 17 | 18 | @RestQueryTest 19 | public class ProductQueryControllerTest extends AbstractControllerTest { 20 | 21 | private String productsJson; 22 | 23 | public void shouldFindProducts() throws Exception { 24 | // @formatter:off 25 | productsJson = getMockMvc().perform(get("/products").accept(MediaType.APPLICATION_JSON)) 26 | //.andDo(print()) 27 | .andExpect(status().isOk()) 28 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) 29 | .andExpect(jsonPath("$totalElements").value(3)) 30 | .andExpect(jsonPath("$content[0].productName").value("Example DDD/CQRS client")) 31 | .andExpect(jsonPath("$content[1].productName").value("Example DDD/CQRS server")) 32 | .andExpect(jsonPath("$content[2].productName").value("Product with no backlog items")) 33 | .andReturn().getResponse().getContentAsString(); 34 | // @formatter:on 35 | } 36 | 37 | @Test(dependsOnMethods = "shouldFindProducts") 38 | public void shouldFindProduct() throws Exception { 39 | String productName = "Example DDD/CQRS server"; 40 | String productId = findProductIdByName(productName); 41 | 42 | // @formatter:off 43 | getMockMvc().perform(get("/products/{productId}", productId) 44 | .accept(MediaType.APPLICATION_JSON)) 45 | //.andDo(print()) 46 | .andExpect(status().isOk()) 47 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) 48 | .andExpect(jsonPath("$productId").value(productId)) 49 | .andExpect(jsonPath("$productName").value(productName)); 50 | // @formatter:on 51 | } 52 | 53 | @Test(dependsOnMethods = "shouldFindProducts") 54 | public void shouldFindBacklogItems() throws Exception { 55 | String productName = "Example DDD/CQRS server"; 56 | String productId = findProductIdByName(productName); 57 | 58 | // @formatter:off 59 | getMockMvc().perform(get("/products/{productId}/backlogItems", productId) 60 | .accept(MediaType.APPLICATION_JSON)) 61 | //.andDo(print()) 62 | .andExpect(status().isOk()) 63 | .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) 64 | .andExpect(jsonPath("$[0].backlogItemStory").value("Write documentation")) 65 | .andExpect(jsonPath("$[0].backlogItemPosition").value(0)) 66 | .andExpect(jsonPath("$[1].backlogItemStory").value("Add more unit tests")) 67 | .andExpect(jsonPath("$[1].backlogItemPosition").value(1)); 68 | // @formatter:on 69 | } 70 | 71 | private String findProductIdByName(String name) { 72 | return (String) JsonPath.read(productsJson, "$content[?].productId[0]", filter(where("productName").is(name))); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/test/resources/sample.sql: -------------------------------------------------------------------------------- 1 | SET DB_CLOSE_DELAY -1; 2 | ; 3 | CREATE USER IF NOT EXISTS SA SALT '156da56d219be548' HASH '502c79fad489d809b3a5fb71dd92c92ae803ec359bfdadc3dc74983fca227ba7' ADMIN; 4 | CREATE SEQUENCE PUBLIC.SYSTEM_SEQUENCE_309235C0_29B5_45D9_8DF8_EFA82BBE245F START WITH 1 BELONGS_TO_TABLE; 5 | CREATE SEQUENCE PUBLIC.SYSTEM_SEQUENCE_2D04285B_C0F2_45FD_AB2F_84F596E49B76 START WITH 1 BELONGS_TO_TABLE; 6 | CREATE SEQUENCE PUBLIC.SYSTEM_SEQUENCE_99DBA090_0E8D_48A3_B0FB_B8FB0EB1E64F START WITH 5 BELONGS_TO_TABLE; 7 | CREATE SEQUENCE PUBLIC.SYSTEM_SEQUENCE_2C7660DF_4BF3_439C_89E6_8447074213ED START WITH 1 BELONGS_TO_TABLE; 8 | CREATE MEMORY TABLE PUBLIC.T_BACKLOG_ITEM( 9 | C_BACKLOG_ITEM_ID VARCHAR(255) NOT NULL, 10 | C_VERSION INTEGER, 11 | C_PRODUCT_ID VARCHAR(255) NOT NULL, 12 | C_ID VARCHAR(255), 13 | C_STORY VARCHAR(255) NOT NULL 14 | ); 15 | ALTER TABLE PUBLIC.T_BACKLOG_ITEM ADD CONSTRAINT PUBLIC.CONSTRAINT_7 PRIMARY KEY(C_BACKLOG_ITEM_ID); 16 | -- 4 +/- SELECT COUNT(*) FROM PUBLIC.T_BACKLOG_ITEM; 17 | INSERT INTO PUBLIC.T_BACKLOG_ITEM(C_BACKLOG_ITEM_ID, C_VERSION, C_PRODUCT_ID, C_ID, C_STORY) VALUES('adfefe13-67ec-4bd1-a5b6-680ae0360feb', 0, 'd1a06697-0914-48b5-969c-52967f7c6f92', NULL, 'Write documentation'); 18 | INSERT INTO PUBLIC.T_BACKLOG_ITEM(C_BACKLOG_ITEM_ID, C_VERSION, C_PRODUCT_ID, C_ID, C_STORY) VALUES('0cdc4b57-5624-4778-a1b6-c65a7fd01c0e', 0, 'd1a06697-0914-48b5-969c-52967f7c6f92', NULL, 'Add more unit tests'); 19 | INSERT INTO PUBLIC.T_BACKLOG_ITEM(C_BACKLOG_ITEM_ID, C_VERSION, C_PRODUCT_ID, C_ID, C_STORY) VALUES('d81d9671-7a43-4cbe-8c10-30706574c7df', 0, '7c5276e2-afe6-4068-8be8-826eee89cf7e', NULL, 'Apply Twitter Bootstrap'); 20 | INSERT INTO PUBLIC.T_BACKLOG_ITEM(C_BACKLOG_ITEM_ID, C_VERSION, C_PRODUCT_ID, C_ID, C_STORY) VALUES('1c49cd4b-4e56-409b-a679-df48113862fd', 0, '7c5276e2-afe6-4068-8be8-826eee89cf7e', NULL, 'Create Angular controllers'); 21 | CREATE MEMORY TABLE PUBLIC.T_COMMITED_BACKLOG_ITEM( 22 | C_ENTITY_ID BIGINT DEFAULT (NEXT VALUE FOR PUBLIC.SYSTEM_SEQUENCE_2C7660DF_4BF3_439C_89E6_8447074213ED) NOT NULL NULL_TO_DEFAULT SEQUENCE PUBLIC.SYSTEM_SEQUENCE_2C7660DF_4BF3_439C_89E6_8447074213ED, 23 | C_BACKLOG_ITEM_ID VARCHAR(255) NOT NULL, 24 | C_SPRINT_ID VARCHAR(255) NOT NULL 25 | ); 26 | ALTER TABLE PUBLIC.T_COMMITED_BACKLOG_ITEM ADD CONSTRAINT PUBLIC.CONSTRAINT_9 PRIMARY KEY(C_ENTITY_ID); 27 | -- 0 +/- SELECT COUNT(*) FROM PUBLIC.T_COMMITED_BACKLOG_ITEM; 28 | CREATE MEMORY TABLE PUBLIC.T_PRODUCT( 29 | C_PRODUCT_ID VARCHAR(255) NOT NULL, 30 | C_VERSION INTEGER, 31 | C_NAME VARCHAR(255) NOT NULL 32 | ); 33 | ALTER TABLE PUBLIC.T_PRODUCT ADD CONSTRAINT PUBLIC.CONSTRAINT_4 PRIMARY KEY(C_PRODUCT_ID); 34 | -- 3 +/- SELECT COUNT(*) FROM PUBLIC.T_PRODUCT; 35 | INSERT INTO PUBLIC.T_PRODUCT(C_PRODUCT_ID, C_VERSION, C_NAME) VALUES('d1a06697-0914-48b5-969c-52967f7c6f92', 4, 'Example DDD/CQRS server'); 36 | INSERT INTO PUBLIC.T_PRODUCT(C_PRODUCT_ID, C_VERSION, C_NAME) VALUES('7c5276e2-afe6-4068-8be8-826eee89cf7e', 4, 'Example DDD/CQRS client'); 37 | INSERT INTO PUBLIC.T_PRODUCT(C_PRODUCT_ID, C_VERSION, C_NAME) VALUES('6bd24930-e051-47d8-87d0-0df9391cfaa7', 0, 'Product with no backlog items'); 38 | CREATE MEMORY TABLE PUBLIC.T_PRODUCT_BACKLOG_ITEM( 39 | C_ENTITY_ID BIGINT DEFAULT (NEXT VALUE FOR PUBLIC.SYSTEM_SEQUENCE_99DBA090_0E8D_48A3_B0FB_B8FB0EB1E64F) NOT NULL NULL_TO_DEFAULT SEQUENCE PUBLIC.SYSTEM_SEQUENCE_99DBA090_0E8D_48A3_B0FB_B8FB0EB1E64F, 40 | C_BACKLOG_ITEM_ID VARCHAR(255) NOT NULL, 41 | C_POSITION INTEGER NOT NULL, 42 | C_PRODUCT_ID VARCHAR(255) NOT NULL 43 | ); 44 | ALTER TABLE PUBLIC.T_PRODUCT_BACKLOG_ITEM ADD CONSTRAINT PUBLIC.CONSTRAINT_4B PRIMARY KEY(C_ENTITY_ID); 45 | -- 4 +/- SELECT COUNT(*) FROM PUBLIC.T_PRODUCT_BACKLOG_ITEM; 46 | INSERT INTO PUBLIC.T_PRODUCT_BACKLOG_ITEM(C_ENTITY_ID, C_BACKLOG_ITEM_ID, C_POSITION, C_PRODUCT_ID) VALUES(1, 'adfefe13-67ec-4bd1-a5b6-680ae0360feb', 0, 'd1a06697-0914-48b5-969c-52967f7c6f92'); 47 | INSERT INTO PUBLIC.T_PRODUCT_BACKLOG_ITEM(C_ENTITY_ID, C_BACKLOG_ITEM_ID, C_POSITION, C_PRODUCT_ID) VALUES(2, '0cdc4b57-5624-4778-a1b6-c65a7fd01c0e', 1, 'd1a06697-0914-48b5-969c-52967f7c6f92'); 48 | INSERT INTO PUBLIC.T_PRODUCT_BACKLOG_ITEM(C_ENTITY_ID, C_BACKLOG_ITEM_ID, C_POSITION, C_PRODUCT_ID) VALUES(3, 'd81d9671-7a43-4cbe-8c10-30706574c7df', 0, '7c5276e2-afe6-4068-8be8-826eee89cf7e'); 49 | INSERT INTO PUBLIC.T_PRODUCT_BACKLOG_ITEM(C_ENTITY_ID, C_BACKLOG_ITEM_ID, C_POSITION, C_PRODUCT_ID) VALUES(4, '1c49cd4b-4e56-409b-a679-df48113862fd', 1, '7c5276e2-afe6-4068-8be8-826eee89cf7e'); 50 | CREATE MEMORY TABLE PUBLIC.T_RELEASE( 51 | C_RELEASE_ID VARCHAR(255) NOT NULL, 52 | C_VERSION INTEGER, 53 | C_NAME VARCHAR(255) NOT NULL, 54 | C_PRODUCT_ID VARCHAR(255) NOT NULL, 55 | C_RELEASE_DATE TIMESTAMP NOT NULL 56 | ); 57 | ALTER TABLE PUBLIC.T_RELEASE ADD CONSTRAINT PUBLIC.CONSTRAINT_9E PRIMARY KEY(C_RELEASE_ID); 58 | -- 2 +/- SELECT COUNT(*) FROM PUBLIC.T_RELEASE; 59 | INSERT INTO PUBLIC.T_RELEASE(C_RELEASE_ID, C_VERSION, C_NAME, C_PRODUCT_ID, C_RELEASE_DATE) VALUES('c1c1bf0e-b397-4dfb-9d1d-3e7a5f207219', 0, 'Release 1', 'd1a06697-0914-48b5-969c-52967f7c6f92', TIMESTAMP '2011-01-14 00:00:00.0'); 60 | INSERT INTO PUBLIC.T_RELEASE(C_RELEASE_ID, C_VERSION, C_NAME, C_PRODUCT_ID, C_RELEASE_DATE) VALUES('f85dcccf-2eb0-4ba5-9615-ff45701089bb', 0, 'Release 2', 'd1a06697-0914-48b5-969c-52967f7c6f92', TIMESTAMP '2011-01-29 00:00:00.0'); 61 | CREATE MEMORY TABLE PUBLIC.T_REMAINING_AMENDMENT( 62 | C_ENTITY_ID BIGINT DEFAULT (NEXT VALUE FOR PUBLIC.SYSTEM_SEQUENCE_309235C0_29B5_45D9_8DF8_EFA82BBE245F) NOT NULL NULL_TO_DEFAULT SEQUENCE PUBLIC.SYSTEM_SEQUENCE_309235C0_29B5_45D9_8DF8_EFA82BBE245F, 63 | C_EFFECTIVE_DATE TIMESTAMP NOT NULL, 64 | C_HOURS_REMAINING INTEGER NOT NULL, 65 | C_TASK_ID VARCHAR(255) NOT NULL, 66 | C_REMAINING_AMENDMENTS_ORDER INTEGER 67 | ); 68 | ALTER TABLE PUBLIC.T_REMAINING_AMENDMENT ADD CONSTRAINT PUBLIC.CONSTRAINT_E PRIMARY KEY(C_ENTITY_ID); 69 | -- 0 +/- SELECT COUNT(*) FROM PUBLIC.T_REMAINING_AMENDMENT; 70 | CREATE MEMORY TABLE PUBLIC.T_SCHEDULED_BACKLOG_ITEM( 71 | C_ENTITY_ID BIGINT DEFAULT (NEXT VALUE FOR PUBLIC.SYSTEM_SEQUENCE_2D04285B_C0F2_45FD_AB2F_84F596E49B76) NOT NULL NULL_TO_DEFAULT SEQUENCE PUBLIC.SYSTEM_SEQUENCE_2D04285B_C0F2_45FD_AB2F_84F596E49B76, 72 | C_BACKLOG_ITEM_ID VARCHAR(255) NOT NULL, 73 | C_RELEASE_ID VARCHAR(255) NOT NULL 74 | ); 75 | ALTER TABLE PUBLIC.T_SCHEDULED_BACKLOG_ITEM ADD CONSTRAINT PUBLIC.CONSTRAINT_3 PRIMARY KEY(C_ENTITY_ID); 76 | -- 0 +/- SELECT COUNT(*) FROM PUBLIC.T_SCHEDULED_BACKLOG_ITEM; 77 | CREATE MEMORY TABLE PUBLIC.T_SPRINT( 78 | C_SPRINT_ID VARCHAR(255) NOT NULL, 79 | C_VERSION INTEGER, 80 | C_BEGIN_DATE TIMESTAMP NOT NULL, 81 | C_END_DATE TIMESTAMP NOT NULL, 82 | C_NAME VARCHAR(255) NOT NULL, 83 | C_PRODUCT_ID VARCHAR(255) NOT NULL 84 | ); 85 | ALTER TABLE PUBLIC.T_SPRINT ADD CONSTRAINT PUBLIC.CONSTRAINT_49 PRIMARY KEY(C_SPRINT_ID); 86 | -- 2 +/- SELECT COUNT(*) FROM PUBLIC.T_SPRINT; 87 | INSERT INTO PUBLIC.T_SPRINT(C_SPRINT_ID, C_VERSION, C_BEGIN_DATE, C_END_DATE, C_NAME, C_PRODUCT_ID) VALUES('d4345cdc-12d3-4c94-a86b-dba2b0878d43', 0, TIMESTAMP '2011-01-01 00:00:00.0', TIMESTAMP '2011-01-14 00:00:00.0', 'Sprint 1', 'd1a06697-0914-48b5-969c-52967f7c6f92'); 88 | INSERT INTO PUBLIC.T_SPRINT(C_SPRINT_ID, C_VERSION, C_BEGIN_DATE, C_END_DATE, C_NAME, C_PRODUCT_ID) VALUES('1ba00f5c-3c21-4c35-92ae-24de16f79f87', 0, TIMESTAMP '2011-01-15 00:00:00.0', TIMESTAMP '2011-01-29 00:00:00.0', 'Sprint 2', 'd1a06697-0914-48b5-969c-52967f7c6f92'); 89 | CREATE MEMORY TABLE PUBLIC.T_TASK( 90 | C_TASK_ID VARCHAR(255) NOT NULL, 91 | C_VERSION INTEGER, 92 | C_BACKLOG_ITEM_ID VARCHAR(255) NOT NULL, 93 | C_HOURS_REMAINING INTEGER NOT NULL, 94 | C_NAME VARCHAR(255) NOT NULL, 95 | C_STATUS VARCHAR(255) 96 | ); 97 | ALTER TABLE PUBLIC.T_TASK ADD CONSTRAINT PUBLIC.CONSTRAINT_94 PRIMARY KEY(C_TASK_ID); 98 | -- 0 +/- SELECT COUNT(*) FROM PUBLIC.T_TASK; 99 | ALTER TABLE PUBLIC.T_PRODUCT ADD CONSTRAINT PUBLIC.UK_517KAENCTU69XIS4BIAT5V1IE UNIQUE(C_NAME); 100 | ALTER TABLE PUBLIC.T_REMAINING_AMENDMENT ADD CONSTRAINT PUBLIC.FK_4AQNANQB934G8924UEF9R2W38 FOREIGN KEY(C_TASK_ID) REFERENCES PUBLIC.T_TASK(C_TASK_ID) NOCHECK; 101 | ALTER TABLE PUBLIC.T_SCHEDULED_BACKLOG_ITEM ADD CONSTRAINT PUBLIC.FK_L651SROAE12LC4T3NUUL146PO FOREIGN KEY(C_RELEASE_ID) REFERENCES PUBLIC.T_RELEASE(C_RELEASE_ID) NOCHECK; 102 | ALTER TABLE PUBLIC.T_COMMITED_BACKLOG_ITEM ADD CONSTRAINT PUBLIC.FK_PP7D9JORD0YVLO9Q2OKJB7F3F FOREIGN KEY(C_SPRINT_ID) REFERENCES PUBLIC.T_SPRINT(C_SPRINT_ID) NOCHECK; 103 | ALTER TABLE PUBLIC.T_PRODUCT_BACKLOG_ITEM ADD CONSTRAINT PUBLIC.FK_AMF2TH7I5ENG88VUBPJ2DBIKM FOREIGN KEY(C_PRODUCT_ID) REFERENCES PUBLIC.T_PRODUCT(C_PRODUCT_ID) NOCHECK; 104 | -------------------------------------------------------------------------------- /src/test/resources/test.properties: -------------------------------------------------------------------------------- 1 | jpa.generateDdl=true 2 | jpa.showSql=false --------------------------------------------------------------------------------