├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── Aggregate Implementation Patterns Usecases.jpg ├── eventstorming.jpg └── test_scenarios.jpg └── src ├── main └── java │ └── domain │ ├── functional │ ├── es │ │ └── customer │ │ │ ├── Customer5.java │ │ │ ├── Customer6.java │ │ │ ├── Customer7.java │ │ │ └── CustomerState.java │ └── traditional │ │ └── customer │ │ ├── Customer2.java │ │ └── CustomerState.java │ ├── oop │ ├── es │ │ └── customer │ │ │ ├── Customer3.java │ │ │ └── Customer4.java │ └── traditional │ │ └── customer │ │ └── Customer1.java │ └── shared │ ├── command │ ├── ChangeCustomerEmailAddress.java │ ├── ChangeCustomerName.java │ ├── ConfirmCustomerEmailAddress.java │ └── RegisterCustomer.java │ ├── event │ ├── CustomerEmailAddressChanged.java │ ├── CustomerEmailAddressConfirmationFailed.java │ ├── CustomerEmailAddressConfirmed.java │ ├── CustomerNameChanged.java │ ├── CustomerRegistered.java │ └── Event.java │ ├── exception │ └── WrongConfirmationHashException.java │ └── value │ ├── EmailAddress.java │ ├── Hash.java │ ├── ID.java │ └── PersonName.java └── test └── java └── domain ├── THelper.java ├── functional ├── es │ └── customer │ │ ├── Customer5Test.java │ │ ├── Customer6Test.java │ │ └── Customer7Test.java └── traditional │ └── customer │ └── Customer2Test.java └── oop ├── es └── customer │ ├── Customer3Test.java │ └── Customer4Test.java └── traditional └── customer └── Customer1Test.java /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | 3 | .idea 4 | .iml 5 | 6 | .gradle 7 | build 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aggregate Implementation Patterns 2 | There are a lot of different possibilities when implementing an aggregate. Many of them are not generally better than 3 | others, hence it's crucial to take the use case into account when deciding for one model. We want to have a look at a few 4 | different patterns together and discuss their pros and cons. In the end, you should have more options to choose from for 5 | your next project. 6 | 7 | ## Implementation Characteristics 8 | While this is certainly a very simplified approach, we'll focus on the following two primary dimensions: 9 | * event-sourced vs. traditional (i.e. full state gets persisted) 10 | * functional vs. object-oriented 11 | 12 | We have prepared 7 variants of the Customer model for you, and most of them contain **tasks** that you will have 13 | to solve. Here is how those variants map to our two dimensions: 14 | 15 | | | ES | TRAD | 16 | | ------- | :------------------------------------ | -----------: | 17 | | **OOP** | *Customer3*, *Customer4* | *Customer1* | 18 | | **FP** | *Customer5*, *Customer6*, *Customer7* | *Customer2* | 19 | 20 | ## Our Aggregate Example 21 | In this workshop, we want to focus on a Customer aggregate supporting three simple use cases. The details, which you can 22 | find below, could have been collected in an *Event Storming* workshop beforehand: 23 | 24 | ![Use-Cases from Event Storming](images/eventstorming.jpg) 25 | 26 | ### Customer Registration 27 | Command: **RegisterCustomer** with properties 28 | * customerID (ID) 29 | * emailAddress (EmailAddress) 30 | * confirmationHash (Hash) 31 | * name (PersonName) 32 | 33 | Event: **CustomerRegistered** with properties 34 | * customerID (ID) 35 | * emailAddress (EmailAddress) 36 | * confirmationHash (Hash) 37 | * name (PersonName) 38 | 39 | **Rules & Policies**: 40 | * new email addresses are unconfirmed 41 | * new email addresses must get a new hash 42 | 43 | ### Email Confirmation 44 | Command: **ConfirmCustomerEmailAddress** with properties 45 | * customerID (ID) 46 | * confirmationHash (Hash) 47 | 48 | Event: **CustomerEmailAddressConfirmed** or **CustomerEmailAddressConfirmationFailed**, both with property 49 | * customerID (ID) 50 | 51 | **Rules & Policies**: 52 | * email addresses can only be confirmed with matching hash 53 | 54 | ### Email Change 55 | Command: **ChangeCustomerEmailAddress** with properties 56 | * customerID (ID) 57 | * emailAddress (EmailAddress) 58 | * confirmationHash (Hash) 59 | 60 | Event: **CustomerEmailAddressChanged** with properties 61 | * customerID (ID) 62 | * emailAddress (EmailAddress) 63 | * confirmationHash (Hash) 64 | 65 | **Rules & Policies**: 66 | * new email addresses are unconfirmed 67 | * new email addresses must get a new hash 68 | 69 | ##### Hint 70 | Our Customer gets registered with a name (PersonName), but we have removed the *Change Name* use-case for brevity, 71 | so that you are able to implement all use-cases in all variants. Still, there is a *ChangeCustomerName* 72 | Command and a *CustomerNameChanged* Event in the codebase. If we are super fast, we can use them to add this use-case. 73 | 74 | ## General Instructions to solve the tasks 75 | We have prepared 7 variants of the Customer model for you, and most of them contain **tasks** that you will have to solve. 76 | 77 | For each model variant, you can find a class *CustomerX* containing production code as well as a corresponding test 78 | class *CustomerXTest*, which contain the following test scenarios which are written in the Given/When/Then 79 | (GWT, Gherkin language) format: 80 | 81 | ![Test Scenarios](images/test_scenarios.jpg) 82 | 83 | For each *CustomerX*: 84 | The production code is **missing relevant pieces of code** which you will have to fill to make 85 | the tests green. There are **TODO** comments which mark where code is missing. You don't have to modify test cases, 86 | and you don't have to modify the existing code to complete the tasks. 87 | Please always make the first failing test green! 88 | 89 | ## Setup 90 | The repository contains a *Gradle* build file to set up the dependencies (Junit 5.7). 91 | 92 | ## Primer 93 | 94 | The two traditional (full state gets persisted) variants are rather trivial, each of you has probably implemented 95 | such models before. Therefore, we will just have a look at them together and discuss them. One interesting aspect 96 | will be how to test them. 97 | 98 | ### OOP & Traditional 99 | 100 | #### Customer1 101 | * state and behavior is the same object 102 | * directly modifies the state 103 | 104 | ### Functional & Traditional 105 | 106 | #### Customer2 107 | * state and behavior are different objects 108 | * directly modifies the state 109 | 110 | ## Exercises - Implement the following variants 111 | 112 | ### OOP & Event-Sourced 113 | 114 | #### Customer3 115 | * directly returns the events that have happened 116 | 117 | #### Customer4 (*optional*) 118 | * records the events that have happened 119 | * the client (e.g. an Application Service) has to request those events 120 | 121 | ### Functional & Event-Sourced 122 | 123 | #### Customer5 124 | * internal state per function (variables) 125 | * those variables get reconstituted inside of the behavior functions from the events that are given as input 126 | 127 | #### Customer6 128 | * uses a state object (*CustomerState*) 129 | * this is reconstituted inside of the behavior functions from the events that are given as input 130 | 131 | #### Customer7 (*optional*) 132 | * uses a state object (*CustomerState*) 133 | * this is reconstituted outside and given as input to the behavior functions 134 | 135 | ### Bonus task (in case we are very fast) 136 | 137 | #### Implement the *Change Name* use-case with your favourite variant(s) 138 | * figure our the requirements with your group 139 | * have a look at the *ChangeCustomerName* Command and *CustomerNameChanged* Event 140 | * implement the required test cases first 141 | * implement just enough production code to make the test pass -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | testImplementation('org.junit.jupiter:junit-jupiter:5.9.2') 11 | } 12 | 13 | tasks.test { 14 | useJUnitPlatform() 15 | } 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaibornWolff/aggregate-implementation-patterns-java/efe7e2172e8b52fd8789040dd0cf4521c6e3ef40/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 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 %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 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 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /images/Aggregate Implementation Patterns Usecases.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaibornWolff/aggregate-implementation-patterns-java/efe7e2172e8b52fd8789040dd0cf4521c6e3ef40/images/Aggregate Implementation Patterns Usecases.jpg -------------------------------------------------------------------------------- /images/eventstorming.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaibornWolff/aggregate-implementation-patterns-java/efe7e2172e8b52fd8789040dd0cf4521c6e3ef40/images/eventstorming.jpg -------------------------------------------------------------------------------- /images/test_scenarios.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaibornWolff/aggregate-implementation-patterns-java/efe7e2172e8b52fd8789040dd0cf4521c6e3ef40/images/test_scenarios.jpg -------------------------------------------------------------------------------- /src/main/java/domain/functional/es/customer/Customer5.java: -------------------------------------------------------------------------------- 1 | package domain.functional.es.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.event.*; 7 | import domain.shared.value.EmailAddress; 8 | import domain.shared.value.Hash; 9 | 10 | import java.util.List; 11 | 12 | public class Customer5 { 13 | public static CustomerRegistered register(RegisterCustomer command) { 14 | return null; // TODO 15 | } 16 | 17 | public static List confirmEmailAddress(List eventStream, ConfirmCustomerEmailAddress command) { 18 | boolean isEmailAddressConfirmed = false; 19 | Hash confirmationHash = null; 20 | for (Event event : eventStream) { 21 | if (event instanceof CustomerRegistered) { 22 | // TODO 23 | } else if (event instanceof CustomerEmailAddressConfirmed) { 24 | // TODO 25 | } else if (event instanceof CustomerEmailAddressChanged) { 26 | // TODO 27 | } 28 | } 29 | 30 | // TODO 31 | 32 | return List.of(); // TODO 33 | } 34 | 35 | public static List changeEmailAddress(List eventStream, ChangeCustomerEmailAddress command) { 36 | EmailAddress emailAddress = null; 37 | for (Event event : eventStream) { 38 | if (event instanceof CustomerRegistered) { 39 | // TODO 40 | } else if (event instanceof CustomerEmailAddressChanged) { 41 | // TODO 42 | } 43 | } 44 | 45 | // TODO 46 | 47 | return List.of(); // TODO 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/domain/functional/es/customer/Customer6.java: -------------------------------------------------------------------------------- 1 | package domain.functional.es.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.event.*; 7 | 8 | import java.util.List; 9 | 10 | public class Customer6 { 11 | public static CustomerRegistered register(RegisterCustomer command) { 12 | return null; // TODO 13 | } 14 | 15 | public static List confirmEmailAddress(List eventStream, ConfirmCustomerEmailAddress command) { 16 | var current = CustomerState.reconstitute(eventStream); 17 | 18 | // TODO 19 | 20 | return List.of(); // TODO 21 | } 22 | 23 | public static List changeEmailAddress(List eventStream, ChangeCustomerEmailAddress command) { 24 | var current = CustomerState.reconstitute(eventStream); 25 | 26 | // TODO 27 | 28 | return List.of(); // TODO 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/domain/functional/es/customer/Customer7.java: -------------------------------------------------------------------------------- 1 | package domain.functional.es.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.event.*; 7 | 8 | import java.util.List; 9 | 10 | public class Customer7 { 11 | public static CustomerRegistered register(RegisterCustomer command) { 12 | return null; // TODO 13 | } 14 | 15 | public static List confirmEmailAddress(CustomerState current, ConfirmCustomerEmailAddress command) { 16 | // TODO 17 | 18 | return List.of(); // TODO 19 | } 20 | 21 | public static List changeEmailAddress(CustomerState current, ChangeCustomerEmailAddress command) { 22 | // TODO 23 | 24 | return List.of(); // TODO 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/domain/functional/es/customer/CustomerState.java: -------------------------------------------------------------------------------- 1 | package domain.functional.es.customer; 2 | 3 | import domain.shared.event.*; 4 | import domain.shared.value.EmailAddress; 5 | import domain.shared.value.Hash; 6 | import domain.shared.value.PersonName; 7 | 8 | import java.util.List; 9 | 10 | public class CustomerState { 11 | EmailAddress emailAddress; 12 | Hash confirmationHash; 13 | PersonName name; 14 | boolean isEmailAddressConfirmed; 15 | 16 | private CustomerState() {} 17 | 18 | public static CustomerState reconstitute(List events) { 19 | var customer = new CustomerState(); 20 | 21 | customer.apply(events); 22 | 23 | return customer; 24 | } 25 | 26 | void apply(List events) { 27 | for (Event event : events) { 28 | if (event.getClass() == CustomerRegistered.class) { 29 | // TODO 30 | continue; 31 | } 32 | 33 | if (event.getClass() == CustomerEmailAddressConfirmed.class) { 34 | // TODO 35 | continue; 36 | } 37 | 38 | if (event.getClass() == CustomerEmailAddressChanged.class) { 39 | // TODO 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/domain/functional/traditional/customer/Customer2.java: -------------------------------------------------------------------------------- 1 | package domain.functional.traditional.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.exception.WrongConfirmationHashException; 7 | 8 | public class Customer2 { 9 | public static CustomerState register(RegisterCustomer command) { 10 | return new CustomerState( 11 | command.customerID, 12 | command.emailAddress, 13 | command.confirmationHash, 14 | command.name 15 | ); 16 | } 17 | 18 | public static CustomerState confirmEmailAddress(CustomerState current, ConfirmCustomerEmailAddress command) throws WrongConfirmationHashException { 19 | if (!command.confirmationHash.equals(current.confirmationHash)) { 20 | throw new WrongConfirmationHashException(); 21 | } 22 | 23 | return new CustomerState( 24 | current.id, 25 | current.emailAddress, 26 | current.confirmationHash, 27 | current.name, 28 | true 29 | ); 30 | } 31 | 32 | public static CustomerState changeEmailAddress(CustomerState current, ChangeCustomerEmailAddress command) { 33 | return new CustomerState( 34 | current.id, 35 | command.emailAddress, 36 | command.confirmationHash, 37 | current.name, 38 | false 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/domain/functional/traditional/customer/CustomerState.java: -------------------------------------------------------------------------------- 1 | package domain.functional.traditional.customer; 2 | 3 | import domain.shared.value.EmailAddress; 4 | import domain.shared.value.Hash; 5 | import domain.shared.value.ID; 6 | import domain.shared.value.PersonName; 7 | 8 | public class CustomerState { 9 | final ID id; 10 | final EmailAddress emailAddress; 11 | final Hash confirmationHash; 12 | final PersonName name; 13 | final boolean isEmailAddressConfirmed; 14 | 15 | public CustomerState(ID id, EmailAddress emailAddress, Hash confirmationHash, PersonName name, boolean isEmailAddressConfirmed) { 16 | this.id = id; 17 | this.emailAddress = emailAddress; 18 | this.confirmationHash = confirmationHash; 19 | this.name = name; 20 | this.isEmailAddressConfirmed = isEmailAddressConfirmed; 21 | } 22 | 23 | public CustomerState(ID id, EmailAddress emailAddress, Hash confirmationHash, PersonName name) { 24 | this(id, emailAddress, confirmationHash, name, false); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/domain/oop/es/customer/Customer3.java: -------------------------------------------------------------------------------- 1 | package domain.oop.es.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.event.*; 7 | import domain.shared.value.EmailAddress; 8 | import domain.shared.value.Hash; 9 | import domain.shared.value.PersonName; 10 | 11 | import java.util.List; 12 | 13 | public final class Customer3 { 14 | private EmailAddress emailAddress; 15 | private Hash confirmationHash; 16 | private boolean isEmailAddressConfirmed; 17 | private PersonName name; 18 | 19 | private Customer3() { 20 | } 21 | 22 | public static CustomerRegistered register(RegisterCustomer command) { 23 | return null; // TODO 24 | } 25 | 26 | public static Customer3 reconstitute(List events) { 27 | Customer3 customer = new Customer3(); 28 | 29 | customer.apply(events); 30 | 31 | return customer; 32 | } 33 | 34 | public List confirmEmailAddress(ConfirmCustomerEmailAddress command) { 35 | // TODO 36 | 37 | return List.of(); // TODO 38 | } 39 | 40 | public List changeEmailAddress(ChangeCustomerEmailAddress command) { 41 | // TODO 42 | 43 | return List.of(); // TODO 44 | } 45 | 46 | void apply(List events) { 47 | for (Event event : events) { 48 | apply(event); 49 | } 50 | } 51 | 52 | void apply(Event event) { 53 | if (event.getClass() == CustomerRegistered.class) { 54 | // TODO 55 | } else if (event.getClass() == CustomerEmailAddressConfirmed.class) { 56 | // TODO 57 | } else if (event.getClass() == CustomerEmailAddressChanged.class) { 58 | // TODO 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/domain/oop/es/customer/Customer4.java: -------------------------------------------------------------------------------- 1 | package domain.oop.es.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.event.*; 7 | import domain.shared.value.EmailAddress; 8 | import domain.shared.value.Hash; 9 | import domain.shared.value.PersonName; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public final class Customer4 { 15 | private EmailAddress emailAddress; 16 | private Hash confirmationHash; 17 | private boolean isEmailAddressConfirmed; 18 | private PersonName name; 19 | 20 | private final List recordedEvents; 21 | 22 | private Customer4() { 23 | recordedEvents = new ArrayList<>(); 24 | } 25 | 26 | public static Customer4 register(RegisterCustomer command) { 27 | Customer4 customer = new Customer4(); 28 | 29 | // TODO 30 | 31 | return customer; 32 | } 33 | 34 | public static Customer4 reconstitute(List events) { 35 | var customer = new Customer4(); 36 | 37 | customer.apply(events); 38 | 39 | return customer; 40 | } 41 | 42 | public void confirmEmailAddress(ConfirmCustomerEmailAddress command) { 43 | // TODO 44 | } 45 | 46 | public void changeEmailAddress(ChangeCustomerEmailAddress command) { 47 | // TODO 48 | } 49 | 50 | public List getRecordedEvents() { 51 | return recordedEvents; 52 | } 53 | 54 | private void recordThat(Event event) { 55 | recordedEvents.add(event); 56 | } 57 | 58 | void apply(List events) { 59 | for (Event event : events) { 60 | apply(event); 61 | } 62 | } 63 | 64 | void apply(Event event) { 65 | if (event.getClass() == CustomerRegistered.class) { 66 | // TODO 67 | } else if (event.getClass() == CustomerEmailAddressConfirmed.class) { 68 | // TODO 69 | } else if (event.getClass() == CustomerEmailAddressChanged.class) { 70 | // TODO 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/domain/oop/traditional/customer/Customer1.java: -------------------------------------------------------------------------------- 1 | package domain.oop.traditional.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.exception.WrongConfirmationHashException; 7 | import domain.shared.value.EmailAddress; 8 | import domain.shared.value.Hash; 9 | import domain.shared.value.ID; 10 | import domain.shared.value.PersonName; 11 | 12 | public class Customer1 { 13 | final ID id; 14 | EmailAddress emailAddress; 15 | Hash confirmationHash; 16 | boolean isEmailAddressConfirmed; 17 | PersonName name; 18 | 19 | private Customer1(ID id, EmailAddress emailAddress, Hash confirmationHash, PersonName name) { 20 | this.id = id; 21 | this.emailAddress = emailAddress; 22 | this.confirmationHash = confirmationHash; 23 | this.name = name; 24 | } 25 | 26 | public static Customer1 register(RegisterCustomer command) { 27 | return new Customer1( 28 | command.customerID, 29 | command.emailAddress, 30 | command.confirmationHash, 31 | command.name 32 | ); 33 | } 34 | 35 | public void confirmEmailAddress(ConfirmCustomerEmailAddress command) throws WrongConfirmationHashException { 36 | if (!command.confirmationHash.equals(confirmationHash)) { 37 | throw new WrongConfirmationHashException(); 38 | } 39 | 40 | isEmailAddressConfirmed = true; 41 | } 42 | 43 | public void changeEmailAddress(ChangeCustomerEmailAddress command) { 44 | emailAddress = command.emailAddress; 45 | confirmationHash = command.confirmationHash; 46 | isEmailAddressConfirmed = false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/command/ChangeCustomerEmailAddress.java: -------------------------------------------------------------------------------- 1 | package domain.shared.command; 2 | 3 | import domain.shared.value.ID; 4 | import domain.shared.value.EmailAddress; 5 | import domain.shared.value.Hash; 6 | 7 | public final class ChangeCustomerEmailAddress { 8 | public final ID customerID; 9 | public final EmailAddress emailAddress; 10 | public final Hash confirmationHash; 11 | 12 | private ChangeCustomerEmailAddress(String customerID, String emailAddress) { 13 | this.customerID = ID.build(customerID); 14 | this.emailAddress = EmailAddress.build(emailAddress); 15 | this.confirmationHash = Hash.generate(); 16 | } 17 | 18 | public static ChangeCustomerEmailAddress build(String customerID, String emailAddress) { 19 | return new ChangeCustomerEmailAddress(customerID, emailAddress); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/command/ChangeCustomerName.java: -------------------------------------------------------------------------------- 1 | package domain.shared.command; 2 | 3 | import domain.shared.value.ID; 4 | import domain.shared.value.PersonName; 5 | 6 | public final class ChangeCustomerName { 7 | public final ID customerID; 8 | public final PersonName name; 9 | 10 | private ChangeCustomerName(String customerID, String givenName, String familyName) { 11 | this.customerID = ID.build(customerID); 12 | this.name = PersonName.build(givenName, familyName); 13 | } 14 | 15 | public static ChangeCustomerName build(String customerID, String givenName, String familyName) { 16 | return new ChangeCustomerName(customerID, givenName, familyName); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/command/ConfirmCustomerEmailAddress.java: -------------------------------------------------------------------------------- 1 | package domain.shared.command; 2 | 3 | import domain.shared.value.ID; 4 | import domain.shared.value.Hash; 5 | 6 | public final class ConfirmCustomerEmailAddress { 7 | public final ID customerID; 8 | public final Hash confirmationHash; 9 | 10 | private ConfirmCustomerEmailAddress(String customerID, String confirmationHash) { 11 | this.customerID = ID.build(customerID); 12 | this.confirmationHash = Hash.build(confirmationHash); 13 | } 14 | 15 | public static ConfirmCustomerEmailAddress build(String customerID, String confirmationHash) { 16 | return new ConfirmCustomerEmailAddress(customerID, confirmationHash); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/command/RegisterCustomer.java: -------------------------------------------------------------------------------- 1 | package domain.shared.command; 2 | 3 | import domain.shared.value.ID; 4 | import domain.shared.value.EmailAddress; 5 | import domain.shared.value.Hash; 6 | import domain.shared.value.PersonName; 7 | 8 | public final class RegisterCustomer { 9 | public final ID customerID; 10 | public final EmailAddress emailAddress; 11 | public final Hash confirmationHash; 12 | public final PersonName name; 13 | 14 | private RegisterCustomer(String emailAddress, String givenName, String familyName) { 15 | this.customerID = ID.generate(); 16 | this.confirmationHash = Hash.generate(); 17 | this.emailAddress = EmailAddress.build(emailAddress); 18 | this.name = PersonName.build(givenName, familyName); 19 | } 20 | 21 | public static RegisterCustomer build(String emailAddress, String givenName, String familyName) { 22 | return new RegisterCustomer(emailAddress, givenName, familyName); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/event/CustomerEmailAddressChanged.java: -------------------------------------------------------------------------------- 1 | package domain.shared.event; 2 | 3 | import domain.shared.value.ID; 4 | import domain.shared.value.EmailAddress; 5 | import domain.shared.value.Hash; 6 | 7 | public final class CustomerEmailAddressChanged implements Event { 8 | public final ID customerID; 9 | public final EmailAddress emailAddress; 10 | public final Hash confirmationHash; 11 | 12 | private CustomerEmailAddressChanged(ID customerID, EmailAddress emailAddress, Hash confirmationHash) { 13 | this.customerID = customerID; 14 | this.emailAddress = emailAddress; 15 | this.confirmationHash = confirmationHash; 16 | } 17 | 18 | public static CustomerEmailAddressChanged build(ID customerID, EmailAddress emailAddress, Hash confirmationHash) { 19 | return new CustomerEmailAddressChanged(customerID, emailAddress, confirmationHash); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/event/CustomerEmailAddressConfirmationFailed.java: -------------------------------------------------------------------------------- 1 | package domain.shared.event; 2 | 3 | import domain.shared.value.ID; 4 | 5 | public final class CustomerEmailAddressConfirmationFailed implements Event { 6 | public final ID customerID; 7 | 8 | private CustomerEmailAddressConfirmationFailed(ID customerID) { 9 | this.customerID = customerID; 10 | } 11 | 12 | public static CustomerEmailAddressConfirmationFailed build(ID customerID) { 13 | return new CustomerEmailAddressConfirmationFailed(customerID); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/event/CustomerEmailAddressConfirmed.java: -------------------------------------------------------------------------------- 1 | package domain.shared.event; 2 | 3 | import domain.shared.value.ID; 4 | 5 | public final class CustomerEmailAddressConfirmed implements Event { 6 | public final ID customerID; 7 | 8 | private CustomerEmailAddressConfirmed(ID customerID) { 9 | this.customerID = customerID; 10 | } 11 | 12 | public static CustomerEmailAddressConfirmed build(ID customerID) { 13 | return new CustomerEmailAddressConfirmed(customerID); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/event/CustomerNameChanged.java: -------------------------------------------------------------------------------- 1 | package domain.shared.event; 2 | 3 | import domain.shared.value.ID; 4 | import domain.shared.value.PersonName; 5 | 6 | public final class CustomerNameChanged implements Event { 7 | public final ID customerID; 8 | public final PersonName name; 9 | 10 | private CustomerNameChanged(ID customerID, PersonName name) { 11 | this.customerID = customerID; 12 | this.name = name; 13 | } 14 | 15 | public static CustomerNameChanged build(ID customerID, PersonName name) { 16 | return new CustomerNameChanged(customerID, name); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/event/CustomerRegistered.java: -------------------------------------------------------------------------------- 1 | package domain.shared.event; 2 | 3 | import domain.shared.value.ID; 4 | import domain.shared.value.EmailAddress; 5 | import domain.shared.value.Hash; 6 | import domain.shared.value.PersonName; 7 | 8 | public final class CustomerRegistered implements Event { 9 | public final ID customerID; 10 | public final EmailAddress emailAddress; 11 | public final Hash confirmationHash; 12 | public final PersonName name; 13 | 14 | private CustomerRegistered(ID customerID, EmailAddress emailAddress, Hash confirmationHash, PersonName name) { 15 | this.customerID = customerID; 16 | this.emailAddress = emailAddress; 17 | this.confirmationHash = confirmationHash; 18 | this.name = name; 19 | } 20 | 21 | public static CustomerRegistered build( 22 | ID id, 23 | EmailAddress emailAddress, 24 | Hash confirmationHash, 25 | PersonName name 26 | ) { 27 | return new CustomerRegistered(id, emailAddress, confirmationHash, name); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/event/Event.java: -------------------------------------------------------------------------------- 1 | package domain.shared.event; 2 | 3 | public interface Event { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/exception/WrongConfirmationHashException.java: -------------------------------------------------------------------------------- 1 | package domain.shared.exception; 2 | 3 | public class WrongConfirmationHashException extends Exception { 4 | public WrongConfirmationHashException() { 5 | super("confirmation hash does not match"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/value/EmailAddress.java: -------------------------------------------------------------------------------- 1 | package domain.shared.value; 2 | 3 | public final class EmailAddress { 4 | public final String value; 5 | 6 | private EmailAddress(String value) { 7 | this.value = value; 8 | } 9 | 10 | public static EmailAddress build(String emailAddress) { 11 | return new EmailAddress(emailAddress); 12 | } 13 | 14 | @Override 15 | public boolean equals(Object o) { 16 | if (this == o) return true; 17 | if (o == null || getClass() != o.getClass()) return false; 18 | EmailAddress that = (EmailAddress) o; 19 | return value.equals(that.value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/value/Hash.java: -------------------------------------------------------------------------------- 1 | package domain.shared.value; 2 | 3 | import java.util.UUID; 4 | 5 | public final class Hash { 6 | public final String value; 7 | 8 | private Hash(String value) { 9 | this.value = value; 10 | } 11 | 12 | public static Hash generate() { 13 | return new Hash(UUID.randomUUID().toString()); 14 | } 15 | 16 | public static Hash build(String hash) { 17 | return new Hash(hash); 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | Hash hash = (Hash) o; 25 | return value.equals(hash.value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/value/ID.java: -------------------------------------------------------------------------------- 1 | package domain.shared.value; 2 | 3 | import java.util.UUID; 4 | 5 | public final class ID { 6 | public final String value; 7 | 8 | private ID(String value) { 9 | this.value = value; 10 | } 11 | 12 | public static ID generate() { 13 | return new ID(UUID.randomUUID().toString()); 14 | } 15 | 16 | public static ID build(String id) { 17 | return new ID(id); 18 | } 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | ID id = (ID) o; 25 | return value.equals(id.value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/domain/shared/value/PersonName.java: -------------------------------------------------------------------------------- 1 | package domain.shared.value; 2 | 3 | public final class PersonName { 4 | public final String givenName; 5 | public final String familyName; 6 | 7 | private PersonName(String givenName, String familyName) { 8 | this.givenName = givenName; 9 | this.familyName = familyName; 10 | } 11 | 12 | public static PersonName build(String givenName, String familyName) { 13 | return new PersonName(givenName, familyName); 14 | } 15 | 16 | @Override 17 | public boolean equals(Object o) { 18 | if (this == o) return true; 19 | if (o == null || getClass() != o.getClass()) return false; 20 | PersonName that = (PersonName) o; 21 | return givenName.equals(that.givenName) && 22 | familyName.equals(that.familyName); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/domain/THelper.java: -------------------------------------------------------------------------------- 1 | package domain; 2 | 3 | import domain.shared.event.Event; 4 | 5 | import java.util.List; 6 | 7 | public class THelper { 8 | public static String typeOfFirst(List recordedEvents) { 9 | if (recordedEvents.size() == 0) { 10 | return "???"; 11 | } 12 | 13 | return recordedEvents.get(0).getClass().getSimpleName(); 14 | } 15 | 16 | public static String propertyIsNull(String property) { 17 | return String.format( 18 | "PROBLEM: The %s is null!\n" + 19 | "HINT: Maybe you didn't apply the previous events properly!?\n", 20 | property 21 | ); 22 | } 23 | 24 | public static String eventIsNull(String method, String expectedEvent) { 25 | return String.format( 26 | "PROBLEM in %s(): The recorded/returned event is NULL!\n" + 27 | "HINT: Make sure you record/return a %s event\n\n", 28 | method, 29 | expectedEvent 30 | ); 31 | } 32 | 33 | public static String propertyIsWrong(String method, String property) { 34 | return String.format( 35 | "PROBLEM in %s(): The event contains a wrong %s!\n" + 36 | "HINT: The %s in the event should be taken from the command!\n\n", 37 | method, property, property 38 | ); 39 | } 40 | 41 | public static String noEventWasRecorded(String method, String expectedEvent) { 42 | return String.format( 43 | "PROBLEM in %s(): No event was recorded/returned!\n" + 44 | "HINTS: Build a %s event and record/return it!\n" + 45 | " Did you apply all previous events properly?\n" + 46 | " Check your business logic :-)!\n\n", 47 | method, expectedEvent 48 | ); 49 | } 50 | 51 | public static String eventOfWrongTypeWasRecorded(String method) { 52 | return String.format( 53 | "PROBLEM in %s(): An event of the wrong type was recorded/returned!\n" + 54 | "HINTS: Did you apply all previous events properly?\n" + 55 | " Check your business logic :-)!\n\n", 56 | method 57 | ); 58 | } 59 | 60 | public static String noEventShouldHaveBeenRecorded(String recordedEventType) { 61 | return String.format( 62 | "PROBLEM: No event should have been recorded/returned!\n" + 63 | "HINTS: Check your business logic - this command should be ignored (idempotency)!\n" + 64 | " Did you apply all previous events properly?\n" + 65 | " The recorded/returned event is of type %s.\n\n", 66 | recordedEventType 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/domain/functional/es/customer/Customer5Test.java: -------------------------------------------------------------------------------- 1 | package domain.functional.es.customer; 2 | 3 | import domain.THelper; 4 | import domain.shared.command.ChangeCustomerEmailAddress; 5 | import domain.shared.command.ConfirmCustomerEmailAddress; 6 | import domain.shared.command.RegisterCustomer; 7 | import domain.shared.event.*; 8 | import domain.shared.value.EmailAddress; 9 | import domain.shared.value.Hash; 10 | import domain.shared.value.ID; 11 | import domain.shared.value.PersonName; 12 | import org.junit.jupiter.api.*; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | 19 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 20 | class Customer5Test { 21 | private ID customerID; 22 | private EmailAddress emailAddress; 23 | private EmailAddress changedEmailAddress; 24 | private Hash confirmationHash; 25 | private Hash wrongConfirmationHash; 26 | private Hash changedConfirmationHash; 27 | private PersonName name; 28 | private List eventStream; 29 | private CustomerRegistered customerRegistered; 30 | private List recordedEvents; 31 | 32 | @BeforeEach 33 | void beforeEach() { 34 | customerID = ID.generate(); 35 | emailAddress = EmailAddress.build("john@doe.com"); 36 | changedEmailAddress = EmailAddress.build("john+changed@doe.com"); 37 | confirmationHash = Hash.generate(); 38 | wrongConfirmationHash = Hash.generate(); 39 | changedConfirmationHash = Hash.generate(); 40 | name = PersonName.build("John", "Doe"); 41 | eventStream = new ArrayList<>(); 42 | recordedEvents = new ArrayList<>(); 43 | } 44 | 45 | @Test 46 | @Order(1) 47 | void registerCustomer() { 48 | WHEN_RegisterCustomer(); 49 | THEN_CustomerRegistered(); 50 | } 51 | 52 | @Test 53 | @Order(2) 54 | void confirmEmailAddress() { 55 | GIVEN_CustomerRegistered(); 56 | WHEN_ConfirmEmailAddress_With(confirmationHash); 57 | THEN_EmailAddressConfirmed(); 58 | } 59 | 60 | @Test 61 | @Order(3) 62 | void confirmEmailAddress_withWrongConfirmationHash() { 63 | GIVEN_CustomerRegistered(); 64 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 65 | THEN_EmailAddressConfirmationFailed(); 66 | } 67 | 68 | @Test 69 | @Order(4) 70 | void confirmEmailAddress_whenItWasAlreadyConfirmed() { 71 | GIVEN_CustomerRegistered(); 72 | __and_EmailAddressWasConfirmed(); 73 | WHEN_ConfirmEmailAddress_With(confirmationHash); 74 | THEN_NothingShouldHappen(); 75 | } 76 | 77 | @Test 78 | @Order(5) 79 | void confirmEmailAddress_withWrongConfirmationHash_whenItWasAlreadyConfirmed() { 80 | GIVEN_CustomerRegistered(); 81 | __and_EmailAddressWasConfirmed(); 82 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 83 | THEN_EmailAddressConfirmationFailed(); 84 | } 85 | 86 | @Test 87 | @Order(6) 88 | void changeEmailAddress() { 89 | GIVEN_CustomerRegistered(); 90 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 91 | THEN_EmailAddressChanged(); 92 | } 93 | 94 | @Test 95 | @Order(7) 96 | void changeEmailAddress_withUnchangedEmailAddress() { 97 | // Given 98 | GIVEN_CustomerRegistered(); 99 | WHEN_ChangeEmailAddress_With(emailAddress); 100 | THEN_NothingShouldHappen(); 101 | } 102 | 103 | @Test 104 | @Order(8) 105 | void changeEmailAddress_whenItWasAlreadyChanged() { 106 | GIVEN_CustomerRegistered(); 107 | __and_EmailAddressWasChanged(); 108 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 109 | THEN_NothingShouldHappen(); 110 | } 111 | 112 | @Test 113 | @Order(9) 114 | void confirmEmailAddress_whenItWasPreviouslyConfirmedAndThenChanged() { 115 | // Given 116 | GIVEN_CustomerRegistered(); 117 | __and_EmailAddressWasConfirmed(); 118 | __and_EmailAddressWasChanged(); 119 | WHEN_ConfirmEmailAddress_With(changedConfirmationHash); 120 | THEN_EmailAddressConfirmed(); 121 | } 122 | 123 | /** 124 | * Methods for GIVEN 125 | */ 126 | 127 | private void GIVEN_CustomerRegistered() { 128 | eventStream.add(CustomerRegistered.build(customerID, emailAddress, confirmationHash, name)); 129 | } 130 | 131 | private void __and_EmailAddressWasConfirmed() { 132 | eventStream.add(CustomerEmailAddressConfirmed.build(customerID)); 133 | } 134 | 135 | private void __and_EmailAddressWasChanged() { 136 | eventStream.add(CustomerEmailAddressChanged.build(customerID, changedEmailAddress, changedConfirmationHash)); 137 | emailAddress = changedEmailAddress; 138 | confirmationHash = changedConfirmationHash; 139 | } 140 | 141 | /** 142 | * Methods for WHEN 143 | */ 144 | 145 | private void WHEN_RegisterCustomer() { 146 | var registerCustomer = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 147 | customerRegistered = Customer5.register(registerCustomer); 148 | customerID = registerCustomer.customerID; 149 | confirmationHash = registerCustomer.confirmationHash; 150 | } 151 | 152 | private void WHEN_ConfirmEmailAddress_With(Hash confirmationHash) { 153 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 154 | try { 155 | recordedEvents = Customer5.confirmEmailAddress(eventStream, command); 156 | } catch (NullPointerException e) { 157 | fail(THelper.propertyIsNull("confirmationHash")); 158 | } 159 | } 160 | 161 | private void WHEN_ChangeEmailAddress_With(EmailAddress emailAddress) { 162 | var command = ChangeCustomerEmailAddress.build(customerID.value, emailAddress.value); 163 | try { 164 | recordedEvents = Customer5.changeEmailAddress(eventStream, command); 165 | changedConfirmationHash = command.confirmationHash; 166 | } catch (NullPointerException e) { 167 | fail(THelper.propertyIsNull("emailAddress")); 168 | } 169 | } 170 | 171 | /** 172 | * Methods for THEN 173 | */ 174 | 175 | private void THEN_CustomerRegistered() { 176 | var method = "register"; 177 | var eventName = "CustomerRegistered"; 178 | assertNotNull(customerRegistered, THelper.eventIsNull(method, eventName)); 179 | assertEquals(customerID, customerRegistered.customerID, THelper.propertyIsWrong(method, "customerID")); 180 | assertEquals(emailAddress, customerRegistered.emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 181 | assertEquals(confirmationHash, customerRegistered.confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 182 | assertEquals(name, customerRegistered.name, THelper.propertyIsWrong(method, "name")); 183 | } 184 | 185 | private void THEN_EmailAddressConfirmed() { 186 | var method = "confirmEmailAddress"; 187 | var eventName = "CustomerEmailAddressConfirmed"; 188 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 189 | assertNotNull(recordedEvents.get(0), THelper.eventIsNull(method, eventName)); 190 | assertEquals(CustomerEmailAddressConfirmed.class, recordedEvents.get(0).getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 191 | var event = (CustomerEmailAddressConfirmed) recordedEvents.get(0); 192 | assertEquals(customerID, event.customerID, THelper.propertyIsWrong(method, "customerID")); 193 | } 194 | 195 | private void THEN_EmailAddressConfirmationFailed() { 196 | var method = "confirmEmailAddress"; 197 | var eventName = "CustomerEmailAddressConfirmationFailed"; 198 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 199 | assertNotNull(recordedEvents.get(0), THelper.eventIsNull(method, eventName)); 200 | assertEquals(CustomerEmailAddressConfirmationFailed.class, recordedEvents.get(0).getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 201 | var event = (CustomerEmailAddressConfirmationFailed) recordedEvents.get(0); 202 | assertEquals(customerID, event.customerID, THelper.propertyIsWrong(method, "customerID")); 203 | } 204 | 205 | private void THEN_EmailAddressChanged() { 206 | var method = "changeEmailAddress"; 207 | var eventName = "CustomerEmailAddressChanged"; 208 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 209 | assertNotNull(recordedEvents.get(0), THelper.eventIsNull(method, eventName)); 210 | assertEquals(CustomerEmailAddressChanged.class, recordedEvents.get(0).getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 211 | var event = (CustomerEmailAddressChanged) recordedEvents.get(0); 212 | assertEquals(customerID, event.customerID, THelper.propertyIsWrong(method, "customerID")); 213 | assertEquals(changedEmailAddress, event.emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 214 | assertEquals(changedConfirmationHash, event.confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 215 | } 216 | 217 | private void THEN_NothingShouldHappen() { 218 | assertEquals(0, recordedEvents.size(), 219 | THelper.noEventShouldHaveBeenRecorded(THelper.typeOfFirst(recordedEvents))); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/test/java/domain/functional/es/customer/Customer6Test.java: -------------------------------------------------------------------------------- 1 | package domain.functional.es.customer; 2 | 3 | import domain.THelper; 4 | import domain.shared.command.ChangeCustomerEmailAddress; 5 | import domain.shared.command.ConfirmCustomerEmailAddress; 6 | import domain.shared.command.RegisterCustomer; 7 | import domain.shared.event.*; 8 | import domain.shared.value.EmailAddress; 9 | import domain.shared.value.Hash; 10 | import domain.shared.value.ID; 11 | import domain.shared.value.PersonName; 12 | import org.junit.jupiter.api.*; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | 19 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 20 | class Customer6Test { 21 | private ID customerID; 22 | private EmailAddress emailAddress; 23 | private EmailAddress changedEmailAddress; 24 | private Hash confirmationHash; 25 | private Hash wrongConfirmationHash; 26 | private Hash changedConfirmationHash; 27 | private PersonName name; 28 | private List eventStream; 29 | private CustomerRegistered customerRegistered; 30 | private List recordedEvents; 31 | 32 | @BeforeEach 33 | void beforeEach() { 34 | customerID = ID.generate(); 35 | emailAddress = EmailAddress.build("john@doe.com"); 36 | changedEmailAddress = EmailAddress.build("john+changed@doe.com"); 37 | confirmationHash = Hash.generate(); 38 | wrongConfirmationHash = Hash.generate(); 39 | changedConfirmationHash = Hash.generate(); 40 | name = PersonName.build("John", "Doe"); 41 | eventStream = new ArrayList<>(); 42 | recordedEvents = new ArrayList<>(); 43 | } 44 | 45 | @Test 46 | @Order(1) 47 | void registerCustomer() { 48 | WHEN_RegisterCustomer(); 49 | THEN_CustomerRegistered(); 50 | } 51 | 52 | @Test 53 | @Order(2) 54 | void confirmEmailAddress() { 55 | GIVEN_CustomerRegistered(); 56 | WHEN_ConfirmEmailAddress_With(confirmationHash); 57 | THEN_EmailAddressConfirmed(); 58 | } 59 | 60 | @Test 61 | @Order(3) 62 | void confirmEmailAddress_withWrongConfirmationHash() { 63 | GIVEN_CustomerRegistered(); 64 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 65 | THEN_EmailAddressConfirmationFailed(); 66 | } 67 | 68 | @Test 69 | @Order(4) 70 | void confirmEmailAddress_whenItWasAlreadyConfirmed() { 71 | GIVEN_CustomerRegistered(); 72 | __and_EmailAddressWasConfirmed(); 73 | WHEN_ConfirmEmailAddress_With(confirmationHash); 74 | THEN_NothingShouldHappen(); 75 | } 76 | 77 | @Test 78 | @Order(5) 79 | void confirmEmailAddress_withWrongConfirmationHash_whenItWasAlreadyConfirmed() { 80 | GIVEN_CustomerRegistered(); 81 | __and_EmailAddressWasConfirmed(); 82 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 83 | THEN_EmailAddressConfirmationFailed(); 84 | } 85 | 86 | @Test 87 | @Order(6) 88 | void changeEmailAddress() { 89 | GIVEN_CustomerRegistered(); 90 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 91 | THEN_EmailAddressChanged(); 92 | } 93 | 94 | @Test 95 | @Order(7) 96 | void changeEmailAddress_withUnchangedEmailAddress() { 97 | // Given 98 | GIVEN_CustomerRegistered(); 99 | WHEN_ChangeEmailAddress_With(emailAddress); 100 | THEN_NothingShouldHappen(); 101 | } 102 | 103 | @Test 104 | @Order(8) 105 | void changeEmailAddress_whenItWasAlreadyChanged() { 106 | GIVEN_CustomerRegistered(); 107 | __and_EmailAddressWasChanged(); 108 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 109 | THEN_NothingShouldHappen(); 110 | } 111 | 112 | @Test 113 | @Order(9) 114 | void confirmEmailAddress_whenItWasPreviouslyConfirmedAndThenChanged() { 115 | // Given 116 | GIVEN_CustomerRegistered(); 117 | __and_EmailAddressWasConfirmed(); 118 | __and_EmailAddressWasChanged(); 119 | WHEN_ConfirmEmailAddress_With(changedConfirmationHash); 120 | THEN_EmailAddressConfirmed(); 121 | } 122 | 123 | /** 124 | * Methods for GIVEN 125 | */ 126 | 127 | private void GIVEN_CustomerRegistered() { 128 | eventStream.add(CustomerRegistered.build(customerID, emailAddress, confirmationHash, name)); 129 | } 130 | 131 | private void __and_EmailAddressWasConfirmed() { 132 | eventStream.add(CustomerEmailAddressConfirmed.build(customerID)); 133 | } 134 | 135 | private void __and_EmailAddressWasChanged() { 136 | eventStream.add(CustomerEmailAddressChanged.build(customerID, changedEmailAddress, changedConfirmationHash)); 137 | emailAddress = changedEmailAddress; 138 | confirmationHash = changedConfirmationHash; 139 | } 140 | 141 | /** 142 | * Methods for WHEN 143 | */ 144 | 145 | private void WHEN_RegisterCustomer() { 146 | var registerCustomer = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 147 | customerRegistered = Customer6.register(registerCustomer); 148 | customerID = registerCustomer.customerID; 149 | confirmationHash = registerCustomer.confirmationHash; 150 | } 151 | 152 | private void WHEN_ConfirmEmailAddress_With(Hash confirmationHash) { 153 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 154 | try { 155 | recordedEvents = Customer6.confirmEmailAddress(eventStream, command); 156 | } catch (NullPointerException e) { 157 | fail(THelper.propertyIsNull("confirmationHash")); 158 | } 159 | } 160 | 161 | private void WHEN_ChangeEmailAddress_With(EmailAddress emailAddress) { 162 | var command = ChangeCustomerEmailAddress.build(customerID.value, emailAddress.value); 163 | try { 164 | recordedEvents = Customer6.changeEmailAddress(eventStream, command); 165 | changedConfirmationHash = command.confirmationHash; 166 | } catch (NullPointerException e) { 167 | fail(THelper.propertyIsNull("emailAddress")); 168 | } 169 | } 170 | 171 | /** 172 | * Methods for THEN 173 | */ 174 | 175 | private void THEN_CustomerRegistered() { 176 | var method = "register"; 177 | var eventName = "CustomerRegistered"; 178 | assertNotNull(customerRegistered, THelper.eventIsNull(method, eventName)); 179 | assertEquals(customerID, customerRegistered.customerID, THelper.propertyIsWrong(method, "customerID")); 180 | assertEquals(emailAddress, customerRegistered.emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 181 | assertEquals(confirmationHash, customerRegistered.confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 182 | assertEquals(name, customerRegistered.name, THelper.propertyIsWrong(method, "name")); 183 | } 184 | 185 | private void THEN_EmailAddressConfirmed() { 186 | var method = "confirmEmailAddress"; 187 | var eventName = "CustomerEmailAddressConfirmed"; 188 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 189 | var event = recordedEvents.get(0); 190 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 191 | assertEquals(CustomerEmailAddressConfirmed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 192 | var typedEvent = (CustomerEmailAddressConfirmed) event; 193 | assertEquals(customerID, typedEvent.customerID, THelper.propertyIsWrong(method, "customerID")); 194 | } 195 | 196 | private void THEN_EmailAddressConfirmationFailed() { 197 | var method = "confirmEmailAddress"; 198 | var eventName = "CustomerEmailAddressConfirmationFailed"; 199 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 200 | var event = recordedEvents.get(0); 201 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 202 | assertEquals(CustomerEmailAddressConfirmationFailed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 203 | var typedEvent = (CustomerEmailAddressConfirmationFailed) event; 204 | assertEquals(customerID, typedEvent.customerID, THelper.propertyIsWrong(method, "customerID")); 205 | } 206 | 207 | private void THEN_EmailAddressChanged() { 208 | var method = "changeEmailAddress"; 209 | var eventName = "CustomerEmailAddressChanged"; 210 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 211 | var event = recordedEvents.get(0); 212 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 213 | assertEquals(CustomerEmailAddressChanged.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 214 | var typedEvent = (CustomerEmailAddressChanged) event; 215 | assertEquals(customerID, typedEvent.customerID, THelper.propertyIsWrong(method, "customerID")); 216 | assertEquals(changedEmailAddress, typedEvent.emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 217 | assertEquals(changedConfirmationHash, typedEvent.confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 218 | } 219 | 220 | private void THEN_NothingShouldHappen() { 221 | assertEquals(0, recordedEvents.size(), 222 | THelper.noEventShouldHaveBeenRecorded(THelper.typeOfFirst(recordedEvents))); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/test/java/domain/functional/es/customer/Customer7Test.java: -------------------------------------------------------------------------------- 1 | package domain.functional.es.customer; 2 | 3 | import domain.THelper; 4 | import domain.shared.command.ChangeCustomerEmailAddress; 5 | import domain.shared.command.ConfirmCustomerEmailAddress; 6 | import domain.shared.command.RegisterCustomer; 7 | import domain.shared.event.*; 8 | import domain.shared.value.EmailAddress; 9 | import domain.shared.value.Hash; 10 | import domain.shared.value.ID; 11 | import domain.shared.value.PersonName; 12 | import org.junit.jupiter.api.*; 13 | 14 | import java.util.List; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | import static org.junit.jupiter.api.Assertions.fail; 18 | 19 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 20 | class Customer7Test { 21 | private ID customerID; 22 | private EmailAddress emailAddress; 23 | private EmailAddress changedEmailAddress; 24 | private Hash confirmationHash; 25 | private Hash wrongConfirmationHash; 26 | private Hash changedConfirmationHash; 27 | private PersonName name; 28 | private CustomerState currentState; 29 | private CustomerRegistered customerRegistered; 30 | private List recordedEvents; 31 | 32 | @BeforeEach 33 | void beforeEach() { 34 | customerID = ID.generate(); 35 | emailAddress = EmailAddress.build("john@doe.com"); 36 | changedEmailAddress = EmailAddress.build("john+changed@doe.com"); 37 | confirmationHash = Hash.generate(); 38 | wrongConfirmationHash = Hash.generate(); 39 | changedConfirmationHash = Hash.generate(); 40 | name = PersonName.build("John", "Doe"); 41 | } 42 | 43 | @Test 44 | @Order(1) 45 | void registerCustomer() { 46 | WHEN_RegisterCustomer(); 47 | THEN_CustomerRegistered(); 48 | } 49 | 50 | @Test 51 | @Order(2) 52 | void confirmEmailAddress() { 53 | GIVEN(customerIsRegistered()); 54 | WHEN_ConfirmEmailAddress_With(confirmationHash); 55 | THEN_EmailAddressConfirmed(); 56 | } 57 | 58 | @Test 59 | @Order(3) 60 | void confirmEmailAddress_withWrongConfirmationHash() { 61 | GIVEN(customerIsRegistered()); 62 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 63 | THEN_EmailAddressConfirmationFailed(); 64 | } 65 | 66 | @Test 67 | @Order(4) 68 | void confirmEmailAddress_whenItWasAlreadyConfirmed() { 69 | GIVEN(customerIsRegistered()); 70 | WHEN_ConfirmEmailAddress_With(confirmationHash); 71 | THEN_EmailAddressConfirmed(); 72 | } 73 | 74 | @Test 75 | @Order(5) 76 | void confirmEmailAddress_withWrongConfirmationHash_whenItWasAlreadyConfirmed() { 77 | GIVEN(customerIsRegistered()); 78 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 79 | THEN_EmailAddressConfirmationFailed(); 80 | } 81 | 82 | @Test 83 | @Order(6) 84 | void changeEmailAddress() { 85 | GIVEN(customerIsRegistered()); 86 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 87 | THEN_EmailAddressChanged(); 88 | } 89 | 90 | @Test 91 | @Order(7) 92 | void changeEmailAddress_withUnchangedEmailAddress() { 93 | GIVEN(customerIsRegistered()); 94 | WHEN_ChangeEmailAddress_With(emailAddress); 95 | THEN_NothingShouldHappen(); 96 | } 97 | 98 | @Test 99 | @Order(8) 100 | void changeEmailAddress_whenItWasAlreadyChanged() { 101 | GIVEN(customerIsRegistered(), 102 | __and_EmailAddressWasChanged()); 103 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 104 | THEN_NothingShouldHappen(); 105 | } 106 | 107 | @Test 108 | @Order(9) 109 | void confirmEmailAddress_whenItWasPreviouslyConfirmedAndThenChanged() { 110 | GIVEN(customerIsRegistered(), 111 | __and_EmailAddressWasConfirmed(), 112 | __and_EmailAddressWasChanged()); 113 | WHEN_ConfirmEmailAddress_With(changedConfirmationHash); 114 | THEN_EmailAddressConfirmed(); 115 | } 116 | 117 | /** 118 | * Methods for GIVEN 119 | */ 120 | 121 | private void GIVEN(Event... events) { 122 | currentState = CustomerState.reconstitute(List.of(events)); 123 | } 124 | 125 | private CustomerRegistered customerIsRegistered() { 126 | return CustomerRegistered.build(customerID, emailAddress, confirmationHash, name); 127 | } 128 | 129 | private CustomerEmailAddressConfirmed __and_EmailAddressWasConfirmed() { 130 | return CustomerEmailAddressConfirmed.build(customerID); 131 | } 132 | 133 | private CustomerEmailAddressChanged __and_EmailAddressWasChanged() { 134 | return CustomerEmailAddressChanged.build(customerID, changedEmailAddress, changedConfirmationHash); 135 | } 136 | 137 | /** 138 | * Methods for WHEN 139 | */ 140 | 141 | private void WHEN_RegisterCustomer() { 142 | var registerCustomer = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 143 | customerRegistered = Customer7.register(registerCustomer); 144 | customerID = registerCustomer.customerID; 145 | confirmationHash = registerCustomer.confirmationHash; 146 | } 147 | 148 | private void WHEN_ConfirmEmailAddress_With(Hash confirmationHash) { 149 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 150 | try { 151 | recordedEvents = Customer7.confirmEmailAddress(currentState, command); 152 | } catch (NullPointerException e) { 153 | fail(THelper.propertyIsNull("confirmationHash")); 154 | } 155 | } 156 | 157 | private void WHEN_ChangeEmailAddress_With(EmailAddress emailAddress) { 158 | var command = ChangeCustomerEmailAddress.build(customerID.value, emailAddress.value); 159 | try { 160 | recordedEvents = Customer7.changeEmailAddress(currentState, command); 161 | changedConfirmationHash = command.confirmationHash; 162 | } catch (NullPointerException e) { 163 | fail(THelper.propertyIsNull("emailAddress")); 164 | } 165 | } 166 | 167 | /** 168 | * Methods for THEN 169 | */ 170 | 171 | private void THEN_CustomerRegistered() { 172 | var method = "register"; 173 | var eventName = "CustomerRegistered"; 174 | assertNotNull(customerRegistered, THelper.eventIsNull(method, eventName)); 175 | assertEquals(customerID, customerRegistered.customerID, THelper.propertyIsWrong(method, "customerID")); 176 | assertEquals(emailAddress, customerRegistered.emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 177 | assertEquals(confirmationHash, customerRegistered.confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 178 | assertEquals(name, customerRegistered.name, THelper.propertyIsWrong(method, "name")); 179 | } 180 | 181 | private void THEN_EmailAddressConfirmed() { 182 | var method = "confirmEmailAddress"; 183 | var eventName = "CustomerEmailAddressConfirmed"; 184 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 185 | var event = recordedEvents.get(0); 186 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 187 | assertEquals(CustomerEmailAddressConfirmed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 188 | var typedEvent = (CustomerEmailAddressConfirmed) event; 189 | assertEquals(customerID, typedEvent.customerID, THelper.propertyIsWrong(method, "customerID")); 190 | } 191 | 192 | private void THEN_EmailAddressConfirmationFailed() { 193 | var method = "confirmEmailAddress"; 194 | var eventName = "CustomerEmailAddressConfirmationFailed"; 195 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 196 | var event = recordedEvents.get(0); 197 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 198 | assertEquals(CustomerEmailAddressConfirmationFailed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 199 | var typedEvent = (CustomerEmailAddressConfirmationFailed) event; 200 | assertEquals(customerID, typedEvent.customerID, THelper.propertyIsWrong(method, "customerID")); 201 | } 202 | 203 | private void THEN_EmailAddressChanged() { 204 | var method = "changeEmailAddress"; 205 | var eventName = "CustomerEmailAddressChanged"; 206 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 207 | var event = recordedEvents.get(0); 208 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 209 | assertEquals(CustomerEmailAddressChanged.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 210 | var typedEvent = (CustomerEmailAddressChanged) event; 211 | assertEquals(customerID, typedEvent.customerID, THelper.propertyIsWrong(method, "customerID")); 212 | assertEquals(changedEmailAddress, typedEvent.emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 213 | assertEquals(changedConfirmationHash, typedEvent.confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 214 | } 215 | 216 | private void THEN_NothingShouldHappen() { 217 | assertEquals(0, recordedEvents.size(), 218 | THelper.noEventShouldHaveBeenRecorded(THelper.typeOfFirst(recordedEvents))); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/test/java/domain/functional/traditional/customer/Customer2Test.java: -------------------------------------------------------------------------------- 1 | package domain.functional.traditional.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.exception.WrongConfirmationHashException; 7 | import domain.shared.value.EmailAddress; 8 | import domain.shared.value.Hash; 9 | import domain.shared.value.ID; 10 | import domain.shared.value.PersonName; 11 | import org.junit.jupiter.api.*; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 16 | class Customer2Test { 17 | private ID customerID; 18 | private Hash confirmationHash; 19 | private PersonName name; 20 | private EmailAddress emailAddress; 21 | private EmailAddress changedEmailAddress; 22 | private Hash wrongConfirmationHash; 23 | private Hash changedConfirmationHash; 24 | private CustomerState registeredCustomer; 25 | 26 | @BeforeEach 27 | void beforeEach() { 28 | emailAddress = EmailAddress.build("john@doe.com"); 29 | changedEmailAddress = EmailAddress.build("john+changed@doe.com"); 30 | wrongConfirmationHash = Hash.generate(); 31 | changedConfirmationHash = Hash.generate(); 32 | name = PersonName.build("John", "Doe"); 33 | } 34 | 35 | @Test 36 | @Order(1) 37 | void registerCustomer() { 38 | // When 39 | var command = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 40 | var customer = Customer2.register(command); 41 | 42 | // Then it should succeed 43 | // and it should expose the expected state 44 | assertNotNull(customer); 45 | assertEquals(command.customerID, customer.id); 46 | assertEquals(command.name, customer.name); 47 | assertEquals(command.emailAddress, customer.emailAddress); 48 | assertEquals(command.confirmationHash, customer.confirmationHash); 49 | assertFalse(customer.isEmailAddressConfirmed); 50 | } 51 | 52 | @Test 53 | @Order(2) 54 | void confirmEmailAddress() { 55 | // Given 56 | givenARegisteredCustomer(); 57 | 58 | // When confirmCustomerEmailAddress 59 | // Then it should succeed 60 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 61 | var changedCustomer = assertDoesNotThrow(() -> Customer2.confirmEmailAddress(registeredCustomer, command)); 62 | 63 | // and the emailAddress of the changed Customer should be confirmed 64 | assertTrue(changedCustomer.isEmailAddressConfirmed); 65 | } 66 | 67 | @Test 68 | @Order(3) 69 | void confirmEmailAddress_withWrongConfirmationHash() { 70 | // Given 71 | givenARegisteredCustomer(); 72 | 73 | // When confirmCustomerEmailAddress 74 | // Then it should throw WrongConfirmationHashException 75 | var command = ConfirmCustomerEmailAddress.build(customerID.value, wrongConfirmationHash.value); 76 | assertThrows(WrongConfirmationHashException.class, () -> Customer2.confirmEmailAddress(registeredCustomer, command)); 77 | } 78 | 79 | @Test 80 | @Order(6) 81 | void changeEmailAddress() { 82 | // Given 83 | givenARegisteredCustomer(); 84 | 85 | // When changeCustomerEmailAddress 86 | var command = ChangeCustomerEmailAddress.build(customerID.value, changedEmailAddress.value); 87 | var changedCustomer = Customer2.changeEmailAddress(registeredCustomer, command); 88 | 89 | // Then the emailAddress and confirmationHash should be changed and the emailAddress should be unconfirmed 90 | assertEquals(command.emailAddress, changedCustomer.emailAddress); 91 | assertEquals(command.confirmationHash, changedCustomer.confirmationHash); 92 | assertFalse(changedCustomer.isEmailAddressConfirmed); 93 | } 94 | 95 | @Test 96 | @Order(9) 97 | void confirmEmailAddress_whenItWasPreviouslyConfirmedAndThenChanged() { 98 | // Given 99 | givenARegisteredCustomer(); 100 | givenEmailAddressWasConfirmed(); 101 | givenEmailAddressWasChanged(); 102 | 103 | // When confirmEmailAddress 104 | // Then it should throw WrongConfirmationHashException 105 | var command = ConfirmCustomerEmailAddress.build(customerID.value, changedConfirmationHash.value); 106 | var changedCustomer = assertDoesNotThrow(() -> Customer2.confirmEmailAddress(registeredCustomer, command)); 107 | 108 | // and the emailAddress of the changed Customer should be confirmed 109 | assertTrue(changedCustomer.isEmailAddressConfirmed); 110 | } 111 | 112 | /** 113 | * Helper methods to set up the Given state 114 | */ 115 | private void givenARegisteredCustomer() { 116 | var register = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 117 | customerID = register.customerID; 118 | confirmationHash = register.confirmationHash; 119 | registeredCustomer = Customer2.register(register); 120 | } 121 | 122 | private void givenEmailAddressWasConfirmed() { 123 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 124 | 125 | try { 126 | registeredCustomer = Customer2.confirmEmailAddress(registeredCustomer, command); 127 | } catch (WrongConfirmationHashException e) { 128 | fail("unexpected error in givenEmailAddressWasConfirmed: " + e.getMessage()); 129 | } 130 | } 131 | 132 | private void givenEmailAddressWasChanged() { 133 | var command = ChangeCustomerEmailAddress.build(customerID.value, changedEmailAddress.value); 134 | changedConfirmationHash = command.confirmationHash; 135 | registeredCustomer = Customer2.changeEmailAddress(registeredCustomer, command); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/domain/oop/es/customer/Customer3Test.java: -------------------------------------------------------------------------------- 1 | package domain.oop.es.customer; 2 | 3 | import domain.THelper; 4 | import domain.shared.command.ChangeCustomerEmailAddress; 5 | import domain.shared.command.ConfirmCustomerEmailAddress; 6 | import domain.shared.command.RegisterCustomer; 7 | import domain.shared.event.*; 8 | import domain.shared.value.EmailAddress; 9 | import domain.shared.value.Hash; 10 | import domain.shared.value.ID; 11 | import domain.shared.value.PersonName; 12 | import org.junit.jupiter.api.*; 13 | 14 | import java.util.List; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 19 | class Customer3Test { 20 | private ID customerID; 21 | private EmailAddress emailAddress; 22 | private EmailAddress changedEmailAddress; 23 | private Hash confirmationHash; 24 | private Hash wrongConfirmationHash; 25 | private Hash changedConfirmationHash; 26 | private PersonName name; 27 | private CustomerRegistered customerRegistered; 28 | private List recordedEvents; 29 | private Customer3 registeredCustomer; 30 | 31 | @BeforeEach 32 | void beforeEach() { 33 | customerID = ID.generate(); 34 | emailAddress = EmailAddress.build("john@doe.com"); 35 | changedEmailAddress = EmailAddress.build("john+changed@doe.com"); 36 | confirmationHash = Hash.generate(); 37 | wrongConfirmationHash = Hash.generate(); 38 | changedConfirmationHash = Hash.generate(); 39 | name = PersonName.build("John", "Doe"); 40 | } 41 | 42 | @Test 43 | @Order(1) 44 | void registerCustomer() { 45 | WHEN_RegisterCustomer(); 46 | THEN_CustomerRegistered(); 47 | } 48 | 49 | @Test 50 | @Order(2) 51 | void confirmEmailAddress() { 52 | GIVEN(customerIsRegistered()); 53 | WHEN_ConfirmEmailAddress_With(confirmationHash); 54 | THEN_EmailAddressConfirmed(); 55 | } 56 | 57 | @Test 58 | @Order(3) 59 | void confirmEmailAddress_withWrongConfirmationHash() { 60 | GIVEN(customerIsRegistered()); 61 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 62 | THEN_EmailAddressConfirmationFailed(); 63 | } 64 | 65 | @Test 66 | @Order(4) 67 | void confirmEmailAddress_whenItWasAlreadyConfirmed() { 68 | GIVEN(customerIsRegistered(), 69 | __and_EmailAddressWasConfirmed()); 70 | WHEN_ConfirmEmailAddress_With(confirmationHash); 71 | THEN_NothingShouldHappen(); 72 | } 73 | 74 | @Test 75 | @Order(5) 76 | void confirmEmailAddress_withWrongConfirmationHash_whenItWasAlreadyConfirmed() { 77 | GIVEN(customerIsRegistered(), 78 | __and_EmailAddressWasConfirmed()); 79 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 80 | THEN_EmailAddressConfirmationFailed(); 81 | } 82 | 83 | @Test 84 | @Order(6) 85 | void changeEmailAddress() { 86 | GIVEN(customerIsRegistered()); 87 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 88 | THEN_EmailAddressChanged(); 89 | } 90 | 91 | @Test 92 | @Order(7) 93 | void changeEmailAddress_withUnchangedEmailAddress() { 94 | GIVEN(customerIsRegistered()); 95 | WHEN_ChangeEmailAddress_With(emailAddress); 96 | THEN_NothingShouldHappen(); 97 | } 98 | 99 | @Test 100 | @Order(8) 101 | void changeEmailAddress_whenItWasAlreadyChanged() { 102 | GIVEN(customerIsRegistered(), 103 | __and_EmailAddressWasChanged()); 104 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 105 | THEN_NothingShouldHappen(); 106 | } 107 | 108 | @Test 109 | @Order(9) 110 | void confirmEmailAddress_whenItWasPreviouslyConfirmedAndThenChanged() { 111 | GIVEN(customerIsRegistered(), 112 | __and_EmailAddressWasConfirmed(), 113 | __and_EmailAddressWasChanged()); 114 | WHEN_ConfirmEmailAddress_With(changedConfirmationHash); 115 | THEN_EmailAddressConfirmed(); 116 | } 117 | 118 | /** 119 | * Methods for GIVEN 120 | */ 121 | 122 | private void GIVEN(Event... events) { 123 | registeredCustomer = Customer3.reconstitute(List.of(events)); 124 | } 125 | 126 | private CustomerRegistered customerIsRegistered() { 127 | return CustomerRegistered.build(customerID, emailAddress, confirmationHash, name); 128 | } 129 | 130 | private CustomerEmailAddressConfirmed __and_EmailAddressWasConfirmed() { 131 | return CustomerEmailAddressConfirmed.build(customerID); 132 | } 133 | 134 | private CustomerEmailAddressChanged __and_EmailAddressWasChanged() { 135 | return CustomerEmailAddressChanged.build(customerID, changedEmailAddress, changedConfirmationHash); 136 | } 137 | 138 | /** 139 | * Methods for WHEN 140 | */ 141 | 142 | private void WHEN_RegisterCustomer() { 143 | var registerCustomer = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 144 | customerRegistered = Customer3.register(registerCustomer); 145 | customerID = registerCustomer.customerID; 146 | confirmationHash = registerCustomer.confirmationHash; 147 | } 148 | 149 | private void WHEN_ConfirmEmailAddress_With(Hash confirmationHash) { 150 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 151 | try { 152 | recordedEvents = registeredCustomer.confirmEmailAddress(command); 153 | } catch (NullPointerException e) { 154 | fail(THelper.propertyIsNull("confirmationHash")); 155 | } 156 | } 157 | 158 | private void WHEN_ChangeEmailAddress_With(EmailAddress emailAddress) { 159 | var command = ChangeCustomerEmailAddress.build(customerID.value, emailAddress.value); 160 | try { 161 | recordedEvents = registeredCustomer.changeEmailAddress(command); 162 | } catch (NullPointerException e) { 163 | fail(THelper.propertyIsNull("emailAddress")); 164 | } 165 | } 166 | 167 | /** 168 | * Methods for THEN 169 | */ 170 | 171 | private void THEN_CustomerRegistered() { 172 | var method = "register"; 173 | var eventName = "CustomerRegistered"; 174 | assertNotNull(customerRegistered, THelper.eventIsNull("register", eventName)); 175 | assertEquals(customerID, customerRegistered.customerID, THelper.propertyIsWrong(method, "customerID")); 176 | assertEquals(emailAddress, customerRegistered.emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 177 | assertEquals(confirmationHash, customerRegistered.confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 178 | assertEquals(name, customerRegistered.name, THelper.propertyIsWrong(method, "name")); 179 | } 180 | 181 | private void THEN_EmailAddressConfirmed() { 182 | var method = "confirmEmailAddress"; 183 | var eventName = "CustomerEmailAddressConfirmed"; 184 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 185 | var event = recordedEvents.get(0); 186 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 187 | assertEquals(CustomerEmailAddressConfirmed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 188 | assertEquals(customerID, ((CustomerEmailAddressConfirmed) event).customerID, THelper.propertyIsWrong(method, "customerID")); 189 | } 190 | 191 | private void THEN_EmailAddressConfirmationFailed() { 192 | var method = "confirmEmailAddress"; 193 | var eventName = "CustomerEmailAddressConfirmationFailed"; 194 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 195 | var event = recordedEvents.get(0); 196 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 197 | assertEquals(CustomerEmailAddressConfirmationFailed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 198 | assertEquals(customerID, ((CustomerEmailAddressConfirmationFailed) event).customerID, THelper.propertyIsWrong(method, "customerID")); 199 | } 200 | 201 | private void THEN_EmailAddressChanged() { 202 | var method = "changeEmailAddress"; 203 | var eventName = "CustomerEmailAddressChanged"; 204 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 205 | var event = recordedEvents.get(0); 206 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 207 | assertEquals(CustomerEmailAddressChanged.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 208 | assertEquals(customerID, ((CustomerEmailAddressChanged) event).customerID, THelper.propertyIsWrong(method, "customerID")); 209 | assertEquals(changedEmailAddress, ((CustomerEmailAddressChanged) event).emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 210 | } 211 | 212 | private void THEN_NothingShouldHappen() { 213 | assertEquals(0, recordedEvents.size(), THelper.noEventShouldHaveBeenRecorded(THelper.typeOfFirst(recordedEvents))); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/test/java/domain/oop/es/customer/Customer4Test.java: -------------------------------------------------------------------------------- 1 | package domain.oop.es.customer; 2 | 3 | import domain.THelper; 4 | import domain.shared.command.ChangeCustomerEmailAddress; 5 | import domain.shared.command.ConfirmCustomerEmailAddress; 6 | import domain.shared.command.RegisterCustomer; 7 | import domain.shared.event.*; 8 | import domain.shared.value.EmailAddress; 9 | import domain.shared.value.Hash; 10 | import domain.shared.value.ID; 11 | import domain.shared.value.PersonName; 12 | import org.junit.jupiter.api.*; 13 | 14 | import java.util.List; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 19 | class Customer4Test { 20 | private ID customerID; 21 | private EmailAddress emailAddress; 22 | private EmailAddress changedEmailAddress; 23 | private Hash confirmationHash; 24 | private Hash wrongConfirmationHash; 25 | private Hash changedConfirmationHash; 26 | private PersonName name; 27 | private Customer4 registeredCustomer; 28 | 29 | @BeforeEach 30 | void beforeEach() { 31 | customerID = ID.generate(); 32 | emailAddress = EmailAddress.build("john@doe.com"); 33 | changedEmailAddress = EmailAddress.build("john+changed@doe.com"); 34 | confirmationHash = Hash.generate(); 35 | wrongConfirmationHash = Hash.generate(); 36 | changedConfirmationHash = Hash.generate(); 37 | name = PersonName.build("John", "Doe"); 38 | } 39 | 40 | @Test 41 | @Order(1) 42 | void registerCustomer() { 43 | WHEN_RegisterCustomer(); 44 | THEN_CustomerRegistered(); 45 | } 46 | 47 | @Test 48 | @Order(2) 49 | void confirmEmailAddress() { 50 | GIVEN(customerIsRegistered()); 51 | WHEN_ConfirmEmailAddress_With(confirmationHash); 52 | THEN_EmailAddressConfirmed(); 53 | } 54 | 55 | @Test 56 | @Order(3) 57 | void confirmEmailAddress_withWrongConfirmationHash() { 58 | GIVEN(customerIsRegistered()); 59 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 60 | THEN_EmailAddressConfirmationFailed(); 61 | } 62 | 63 | @Test 64 | @Order(4) 65 | void confirmEmailAddress_whenItWasAlreadyConfirmed() { 66 | GIVEN(customerIsRegistered(), 67 | __and_EmailAddressWasConfirmed()); 68 | WHEN_ConfirmEmailAddress_With(confirmationHash); 69 | THEN_NothingShouldHappen(); 70 | } 71 | 72 | @Test 73 | @Order(5) 74 | void confirmEmailAddress_withWrongConfirmationHash_whenItWasAlreadyConfirmed() { 75 | GIVEN(customerIsRegistered(), 76 | __and_EmailAddressWasConfirmed()); 77 | WHEN_ConfirmEmailAddress_With(wrongConfirmationHash); 78 | THEN_EmailAddressConfirmationFailed(); 79 | } 80 | 81 | @Test 82 | @Order(6) 83 | void changeEmailAddress() { 84 | // Given 85 | GIVEN(customerIsRegistered()); 86 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 87 | THEN_EmailAddressChanged(); 88 | } 89 | 90 | @Test 91 | @Order(7) 92 | void changeEmailAddress_withUnchangedEmailAddress() { 93 | GIVEN(customerIsRegistered()); 94 | WHEN_ChangeEmailAddress_With(emailAddress); 95 | THEN_NothingShouldHappen(); 96 | } 97 | 98 | @Test 99 | @Order(8) 100 | void changeEmailAddress_whenItWasAlreadyChanged() { 101 | GIVEN(customerIsRegistered(), 102 | __and_EmailAddressWasChanged()); 103 | WHEN_ChangeEmailAddress_With(changedEmailAddress); 104 | THEN_NothingShouldHappen(); 105 | } 106 | 107 | @Test 108 | @Order(9) 109 | void confirmEmailAddress_whenItWasPreviouslyConfirmedAndThenChanged() { 110 | GIVEN(customerIsRegistered(), 111 | __and_EmailAddressWasConfirmed(), 112 | __and_EmailAddressWasChanged()); 113 | WHEN_ConfirmEmailAddress_With(changedConfirmationHash); 114 | THEN_EmailAddressConfirmed(); 115 | } 116 | 117 | /** 118 | * Methods for GIVEN 119 | */ 120 | 121 | private void GIVEN(Event... events) { 122 | registeredCustomer = Customer4.reconstitute(List.of(events)); 123 | } 124 | 125 | private CustomerRegistered customerIsRegistered() { 126 | return CustomerRegistered.build(customerID, emailAddress, confirmationHash, name); 127 | } 128 | 129 | private CustomerEmailAddressConfirmed __and_EmailAddressWasConfirmed() { 130 | return CustomerEmailAddressConfirmed.build(customerID); 131 | } 132 | 133 | private CustomerEmailAddressChanged __and_EmailAddressWasChanged() { 134 | return CustomerEmailAddressChanged.build(customerID, changedEmailAddress, changedConfirmationHash); 135 | } 136 | 137 | /** 138 | * Methods for WHEN 139 | */ 140 | 141 | private void WHEN_RegisterCustomer() { 142 | var registerCustomer = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 143 | registeredCustomer = Customer4.register(registerCustomer); 144 | customerID = registerCustomer.customerID; 145 | confirmationHash = registerCustomer.confirmationHash; 146 | } 147 | 148 | private void WHEN_ConfirmEmailAddress_With(Hash confirmationHash) { 149 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 150 | try { 151 | registeredCustomer.confirmEmailAddress(command); 152 | } catch (NullPointerException e) { 153 | fail(THelper.propertyIsNull("confirmationHash")); 154 | } 155 | } 156 | 157 | private void WHEN_ChangeEmailAddress_With(EmailAddress emailAddress) { 158 | var command = ChangeCustomerEmailAddress.build(customerID.value, emailAddress.value); 159 | try { 160 | registeredCustomer.changeEmailAddress(command); 161 | } catch (NullPointerException e) { 162 | fail(THelper.propertyIsNull("emailAddress")); 163 | } 164 | } 165 | 166 | /** 167 | * Methods for THEN 168 | */ 169 | 170 | void THEN_CustomerRegistered() { 171 | var method = "register"; 172 | var eventName = "CustomerRegistered"; 173 | var recordedEvents = registeredCustomer.getRecordedEvents(); 174 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 175 | var event = recordedEvents.get(0); 176 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 177 | assertEquals(CustomerRegistered.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 178 | assertEquals(customerID, ((CustomerRegistered) event).customerID, THelper.propertyIsWrong(method, "customerID")); 179 | assertEquals(emailAddress, ((CustomerRegistered) event).emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 180 | assertEquals(confirmationHash, ((CustomerRegistered) event).confirmationHash, THelper.propertyIsWrong(method, "confirmationHash")); 181 | assertEquals(name, ((CustomerRegistered) event).name, THelper.propertyIsWrong(method, "name")); 182 | } 183 | 184 | void THEN_EmailAddressConfirmed() { 185 | var method = "confirmEmailAddress"; 186 | var eventName = "CustomerEmailAddressConfirmed"; 187 | var recordedEvents = registeredCustomer.getRecordedEvents(); 188 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 189 | var event = recordedEvents.get(0); 190 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 191 | assertEquals(CustomerEmailAddressConfirmed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 192 | assertEquals(customerID, ((CustomerEmailAddressConfirmed) event).customerID, THelper.propertyIsWrong(method, "customerID")); 193 | } 194 | 195 | void THEN_EmailAddressConfirmationFailed() { 196 | var method = "confirmEmailAddress"; 197 | var eventName = "CustomerEmailAddressConfirmationFailed"; 198 | var recordedEvents = registeredCustomer.getRecordedEvents(); 199 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 200 | var event = recordedEvents.get(0); 201 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 202 | assertEquals(CustomerEmailAddressConfirmationFailed.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 203 | assertEquals(customerID, ((CustomerEmailAddressConfirmationFailed) event).customerID, THelper.propertyIsWrong(method, "customerID")); 204 | } 205 | 206 | private void THEN_EmailAddressChanged() { 207 | var method = "changeEmailAddress"; 208 | var eventName = "CustomerEmailAddressChanged"; 209 | var recordedEvents = registeredCustomer.getRecordedEvents(); 210 | assertEquals(1, recordedEvents.size(), THelper.noEventWasRecorded(method, eventName)); 211 | var event = recordedEvents.get(0); 212 | assertNotNull(event, THelper.eventIsNull(method, eventName)); 213 | assertEquals(CustomerEmailAddressChanged.class, event.getClass(), THelper.eventOfWrongTypeWasRecorded(method)); 214 | assertEquals(customerID, ((CustomerEmailAddressChanged) event).customerID, THelper.propertyIsWrong(method, "customerID")); 215 | assertEquals(changedEmailAddress, ((CustomerEmailAddressChanged) event).emailAddress, THelper.propertyIsWrong(method, "emailAddress")); 216 | } 217 | 218 | void THEN_NothingShouldHappen() { 219 | var recordedEvents = registeredCustomer.getRecordedEvents(); 220 | assertEquals(0, registeredCustomer.getRecordedEvents().size(), THelper.noEventShouldHaveBeenRecorded(THelper.typeOfFirst(recordedEvents))); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/test/java/domain/oop/traditional/customer/Customer1Test.java: -------------------------------------------------------------------------------- 1 | package domain.oop.traditional.customer; 2 | 3 | import domain.shared.command.ChangeCustomerEmailAddress; 4 | import domain.shared.command.ConfirmCustomerEmailAddress; 5 | import domain.shared.command.RegisterCustomer; 6 | import domain.shared.exception.WrongConfirmationHashException; 7 | import domain.shared.value.EmailAddress; 8 | import domain.shared.value.Hash; 9 | import domain.shared.value.ID; 10 | import domain.shared.value.PersonName; 11 | import org.junit.jupiter.api.*; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 16 | class Customer1Test { 17 | private ID customerID; 18 | private Hash confirmationHash; 19 | private PersonName name; 20 | private EmailAddress emailAddress; 21 | private EmailAddress changedEmailAddress; 22 | private Hash wrongConfirmationHash; 23 | private Hash changedConfirmationHash; 24 | private Customer1 registeredCustomer; 25 | 26 | @BeforeEach 27 | void beforeEach() { 28 | emailAddress = EmailAddress.build("john@doe.com"); 29 | changedEmailAddress = EmailAddress.build("john+changed@doe.com"); 30 | wrongConfirmationHash = Hash.generate(); 31 | changedConfirmationHash = Hash.generate(); 32 | name = PersonName.build("John", "Doe"); 33 | } 34 | 35 | @Test 36 | @Order(1) 37 | void registerCustomer() { 38 | // When registerCustomer 39 | var command = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 40 | var customer = Customer1.register(command); 41 | 42 | // Then it should succeed 43 | // and should have the expected state 44 | assertNotNull(customer); 45 | assertEquals(customer.id, command.customerID); 46 | assertEquals(customer.name, command.name); 47 | assertEquals(customer.emailAddress, command.emailAddress); 48 | assertEquals(customer.confirmationHash, command.confirmationHash); 49 | assertFalse(customer.isEmailAddressConfirmed); 50 | } 51 | 52 | @Test 53 | @Order(2) 54 | void confirmEmailAddress() { 55 | // Given 56 | givenARegisteredCustomer(); 57 | 58 | // When confirmCustomerEmailAddress 59 | // Then it should succeed 60 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 61 | assertDoesNotThrow(() -> registeredCustomer.confirmEmailAddress(command)); 62 | 63 | // and the emailAddress should be confirmed 64 | assertTrue(registeredCustomer.isEmailAddressConfirmed); 65 | } 66 | 67 | @Test 68 | @Order(3) 69 | void confirmEmailAddress_withWrongConfirmationHash() { 70 | // Given 71 | givenARegisteredCustomer(); 72 | 73 | // When confirmCustomerEmailAddress 74 | // Then it should throw WrongConfirmationHashException 75 | var command = ConfirmCustomerEmailAddress.build(customerID.value, wrongConfirmationHash.value); 76 | assertThrows(WrongConfirmationHashException.class, () -> registeredCustomer.confirmEmailAddress(command)); 77 | 78 | // and the emailAddress should not be confirmed 79 | assertFalse(registeredCustomer.isEmailAddressConfirmed); 80 | } 81 | 82 | @Test 83 | @Order(6) 84 | void changeEmailAddress() { 85 | // Given 86 | givenARegisteredCustomer(); 87 | 88 | // When changeCustomerEmailAddress 89 | var command = ChangeCustomerEmailAddress.build(customerID.value, changedEmailAddress.value); 90 | registeredCustomer.changeEmailAddress(command); 91 | 92 | // Then the emailAddress and confirmationHash should be changed and the emailAddress should be unconfirmed 93 | assertEquals(registeredCustomer.emailAddress, command.emailAddress); 94 | assertEquals(registeredCustomer.confirmationHash, command.confirmationHash); 95 | assertFalse(registeredCustomer.isEmailAddressConfirmed); 96 | } 97 | 98 | @Test 99 | @Order(9) 100 | void confirmEmailAddress_whenItWasPreviouslyConfirmedAndThenChanged() { 101 | // Given 102 | givenARegisteredCustomer(); 103 | givenEmailAddressWasConfirmed(); 104 | givenEmailAddressWasChanged(); 105 | 106 | // When confirmCustomerEmailAddress 107 | // Then it should succeed 108 | var command = ConfirmCustomerEmailAddress.build(customerID.value, changedConfirmationHash.value); 109 | assertDoesNotThrow(() -> registeredCustomer.confirmEmailAddress(command)); 110 | 111 | // and the emailAddress should be confirmed 112 | assertTrue(registeredCustomer.isEmailAddressConfirmed); 113 | } 114 | 115 | /** 116 | * Helper methods to set up the Given state 117 | */ 118 | private void givenARegisteredCustomer() { 119 | var register = RegisterCustomer.build(emailAddress.value, name.givenName, name.familyName); 120 | customerID = register.customerID; 121 | confirmationHash = register.confirmationHash; 122 | registeredCustomer = Customer1.register(register); 123 | } 124 | 125 | private void givenEmailAddressWasConfirmed() { 126 | var command = ConfirmCustomerEmailAddress.build(customerID.value, confirmationHash.value); 127 | 128 | try { 129 | registeredCustomer.confirmEmailAddress(command); 130 | } catch (WrongConfirmationHashException e) { 131 | fail("unexpected error in givenEmailAddressWasConfirmed: " + e.getMessage()); 132 | } 133 | } 134 | 135 | private void givenEmailAddressWasChanged() { 136 | var command = ChangeCustomerEmailAddress.build(customerID.value, changedEmailAddress.value); 137 | changedConfirmationHash = command.confirmationHash; 138 | registeredCustomer.changeEmailAddress(command); 139 | } 140 | } 141 | --------------------------------------------------------------------------------