diff --git a/spring-reactive-architecture/.gitignore b/spring-reactive-architecture/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/spring-reactive-architecture/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-reactive-architecture/.mvn/wrapper/maven-wrapper.jar b/spring-reactive-architecture/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..c1dd12f
Binary files /dev/null and b/spring-reactive-architecture/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-reactive-architecture/.mvn/wrapper/maven-wrapper.properties b/spring-reactive-architecture/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..b7cb93e
--- /dev/null
+++ b/spring-reactive-architecture/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/spring-reactive-architecture/README.md b/spring-reactive-architecture/README.md
new file mode 100644
index 0000000..afeca0d
--- /dev/null
+++ b/spring-reactive-architecture/README.md
@@ -0,0 +1,4 @@
+# Related Blog Posts
+
+* [A Reactive Architecture using Spring Boot](https://reflectoring.io/reactive-architecture-using-spring-boot/)
+
diff --git a/spring-reactive-architecture/account-management-service/.gitignore b/spring-reactive-architecture/account-management-service/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-reactive-architecture/account-management-service/.mvn/wrapper/maven-wrapper.jar b/spring-reactive-architecture/account-management-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..c1dd12f
Binary files /dev/null and b/spring-reactive-architecture/account-management-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-reactive-architecture/account-management-service/.mvn/wrapper/maven-wrapper.properties b/spring-reactive-architecture/account-management-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..b7cb93e
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/spring-reactive-architecture/account-management-service/Dockerfile b/spring-reactive-architecture/account-management-service/Dockerfile
new file mode 100644
index 0000000..33ca942
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/Dockerfile
@@ -0,0 +1,3 @@
+FROM openjdk:8-jdk-alpine
+COPY target/account-management-service-0.0.1-SNAPSHOT.jar app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/spring-reactive-architecture/account-management-service/mvnw b/spring-reactive-architecture/account-management-service/mvnw
new file mode 100644
index 0000000..8a8fb22
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-reactive-architecture/account-management-service/mvnw.cmd b/spring-reactive-architecture/account-management-service/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/spring-reactive-architecture/account-management-service/pom.xml b/spring-reactive-architecture/account-management-service/pom.xml
new file mode 100644
index 0000000..0b5b2c8
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.3
+
+
+ io.reflectoring
+ account-management-service
+ 0.0.1-SNAPSHOT
+ account-management-service
+ Spring Boot microservice to manage User accounts per transaction
+
+
+ 1.8
+ 2020.0.3
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-stream-kafka
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.1.0
+
+
+
+
+
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/AccountManagementServiceApplication.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/AccountManagementServiceApplication.java
new file mode 100644
index 0000000..0fcae01
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/AccountManagementServiceApplication.java
@@ -0,0 +1,13 @@
+package io.reflectoring.account_management_service;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AccountManagementServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AccountManagementServiceApplication.class, args);
+ }
+
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/constant/TransactionStatus.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/constant/TransactionStatus.java
new file mode 100644
index 0000000..ace1bfd
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/constant/TransactionStatus.java
@@ -0,0 +1,15 @@
+package io.reflectoring.account_management_service.constant;
+
+public enum TransactionStatus {
+ INITIATED,
+ SUCCESS,
+ FAILURE,
+ CANCELLED,
+ VALID,
+ ACCOUNT_BLOCKED,
+ CARD_INVALID,
+ FUNDS_UNAVAILABLE,
+ FRAUDULENT,
+ FRAUDULENT_NOTIFY_SUCCESS,
+ FRAUDULENT_NOTIFY_FAILURE
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/controller/AccountManagementController.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/controller/AccountManagementController.java
new file mode 100644
index 0000000..eb7ac93
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/controller/AccountManagementController.java
@@ -0,0 +1,26 @@
+package io.reflectoring.account_management_service.controller;
+
+import io.reflectoring.account_management_service.model.Transaction;
+import io.reflectoring.account_management_service.service.AccountManagementService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@RestController
+@RequestMapping("/banking")
+public class AccountManagementController {
+
+ @Autowired
+ private AccountManagementService accountManagementService;
+
+ @PostMapping("/process")
+ public Mono manage(@RequestBody Transaction transaction) {
+ log.info("Process transaction with details in account management service: {}", transaction);
+ return accountManagementService.manage(transaction);
+ }
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/kafka/consumer/TransactionConsumer.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/kafka/consumer/TransactionConsumer.java
new file mode 100644
index 0000000..c9a5dfc
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/kafka/consumer/TransactionConsumer.java
@@ -0,0 +1,19 @@
+package io.reflectoring.account_management_service.kafka.consumer;
+
+import io.reflectoring.account_management_service.model.Transaction;
+import io.reflectoring.account_management_service.service.AccountManagementService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.function.Consumer;
+
+@Slf4j
+@Configuration
+public class TransactionConsumer {
+
+ @Bean
+ public Consumer consumeTransaction(AccountManagementService accountManagementService) {
+ return accountManagementService::asyncProcess;
+ }
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/kafka/producer/TransactionProducer.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/kafka/producer/TransactionProducer.java
new file mode 100644
index 0000000..f4dcc60
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/kafka/producer/TransactionProducer.java
@@ -0,0 +1,29 @@
+package io.reflectoring.account_management_service.kafka.producer;
+
+import io.reflectoring.account_management_service.model.Transaction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+@Service
+public class TransactionProducer {
+
+ @Autowired
+ private StreamBridge streamBridge;
+
+ public void sendMessage(Transaction transaction) {
+ Message msg = MessageBuilder.withPayload(transaction)
+ .setHeader(KafkaHeaders.MESSAGE_KEY, transaction.getTransactionId().getBytes(StandardCharsets.UTF_8))
+ .build();
+ log.info("Transaction processed to dispatch: {}; Message dispatch successful: {}",
+ msg,
+ streamBridge.send("transaction-out-0", msg));
+ }
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/model/Transaction.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/model/Transaction.java
new file mode 100644
index 0000000..a705b41
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/model/Transaction.java
@@ -0,0 +1,37 @@
+package io.reflectoring.account_management_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.reflectoring.account_management_service.constant.TransactionStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class Transaction {
+
+ @Id
+ @JsonProperty("transaction_id")
+ private String transactionId;
+ private String date;
+
+ @JsonProperty("amount_deducted")
+ private double amountDeducted;
+
+ @JsonProperty("store_name")
+ private String storeName;
+
+ @JsonProperty("store_id")
+ private String storeId;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("transaction_location")
+ private String transactionLocation;
+ private TransactionStatus status;
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/model/User.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/model/User.java
new file mode 100644
index 0000000..ff4367d
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/model/User.java
@@ -0,0 +1,58 @@
+package io.reflectoring.account_management_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Transient;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class User {
+
+ @Id
+ private String id;
+
+ @JsonProperty("first_name")
+ private String firstName;
+
+ @JsonProperty("last_name")
+ private String lastName;
+ private String email;
+ private String address;
+
+ @JsonProperty("home_country")
+ private String homeCountry;
+ private String gender;
+ private String mobile;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("account_number")
+ private String accountNumber;
+
+ @JsonProperty("account_type")
+ private String accountType;
+
+ @JsonProperty("account_locked")
+ private boolean accountLocked;
+
+ @JsonProperty("fraudulent_activity_attempt_count")
+ private Long fraudulentActivityAttemptCount;
+
+ @Transient
+ @JsonProperty("valid_transactions")
+ private List validTransactions;
+
+ @Transient
+ @JsonProperty("fraudulent_transactions")
+ private List fraudulentTransactions;
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/repository/TransactionRepository.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/repository/TransactionRepository.java
new file mode 100644
index 0000000..3858c2b
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/repository/TransactionRepository.java
@@ -0,0 +1,7 @@
+package io.reflectoring.account_management_service.repository;
+
+import io.reflectoring.account_management_service.model.Transaction;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+
+public interface TransactionRepository extends ReactiveMongoRepository {
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/repository/UserRepository.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/repository/UserRepository.java
new file mode 100644
index 0000000..f5f950a
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/repository/UserRepository.java
@@ -0,0 +1,10 @@
+package io.reflectoring.account_management_service.repository;
+
+import io.reflectoring.account_management_service.model.User;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import reactor.core.publisher.Mono;
+
+public interface UserRepository extends ReactiveMongoRepository {
+
+ Mono findByCardId(String cardId);
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/service/AccountManagementService.java b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/service/AccountManagementService.java
new file mode 100644
index 0000000..1777e7a
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/java/io/reflectoring/account_management_service/service/AccountManagementService.java
@@ -0,0 +1,84 @@
+package io.reflectoring.account_management_service.service;
+
+import io.reflectoring.account_management_service.constant.TransactionStatus;
+import io.reflectoring.account_management_service.kafka.producer.TransactionProducer;
+import io.reflectoring.account_management_service.model.Transaction;
+import io.reflectoring.account_management_service.repository.TransactionRepository;
+import io.reflectoring.account_management_service.repository.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+@Service
+public class AccountManagementService {
+
+ @Autowired
+ private TransactionRepository transactionRepo;
+
+ @Autowired
+ private UserRepository userRepo;
+
+ @Autowired
+ private TransactionProducer producer;
+
+ public Mono manage(Transaction transaction) {
+ return userRepo.findByCardId(transaction.getCardId())
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.VALID)) {
+ List newList = new ArrayList<>();
+ newList.add(transaction);
+ if (Objects.isNull(u.getValidTransactions()) || u.getValidTransactions().isEmpty()) {
+ u.setValidTransactions(newList);
+ } else {
+ u.getValidTransactions().add(transaction);
+ }
+ }
+ log.info("User details: {}", u);
+ return u;
+ })
+ .flatMap(userRepo::save)
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.VALID)) {
+ transaction.setStatus(TransactionStatus.SUCCESS);
+ }
+ return transaction;
+ })
+ .flatMap(transactionRepo::save);
+ }
+
+ public void asyncProcess(Transaction transaction) {
+ userRepo.findByCardId(transaction.getCardId())
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.VALID)) {
+ List newList = new ArrayList<>();
+ newList.add(transaction);
+ if (Objects.isNull(u.getValidTransactions()) || u.getValidTransactions().isEmpty()) {
+ u.setValidTransactions(newList);
+ } else {
+ u.getValidTransactions().add(transaction);
+ }
+ }
+ log.info("User details: {}", u);
+ return u;
+ })
+ .flatMap(userRepo::save)
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.VALID)) {
+ transaction.setStatus(TransactionStatus.SUCCESS);
+ producer.sendMessage(transaction);
+ }
+ return transaction;
+ })
+ .filter(t -> t.getStatus().equals(TransactionStatus.VALID)
+ || t.getStatus().equals(TransactionStatus.SUCCESS)
+ )
+ .flatMap(transactionRepo::save)
+ .subscribe();
+ }
+}
diff --git a/spring-reactive-architecture/account-management-service/src/main/resources/application.yml b/spring-reactive-architecture/account-management-service/src/main/resources/application.yml
new file mode 100644
index 0000000..26596fd
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/main/resources/application.yml
@@ -0,0 +1,38 @@
+server:
+ port: 8083
+
+# Configure Spring specific properties
+spring:
+
+ # Enable/Disable hot swapping
+ devtools:
+ restart:
+ enabled: true
+ log-condition-evaluation-delta: false
+
+ # Datasource Configurations
+ data:
+ mongodb:
+ authentication-database: admin
+ uri: mongodb://vlab045701.dom045700.lab:27017/reactive
+ database: reactive
+
+ # Kafka Configuration
+ cloud:
+ function:
+ definition: consumeTransaction
+ stream:
+ kafka:
+ binder:
+ brokers: vlab045701.dom045700.lab:9092
+ autoCreateTopics: false
+ bindings:
+ consumeTransaction-in-0:
+ consumer:
+ max-attempts: 3
+ back-off-initial-interval: 100
+ destination: transactions
+ group: account-management
+ concurrency: 1
+ transaction-out-0:
+ destination: transactions
\ No newline at end of file
diff --git a/spring-reactive-architecture/account-management-service/src/test/java/io/reflectoring/account_management_service/AccountManagementServiceApplicationTests.java b/spring-reactive-architecture/account-management-service/src/test/java/io/reflectoring/account_management_service/AccountManagementServiceApplicationTests.java
new file mode 100644
index 0000000..843e670
--- /dev/null
+++ b/spring-reactive-architecture/account-management-service/src/test/java/io/reflectoring/account_management_service/AccountManagementServiceApplicationTests.java
@@ -0,0 +1,13 @@
+package io.reflectoring.account_management_service;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class AccountManagementServiceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/spring-reactive-architecture/banking-service/.gitignore b/spring-reactive-architecture/banking-service/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-reactive-architecture/banking-service/.mvn/wrapper/maven-wrapper.jar b/spring-reactive-architecture/banking-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..c1dd12f
Binary files /dev/null and b/spring-reactive-architecture/banking-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-reactive-architecture/banking-service/.mvn/wrapper/maven-wrapper.properties b/spring-reactive-architecture/banking-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..b7cb93e
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/spring-reactive-architecture/banking-service/Dockerfile b/spring-reactive-architecture/banking-service/Dockerfile
new file mode 100644
index 0000000..fc4823c
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/Dockerfile
@@ -0,0 +1,3 @@
+FROM openjdk:8-jdk-alpine
+COPY target/banking-service-0.0.1-SNAPSHOT.jar app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/spring-reactive-architecture/banking-service/mvnw b/spring-reactive-architecture/banking-service/mvnw
new file mode 100644
index 0000000..8a8fb22
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-reactive-architecture/banking-service/mvnw.cmd b/spring-reactive-architecture/banking-service/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/spring-reactive-architecture/banking-service/pom.xml b/spring-reactive-architecture/banking-service/pom.xml
new file mode 100644
index 0000000..feaaf7c
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/pom.xml
@@ -0,0 +1,91 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.3
+
+
+ io.reflectoring
+ banking-service
+ 0.0.1-SNAPSHOT
+ banking-service
+ Spring Boot microservice for Banking Transaction Processing Service
+
+
+ 1.8
+ 2020.0.3
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-stream-kafka
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.1.0
+
+
+
+
+
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/BankingServiceApplication.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/BankingServiceApplication.java
new file mode 100644
index 0000000..0bfed2b
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/BankingServiceApplication.java
@@ -0,0 +1,15 @@
+package io.reflectoring.banking_service;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+
+@SpringBootApplication
+@EnableMongoRepositories
+public class BankingServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BankingServiceApplication.class, args);
+ }
+
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/config/ApplicationConfiguration.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/config/ApplicationConfiguration.java
new file mode 100644
index 0000000..1aac72d
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/config/ApplicationConfiguration.java
@@ -0,0 +1,46 @@
+package io.reflectoring.banking_service.config;
+
+import io.netty.channel.ChannelOption;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.netty.handler.timeout.ReadTimeoutHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.web.reactive.function.client.ExchangeStrategies;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.transport.logging.AdvancedByteBufFormat;
+
+import javax.net.ssl.SSLException;
+import java.util.concurrent.TimeUnit;
+
+@Configuration
+public class ApplicationConfiguration {
+
+ @Bean
+ public WebClient getWebClientBuilder() throws SSLException {
+
+ SslContext sslContext = SslContextBuilder
+ .forClient()
+ .trustManager(InsecureTrustManagerFactory.INSTANCE)
+ .build();
+
+ HttpClient httpClient = HttpClient.create()
+ .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 120 * 1000)
+ .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(120 * 1000, TimeUnit.MILLISECONDS)))
+ .wiretap("reactor.netty.http.client.HttpClient", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL)
+ .followRedirect(true)
+ .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
+
+ return WebClient.builder()
+ .clientConnector(new ReactorClientHttpConnector(httpClient))
+ .exchangeStrategies(ExchangeStrategies.builder()
+ .codecs(configurer -> configurer.defaultCodecs()
+ .maxInMemorySize(50 * 1024 * 1024))
+ .build())
+ .build();
+ }
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/constant/TransactionStatus.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/constant/TransactionStatus.java
new file mode 100644
index 0000000..9ec7a79
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/constant/TransactionStatus.java
@@ -0,0 +1,15 @@
+package io.reflectoring.banking_service.constant;
+
+public enum TransactionStatus {
+ INITIATED,
+ SUCCESS,
+ FAILURE,
+ CANCELLED,
+ VALID,
+ ACCOUNT_BLOCKED,
+ CARD_INVALID,
+ FUNDS_UNAVAILABLE,
+ FRAUDULENT,
+ FRAUDULENT_NOTIFY_SUCCESS,
+ FRAUDULENT_NOTIFY_FAILURE
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/controller/TransactionController.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/controller/TransactionController.java
new file mode 100644
index 0000000..9b5d796
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/controller/TransactionController.java
@@ -0,0 +1,27 @@
+package io.reflectoring.banking_service.controller;
+
+import io.reflectoring.banking_service.model.Transaction;
+import io.reflectoring.banking_service.service.TransactionService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@RestController
+@RequestMapping("/banking")
+public class TransactionController {
+
+ @Autowired
+ private TransactionService transactionService;
+
+ @PostMapping(value = "/process", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+ public Mono process(@RequestBody Transaction transaction) {
+ log.info("Process transaction with details: {}", transaction);
+ return transactionService.process(transaction);
+ }
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/init/AppInit.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/init/AppInit.java
new file mode 100644
index 0000000..13e9680
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/init/AppInit.java
@@ -0,0 +1,61 @@
+package io.reflectoring.banking_service.init;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.reflectoring.banking_service.kafka.producer.TransactionProducer;
+import io.reflectoring.banking_service.model.Transaction;
+import io.reflectoring.banking_service.model.User;
+import io.reflectoring.banking_service.repository.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+@Component
+public class AppInit implements ApplicationListener {
+
+ @Autowired
+ private UserRepository userRepo;
+
+ @Autowired
+ TransactionProducer producer;
+
+ @Override
+ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
+ log.info("Application started");
+ ObjectMapper mapper = new ObjectMapper();
+ TypeReference> typeReferenceUser = new TypeReference>(){};
+ InputStream inputStreamUser = TypeReference.class.getResourceAsStream("/json/users.json");
+ try {
+ List usersList = mapper.readValue(inputStreamUser,typeReferenceUser);
+ usersList.forEach(u -> {
+ User user = userRepo.findByCardId(u.getCardId())
+ .share()
+ .block();
+ if (Objects.isNull(user)) {
+ userRepo.save(u).subscribe();
+ }
+ });
+ log.info("User Saved!");
+ } catch (IOException e){
+ log.error("Unable to save User: " + e.getMessage());
+ }
+
+ TypeReference> typeReferenceTransaction = new TypeReference>(){};
+ InputStream inputStreamTransaction = TypeReference.class.getResourceAsStream("/json/transactions.json");
+ try {
+ List transactionsList = mapper.readValue(inputStreamTransaction, typeReferenceTransaction);
+ transactionsList.forEach(t -> producer.sendMessage(t));
+ log.info("Transactions Dispatched to Kafka topic!");
+ } catch (IOException e){
+ log.error("Unable to dispatch transactions to Kafka Topic: " + e.getMessage());
+ }
+ }
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/kafka/consumer/TransactionConsumer.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/kafka/consumer/TransactionConsumer.java
new file mode 100644
index 0000000..0d0899b
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/kafka/consumer/TransactionConsumer.java
@@ -0,0 +1,18 @@
+package io.reflectoring.banking_service.kafka.consumer;
+
+import io.reflectoring.banking_service.model.Transaction;
+import io.reflectoring.banking_service.service.TransactionService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import java.util.function.Consumer;
+
+@Slf4j
+@Configuration
+public class TransactionConsumer {
+
+ @Bean
+ public Consumer consumeTransaction(TransactionService transactionService) {
+ return transactionService::asyncProcess;
+ }
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/kafka/producer/TransactionProducer.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/kafka/producer/TransactionProducer.java
new file mode 100644
index 0000000..47c2d1f
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/kafka/producer/TransactionProducer.java
@@ -0,0 +1,29 @@
+package io.reflectoring.banking_service.kafka.producer;
+
+import io.reflectoring.banking_service.model.Transaction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+@Service
+public class TransactionProducer {
+
+ @Autowired
+ private StreamBridge streamBridge;
+
+ public void sendMessage(Transaction transaction) {
+ Message msg = MessageBuilder.withPayload(transaction)
+ .setHeader(KafkaHeaders.MESSAGE_KEY, transaction.getTransactionId().getBytes(StandardCharsets.UTF_8))
+ .build();
+ log.info("Transaction processed to dispatch: {}; Message dispatch successful: {}",
+ msg,
+ streamBridge.send("transaction-out-0", msg));
+ }
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/model/Transaction.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/model/Transaction.java
new file mode 100644
index 0000000..c1758ce
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/model/Transaction.java
@@ -0,0 +1,37 @@
+package io.reflectoring.banking_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.reflectoring.banking_service.constant.TransactionStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class Transaction {
+
+ @Id
+ @JsonProperty("transaction_id")
+ private String transactionId;
+ private String date;
+
+ @JsonProperty("amount_deducted")
+ private double amountDeducted;
+
+ @JsonProperty("store_name")
+ private String storeName;
+
+ @JsonProperty("store_id")
+ private String storeId;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("transaction_location")
+ private String transactionLocation;
+ private TransactionStatus status;
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/model/User.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/model/User.java
new file mode 100644
index 0000000..691d152
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/model/User.java
@@ -0,0 +1,54 @@
+package io.reflectoring.banking_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.List;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class User {
+
+ @Id
+ private String id;
+
+ @JsonProperty("first_name")
+ private String firstName;
+
+ @JsonProperty("last_name")
+ private String lastName;
+ private String email;
+ private String address;
+
+ @JsonProperty("home_country")
+ private String homeCountry;
+ private String gender;
+ private String mobile;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("account_number")
+ private String accountNumber;
+
+ @JsonProperty("account_type")
+ private String accountType;
+
+ @JsonProperty("account_locked")
+ private boolean accountLocked;
+
+ @JsonProperty("fraudulent_activity_attempt_count")
+ private Long fraudulentActivityAttemptCount;
+
+ @JsonProperty("valid_transactions")
+ private List validTransactions;
+
+ @JsonProperty("fraudulent_transactions")
+ private List fraudulentTransactions;
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/repository/TransactionRepository.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/repository/TransactionRepository.java
new file mode 100644
index 0000000..3ec2093
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/repository/TransactionRepository.java
@@ -0,0 +1,9 @@
+package io.reflectoring.banking_service.repository;
+
+import io.reflectoring.banking_service.model.Transaction;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface TransactionRepository extends ReactiveMongoRepository {
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/repository/UserRepository.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/repository/UserRepository.java
new file mode 100644
index 0000000..a97215c
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/repository/UserRepository.java
@@ -0,0 +1,12 @@
+package io.reflectoring.banking_service.repository;
+
+import io.reflectoring.banking_service.model.User;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Mono;
+
+@Repository
+public interface UserRepository extends ReactiveMongoRepository {
+
+ Mono findByCardId(String cardId);
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/service/TransactionService.java b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/service/TransactionService.java
new file mode 100644
index 0000000..bc9a0af
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/java/io/reflectoring/banking_service/service/TransactionService.java
@@ -0,0 +1,159 @@
+package io.reflectoring.banking_service.service;
+
+import io.reflectoring.banking_service.constant.TransactionStatus;
+import io.reflectoring.banking_service.kafka.producer.TransactionProducer;
+import io.reflectoring.banking_service.model.Transaction;
+import io.reflectoring.banking_service.repository.TransactionRepository;
+import io.reflectoring.banking_service.repository.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.util.Objects;
+
+@Slf4j
+@Service
+public class TransactionService {
+ private static final String USER_NOTIFICATION_SERVICE_URL = "http://localhost:8081/notify/fraudulent-transaction";
+ private static final String REPORTING_SERVICE_URL = "http://localhost:8082/report/";
+ private static final String ACCOUNT_MANAGER_SERVICE_URL = "http://localhost:8083/banking/process";
+
+ @Autowired
+ private TransactionRepository transactionRepo;
+
+ @Autowired
+ private UserRepository userRepo;
+
+ @Autowired
+ private WebClient webClient;
+
+ @Autowired
+ TransactionProducer producer;
+
+ @Transactional
+ public Mono process(Transaction transaction) {
+
+ return Mono.just(transaction)
+ .flatMap(transactionRepo::save)
+ .flatMap(t -> userRepo.findByCardId(t.getCardId())
+ .map(u -> {
+ log.info("User details: {}", u);
+ if (t.getStatus().equals(TransactionStatus.INITIATED)) {
+ // Check whether the card details are valid or not
+ if (Objects.isNull(u)) {
+ t.setStatus(TransactionStatus.CARD_INVALID);
+ }
+
+ // Check whether the account is blocked or not
+ else if (u.isAccountLocked()) {
+ t.setStatus(TransactionStatus.ACCOUNT_BLOCKED);
+ }
+
+ else {
+ // Check if it's a valid transaction or not. The Transaction would be considered valid
+ // if it has been requested from the same home country of the user, else will be considered
+ // as fraudulent
+ if (u.getHomeCountry().equalsIgnoreCase(t.getTransactionLocation())) {
+ t.setStatus(TransactionStatus.VALID);
+
+ // Call Reporting Service to report valid transaction to bank and deduct amount if funds available
+ return webClient.post()
+ .uri(REPORTING_SERVICE_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(BodyInserters.fromValue(t))
+ .retrieve()
+ .bodyToMono(Transaction.class)
+ .zipWhen(t1 ->
+ // Call Account Manager service to process the transaction and send the money
+ webClient.post()
+ .uri(ACCOUNT_MANAGER_SERVICE_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(BodyInserters.fromValue(t))
+ .retrieve()
+ .bodyToMono(Transaction.class)
+ .log(),
+ (t1, t2) -> t2
+ )
+ .log()
+ .share()
+ .block();
+ } else {
+ t.setStatus(TransactionStatus.FRAUDULENT);
+
+ // Call User Notification service to notify for a fraudulent transaction
+ // attempt from the User's card
+ return webClient.post()
+ .uri(USER_NOTIFICATION_SERVICE_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(BodyInserters.fromValue(t))
+ .retrieve()
+ .bodyToMono(Transaction.class)
+ .zipWhen(t1 ->
+ // Call Reporting Service to notify bank that there has been an attempt for fraudulent transaction
+ // and if this attempt exceeds 3 times then auto-block the card and account
+ webClient.post()
+ .uri(REPORTING_SERVICE_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .body(BodyInserters.fromValue(t))
+ .retrieve()
+ .bodyToMono(Transaction.class)
+ .log(),
+ (t1, t2) -> t2
+ )
+ .log()
+ .share()
+ .block();
+ }
+ }
+ } else {
+ // For any other case, the transaction will be considered failure
+ t.setStatus(TransactionStatus.FAILURE);
+ }
+ return t;
+ }));
+ }
+
+ public void asyncProcess(Transaction transaction) {
+ userRepo.findByCardId(transaction.getCardId())
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.INITIATED)) {
+ log.info("Consumed message for processing: {}", transaction);
+ log.info("User details: {}", u);
+ // Check whether the card details are valid or not
+ if (Objects.isNull(u)) {
+ transaction.setStatus(TransactionStatus.CARD_INVALID);
+ }
+
+ // Check whether the account is blocked or not
+ else if (u.isAccountLocked()) {
+ transaction.setStatus(TransactionStatus.ACCOUNT_BLOCKED);
+ }
+
+ else {
+ // Check if it's a valid transaction or not. The Transaction would be considered valid
+ // if it has been requested from the same home country of the user, else will be considered
+ // as fraudulent
+ if (u.getHomeCountry().equalsIgnoreCase(transaction.getTransactionLocation())) {
+ transaction.setStatus(TransactionStatus.VALID);
+ } else {
+ transaction.setStatus(TransactionStatus.FRAUDULENT);
+ }
+ }
+ producer.sendMessage(transaction);
+ }
+ return transaction;
+ })
+ .filter(t -> t.getStatus().equals(TransactionStatus.VALID)
+ || t.getStatus().equals(TransactionStatus.FRAUDULENT)
+ || t.getStatus().equals(TransactionStatus.CARD_INVALID)
+ || t.getStatus().equals(TransactionStatus.ACCOUNT_BLOCKED)
+ )
+ .flatMap(transactionRepo::save)
+ .subscribe();
+ }
+}
diff --git a/spring-reactive-architecture/banking-service/src/main/resources/application.yml b/spring-reactive-architecture/banking-service/src/main/resources/application.yml
new file mode 100644
index 0000000..5b52473
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/resources/application.yml
@@ -0,0 +1,38 @@
+server:
+ port: 8080
+
+# Configure Spring specific properties
+spring:
+
+ # Enable/Disable hot swapping
+ devtools:
+ restart:
+ enabled: true
+ log-condition-evaluation-delta: false
+
+ # Datasource Configurations
+ data:
+ mongodb:
+ authentication-database: admin
+ uri: mongodb://vlab045701.dom045700.lab:27017/reactive
+ database: reactive
+
+ # Kafka Configuration
+ cloud:
+ function:
+ definition: consumeTransaction
+ stream:
+ kafka:
+ binder:
+ brokers: vlab045701.dom045700.lab:9092
+ autoCreateTopics: false
+ bindings:
+ consumeTransaction-in-0:
+ consumer:
+ max-attempts: 3
+ back-off-initial-interval: 100
+ destination: transactions
+ group: banking
+ concurrency: 1
+ transaction-out-0:
+ destination: transactions
\ No newline at end of file
diff --git a/spring-reactive-architecture/banking-service/src/main/resources/json/transactions.json b/spring-reactive-architecture/banking-service/src/main/resources/json/transactions.json
new file mode 100644
index 0000000..75e0395
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/resources/json/transactions.json
@@ -0,0 +1,1002 @@
+[
+ {
+ "transaction_id": "23911954-cb41-4f43-92b5-ba83dec41f89",
+ "date": "2021-08-27T11:37:12 -06:-30",
+ "amount_deducted": 3473.64,
+ "store_name": "Syntac",
+ "store_id": "21f6dd55-639d-4f67-8287-e25adf3a3d61",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "233dd233-d7ad-4d17-ab9f-0e7a12b13bce",
+ "date": "2021-08-07T12:25:44 -06:-30",
+ "amount_deducted": 3448.19,
+ "store_name": "Comtrak",
+ "store_id": "5536cf90-a3d0-469d-a0e8-958338ccda96",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "1a4476dc-6f86-4b05-b3eb-00a1b6f9b547",
+ "date": "2022-02-05T03:40:46 -06:-30",
+ "amount_deducted": 1733.24,
+ "store_name": "Musix",
+ "store_id": "405791da-247f-4a44-97aa-283947d6fc46",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "055dab84-7bf3-4546-84aa-23f8774eeac2",
+ "date": "2021-08-30T12:31:16 -06:-30",
+ "amount_deducted": 1589.46,
+ "store_name": "Exoplode",
+ "store_id": "d67fb937-2e80-4001-9855-1f8699244aa8",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "44013a15-3e80-444f-954e-51336e0fce89",
+ "date": "2021-09-26T02:43:23 -06:-30",
+ "amount_deducted": 3046.56,
+ "store_name": "Comtrail",
+ "store_id": "17e65ba8-050d-412e-b513-a2f71f602a4e",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "e164aa2d-73e7-4e23-99c7-61bc319a58e7",
+ "date": "2021-11-24T09:23:09 -06:-30",
+ "amount_deducted": 2703.47,
+ "store_name": "Dadabase",
+ "store_id": "e1797cc4-12c2-4d35-9488-6f2481a53cc0",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "3c757dd6-7d2f-4cdb-8286-ea28a94df457",
+ "date": "2021-01-04T04:20:09 -06:-30",
+ "amount_deducted": 1608.72,
+ "store_name": "Empirica",
+ "store_id": "b5308c81-1302-49f6-8475-d428e2139104",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "448214e3-dcf7-49b8-8a7e-71ea347cdd80",
+ "date": "2022-01-29T07:04:02 -06:-30",
+ "amount_deducted": 1815.75,
+ "store_name": "Zappix",
+ "store_id": "140403df-5cbf-4da6-8ecd-35888556d6f1",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "98cacc30-0989-4971-a728-4ab5278f3f33",
+ "date": "2021-01-31T04:44:42 -06:-30",
+ "amount_deducted": 1776.55,
+ "store_name": "Mediot",
+ "store_id": "713e71e5-026d-4882-b8af-94d418212141",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "86a87dd0-954d-46bd-9684-3eebc54d4373",
+ "date": "2021-11-30T12:34:00 -06:-30",
+ "amount_deducted": 3993.13,
+ "store_name": "Geeknet",
+ "store_id": "1f371f2f-ac64-46ad-90c8-dc816af80a2e",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "d7fc3cf2-7d64-4e34-991b-8d983d37f6ec",
+ "date": "2021-05-06T07:35:54 -06:-30",
+ "amount_deducted": 3511.53,
+ "store_name": "Virxo",
+ "store_id": "fab07d7a-d2c7-4d0d-aa9e-27ed5564d4d1",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "b84253e8-188f-43b2-9ef2-25d1d81a82ca",
+ "date": "2021-04-28T05:06:09 -06:-30",
+ "amount_deducted": 2734.32,
+ "store_name": "Martgo",
+ "store_id": "15d25bd5-280a-4bad-9a50-023c1c6178be",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "12d79188-5ecc-429b-9a49-788fb164cf7f",
+ "date": "2021-04-05T12:12:57 -06:-30",
+ "amount_deducted": 3802.13,
+ "store_name": "Plasto",
+ "store_id": "e1b49d70-6bee-4a09-a85e-858276d5b0c9",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "98d782a5-d02d-4b31-8698-bc4a3e3acf25",
+ "date": "2021-10-10T01:55:44 -06:-30",
+ "amount_deducted": 1507.64,
+ "store_name": "Confrenzy",
+ "store_id": "f9f66344-426e-4625-b26b-5a369f57522b",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "689ca911-2360-441e-b9ab-7ed70c8f0883",
+ "date": "2021-06-30T12:50:55 -06:-30",
+ "amount_deducted": 1091.51,
+ "store_name": "Zounds",
+ "store_id": "d5770fda-003b-4c43-9963-7bfa6e54cfac",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "67bfb6aa-c3f4-4676-95dc-20e888a0c558",
+ "date": "2022-01-18T09:42:43 -06:-30",
+ "amount_deducted": 1034.00,
+ "store_name": "Magnafone",
+ "store_id": "260a9c10-81af-4401-b7e4-ff1121ac1bd1",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Bosnia and Herzegovina",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "45ff7278-f0ee-41f6-b469-79ef560f461b",
+ "date": "2021-04-15T01:08:09 -06:-30",
+ "amount_deducted": 1575.62,
+ "store_name": "Capscreen",
+ "store_id": "51145103-57e2-4b89-82e2-bfd5fb031f8f",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "f8f7a61b-607d-423d-8921-e8a866bc7dfa",
+ "date": "2021-01-22T02:38:10 -06:-30",
+ "amount_deducted": 1463.67,
+ "store_name": "Frenex",
+ "store_id": "98e1c144-9ee3-47a7-97b7-6920d6c2fea3",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "aec77702-93cb-42b7-bf8b-0e056c87bd56",
+ "date": "2021-12-04T02:56:53 -06:-30",
+ "amount_deducted": 1278.58,
+ "store_name": "Ovium",
+ "store_id": "6cdfc99d-96f6-4975-b2f7-79db801dd785",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "cb67ec0c-82e6-4b67-abfd-3f8815940c61",
+ "date": "2021-08-02T11:40:11 -06:-30",
+ "amount_deducted": 2917.62,
+ "store_name": "Buzzworks",
+ "store_id": "5a65a17f-755d-41a0-8e27-bc79f108bff0",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "562c72b5-8779-44bf-85e5-06d2b440435a",
+ "date": "2021-12-16T09:06:32 -06:-30",
+ "amount_deducted": 1279.82,
+ "store_name": "Kneedles",
+ "store_id": "4f775032-254d-466a-a3bb-7d203dc60681",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "4f65b252-f12f-4f33-a843-2b8ee388676a",
+ "date": "2021-07-26T07:17:32 -06:-30",
+ "amount_deducted": 1279.24,
+ "store_name": "Utarian",
+ "store_id": "6d3c600a-9d9c-4a07-82b3-35c9fef2f759",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "30f446bb-cffc-4d4c-9f1d-f2c26a67d100",
+ "date": "2021-02-25T12:54:33 -06:-30",
+ "amount_deducted": 3161.56,
+ "store_name": "Dreamia",
+ "store_id": "3a273010-a7d1-4817-a478-f44029520b7b",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "ba6f21be-0b12-4d52-a464-460c0785861a",
+ "date": "2021-09-25T11:43:37 -06:-30",
+ "amount_deducted": 3052.41,
+ "store_name": "Springbee",
+ "store_id": "310232a8-6885-4651-afa9-4418a3044abb",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "464d921b-de85-4ff9-89b8-b0d8b9a2da3f",
+ "date": "2021-08-08T11:54:01 -06:-30",
+ "amount_deducted": 1058.21,
+ "store_name": "Enomen",
+ "store_id": "36d3ef46-a110-4685-9fd2-f2cd9670c543",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "38f4d788-c3ce-4201-9e93-d4607c28c760",
+ "date": "2021-10-18T06:12:21 -06:-30",
+ "amount_deducted": 1000.68,
+ "store_name": "Extrawear",
+ "store_id": "8fd7a151-2399-44bb-84e0-3d43562d8fb9",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "6e8056d3-f820-44fb-9cd4-9fb080062a5b",
+ "date": "2021-07-24T10:37:47 -06:-30",
+ "amount_deducted": 3650.74,
+ "store_name": "Circum",
+ "store_id": "298ef5ac-a3ba-44d0-ba39-84dc2feba05b",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "525c8e2a-cc90-4e1e-bb36-60a0b79d6b2d",
+ "date": "2021-10-18T02:08:11 -06:-30",
+ "amount_deducted": 2282.94,
+ "store_name": "Elemantra",
+ "store_id": "82c288fd-1eef-4b78-974d-f56aacaf7a43",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "652311bc-1c30-4723-b520-efb298622e43",
+ "date": "2021-10-26T10:37:05 -06:-30",
+ "amount_deducted": 3494.54,
+ "store_name": "Zerology",
+ "store_id": "1e2e1c97-feef-4d7e-aa33-b77dae3c0e01",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "c6d78ca9-0cff-416f-8532-4cb2ad64e5a4",
+ "date": "2021-04-12T11:08:42 -06:-30",
+ "amount_deducted": 2582.88,
+ "store_name": "Teraprene",
+ "store_id": "77afb52e-33c0-4c0e-9ef9-90f0a8186026",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "0fe46854-c679-4fd6-b608-8aa4cea376a4",
+ "date": "2021-08-20T08:01:50 -06:-30",
+ "amount_deducted": 3775.79,
+ "store_name": "Turnabout",
+ "store_id": "85faf86a-5594-4a15-8f92-9e715796e3f5",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "916e9dcd-47cc-4fd6-bfaa-e5da91bfdbf2",
+ "date": "2021-03-01T06:32:04 -06:-30",
+ "amount_deducted": 2072.08,
+ "store_name": "Prosely",
+ "store_id": "f3ee99b5-2818-4d39-9772-cd552006f6ae",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Bosnia and Herzegovina",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "52cbafff-7e5c-4776-afa4-8ad48d74d7d0",
+ "date": "2021-05-18T11:38:19 -06:-30",
+ "amount_deducted": 3307.58,
+ "store_name": "Buzzworks",
+ "store_id": "64706b98-ebf7-4e24-8fd7-d8dd2eaed17f",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "eb16cce3-b384-4193-bd2a-7752a920caf7",
+ "date": "2021-11-26T06:37:06 -06:-30",
+ "amount_deducted": 2125.14,
+ "store_name": "Uni",
+ "store_id": "c1b0d214-ff2a-4179-8153-6f49980114de",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "0511393d-08dd-40b0-a21b-0ef16f127ab3",
+ "date": "2021-11-23T10:59:06 -06:-30",
+ "amount_deducted": 1131.33,
+ "store_name": "Netbook",
+ "store_id": "b1660293-97b0-44f8-9fa9-8b12e4ce0366",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "029bc3f6-be3b-4b59-a2ec-e0cdf39e4b6c",
+ "date": "2021-04-04T02:18:31 -06:-30",
+ "amount_deducted": 2128.53,
+ "store_name": "Pulze",
+ "store_id": "1ffd1092-fd3b-43dd-a2e1-3aef858608e0",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "9e5c8383-075d-4493-b2d1-f8cff4583924",
+ "date": "2021-03-10T01:30:44 -06:-30",
+ "amount_deducted": 1834.08,
+ "store_name": "Darwinium",
+ "store_id": "b5d7fdde-16bf-4612-8a42-705bf9f50066",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "a4121a21-97d4-482f-8554-9091ae22780c",
+ "date": "2021-05-24T11:08:15 -06:-30",
+ "amount_deducted": 3901.95,
+ "store_name": "Ziore",
+ "store_id": "ec72a324-27f3-45a6-97bb-712c1dfd2dc7",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "748ca42a-466f-4861-8cf2-0613613ba2dd",
+ "date": "2021-05-08T06:06:31 -06:-30",
+ "amount_deducted": 3654.80,
+ "store_name": "Pigzart",
+ "store_id": "1e19e64b-c58e-4d53-b0ce-25d2574097a4",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "6c407440-ccbc-4f66-8bff-717d37723289",
+ "date": "2021-02-08T04:49:39 -06:-30",
+ "amount_deducted": 2389.03,
+ "store_name": "Hatology",
+ "store_id": "8957eb48-0e90-4ddf-a281-503aa56e5497",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "29f6f499-a134-42c3-a786-aeeaa74388a8",
+ "date": "2021-03-31T09:14:33 -06:-30",
+ "amount_deducted": 2051.87,
+ "store_name": "Geeketron",
+ "store_id": "a24412bb-cba6-4145-ae79-9b6f8fd91efa",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "85930e06-8deb-4a7f-bccc-6949e1cc51f9",
+ "date": "2022-01-05T11:50:55 -06:-30",
+ "amount_deducted": 2884.13,
+ "store_name": "Exoswitch",
+ "store_id": "f25a8f52-e4a4-40c2-9e7f-e31949497b23",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "97ca0b37-ffcd-422f-b3fa-8f17c471a4e3",
+ "date": "2021-05-17T09:44:26 -06:-30",
+ "amount_deducted": 2001.12,
+ "store_name": "Ovation",
+ "store_id": "4f53aa21-bcc0-4d34-8a50-59f56ea7c828",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "8d9cf267-9b7d-4762-8bcd-35cbce13de67",
+ "date": "2021-11-19T04:47:59 -06:-30",
+ "amount_deducted": 3729.09,
+ "store_name": "Nexgene",
+ "store_id": "e7f0d56c-bfc4-4962-975d-33df1fa647ce",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "ba968071-c533-479a-9629-a515518626cd",
+ "date": "2021-08-27T11:17:43 -06:-30",
+ "amount_deducted": 3852.19,
+ "store_name": "Mobildata",
+ "store_id": "17728d73-9d5b-4379-a8f1-c8cb43325d4b",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "26fdcba7-53f9-4af8-8f13-e63aa362d6b9",
+ "date": "2021-11-22T11:34:32 -06:-30",
+ "amount_deducted": 2998.27,
+ "store_name": "Aquafire",
+ "store_id": "601eb6a4-7499-40bd-a066-176aa3f2041a",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "6a859280-6b7f-4231-9502-f13cba904d25",
+ "date": "2021-09-08T06:40:13 -06:-30",
+ "amount_deducted": 1157.79,
+ "store_name": "Scentric",
+ "store_id": "42dbe206-37d2-47a7-b429-0bc9cd39d068",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "cf6e6082-da77-47ab-b034-11be1fad520c",
+ "date": "2021-04-26T03:05:11 -06:-30",
+ "amount_deducted": 1252.48,
+ "store_name": "Quantalia",
+ "store_id": "ec899267-d4b2-4e35-b47a-2143f9052045",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "de96c45f-6ac7-4c24-a958-f4fd22ef719f",
+ "date": "2021-10-18T10:34:03 -06:-30",
+ "amount_deducted": 1454.15,
+ "store_name": "Netility",
+ "store_id": "0887285a-5ae6-4ad9-81bb-5b6d7dd34fa0",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "fffe25be-a915-4d51-9e8d-df85e07cbc3f",
+ "date": "2021-01-29T08:21:04 -06:-30",
+ "amount_deducted": 1996.80,
+ "store_name": "Exposa",
+ "store_id": "96bc9ed1-a557-4b7b-843f-8d97f67ddab0",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "89c6fa63-fce0-4438-81d5-82bf356dbef3",
+ "date": "2021-09-13T01:56:02 -06:-30",
+ "amount_deducted": 2821.22,
+ "store_name": "Magnina",
+ "store_id": "340011ea-a184-4b7a-98ad-19ccb3860d7a",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "11d53624-672e-41e7-a0a7-30ebded71920",
+ "date": "2021-01-22T11:52:08 -06:-30",
+ "amount_deducted": 3526.54,
+ "store_name": "Darwinium",
+ "store_id": "8b2fa20c-eca8-40dc-95f2-627efc0c91b2",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "85011396-27ea-439a-ae3a-d5d04354414b",
+ "date": "2021-07-02T07:57:06 -06:-30",
+ "amount_deducted": 1991.96,
+ "store_name": "Flumbo",
+ "store_id": "5444cd40-4478-4b8d-9416-04a127d50f0b",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "323adb80-7200-4062-b4c4-99f4bb79e674",
+ "date": "2021-02-01T03:53:41 -06:-30",
+ "amount_deducted": 3983.38,
+ "store_name": "Genmy",
+ "store_id": "3db763aa-6b1b-4e1c-94aa-5367489f94fd",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "915a5b90-e86f-45d2-920e-66d1b703b39e",
+ "date": "2021-07-04T08:26:41 -06:-30",
+ "amount_deducted": 3982.26,
+ "store_name": "Extragene",
+ "store_id": "f8f00b3f-2052-4f4f-bd40-d0341bf229ae",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "95252380-bed7-4589-bcf3-cba3cf66d708",
+ "date": "2021-07-07T09:21:17 -06:-30",
+ "amount_deducted": 1051.37,
+ "store_name": "Artworlds",
+ "store_id": "4a6f0c13-a424-4498-9d85-0e24317d24db",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "fcb45e46-92ea-4ff3-884d-212df09beaed",
+ "date": "2021-10-06T09:12:14 -06:-30",
+ "amount_deducted": 1484.26,
+ "store_name": "Plutorque",
+ "store_id": "47337e07-3771-4af6-9527-15e194d8602a",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "6a01236a-63a8-46a6-82ca-7d7fa2ea28d8",
+ "date": "2021-06-06T01:43:13 -06:-30",
+ "amount_deducted": 1326.30,
+ "store_name": "Bittor",
+ "store_id": "e3e8f683-359b-4462-ac84-7a725177d759",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Bosnia and Herzegovina",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "cd6b169d-c60d-4c68-b63d-c581998b9d7c",
+ "date": "2022-01-31T04:05:30 -06:-30",
+ "amount_deducted": 1581.00,
+ "store_name": "Synkgen",
+ "store_id": "2ad72627-3b4d-4fe6-a627-831d7f058cf2",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "0895a7e9-2e67-4de8-b878-9a6470924546",
+ "date": "2021-02-11T09:29:59 -06:-30",
+ "amount_deducted": 2689.52,
+ "store_name": "Insource",
+ "store_id": "20a937df-4e02-4127-a89b-ad56bbee6432",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "aff20bcf-a497-40de-a0c6-df6b3785d3f8",
+ "date": "2021-05-07T10:38:56 -06:-30",
+ "amount_deducted": 1087.78,
+ "store_name": "Eargo",
+ "store_id": "3e041be8-9aec-4401-9bb7-0c7469389092",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "65d55f5c-7863-4cf5-a87f-e1b1ebb04c2b",
+ "date": "2021-07-13T10:21:40 -06:-30",
+ "amount_deducted": 2931.67,
+ "store_name": "Endipine",
+ "store_id": "949c6eb1-7495-4fb2-950b-94569514c750",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "e4de2608-4360-4f38-a1c0-1a196adfa700",
+ "date": "2021-08-01T06:47:18 -06:-30",
+ "amount_deducted": 2315.28,
+ "store_name": "Rodeocean",
+ "store_id": "78283d3c-7743-4112-8a55-3b55d337ecc2",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "7f8abd61-2384-4287-ad3e-32265e725548",
+ "date": "2021-10-13T07:04:27 -06:-30",
+ "amount_deducted": 2809.68,
+ "store_name": "Aquoavo",
+ "store_id": "43844cb2-856e-4729-889e-86c7d18b8a0e",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "356b0e47-92b1-46d2-b03d-f1924921b88f",
+ "date": "2021-05-10T02:34:05 -06:-30",
+ "amount_deducted": 2403.64,
+ "store_name": "Extro",
+ "store_id": "be98fc03-836f-42c9-a791-ee43bd38e94b",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Botswana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "bb859918-b417-400f-8be5-36f35ce999ce",
+ "date": "2021-04-16T11:26:03 -06:-30",
+ "amount_deducted": 1135.80,
+ "store_name": "Medifax",
+ "store_id": "860d4ed1-3147-440d-8305-8c27804b9566",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Bosnia and Herzegovina",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "97c1946c-2864-4ed4-b632-3b4480b57c82",
+ "date": "2021-07-15T09:48:16 -06:-30",
+ "amount_deducted": 1459.03,
+ "store_name": "Rockyard",
+ "store_id": "05c2b9dc-aab8-49f9-8a51-490bb9892abd",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "c66e6cb4-763e-46ee-a7b0-b9f28ea1cac1",
+ "date": "2021-12-17T08:46:57 -06:-30",
+ "amount_deducted": 3686.22,
+ "store_name": "Confrenzy",
+ "store_id": "a21743c3-f074-44c5-aad0-8507992ff2e9",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "8346c56f-b50d-42c1-a56f-20a71f875d64",
+ "date": "2021-08-19T08:36:15 -06:-30",
+ "amount_deducted": 3038.12,
+ "store_name": "Centree",
+ "store_id": "3a58dbf5-2b50-466b-a26f-d9e0cd1a787b",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "92d08be5-db79-483d-a313-6f9315a5f2e0",
+ "date": "2021-09-16T08:10:35 -06:-30",
+ "amount_deducted": 1935.66,
+ "store_name": "Terragen",
+ "store_id": "1a1f8804-c726-440b-9c9e-bfb69f56fccf",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "794a6aca-709e-48b6-accd-8f9ead9875c1",
+ "date": "2021-10-13T03:27:25 -06:-30",
+ "amount_deducted": 2681.00,
+ "store_name": "Rocklogic",
+ "store_id": "7cd756e3-8b41-464b-ab88-cfec169544a1",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "b8fec601-fb01-4505-a3ac-04d35f3c3adb",
+ "date": "2021-05-19T09:58:37 -06:-30",
+ "amount_deducted": 2487.47,
+ "store_name": "Trasola",
+ "store_id": "3928f6f5-4715-4f3f-8fed-99baa8f1e09c",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "d490f8e3-5466-4d80-ad4c-8d12346a1266",
+ "date": "2021-01-21T09:56:19 -06:-30",
+ "amount_deducted": 3227.79,
+ "store_name": "Myopium",
+ "store_id": "703ae3b0-7eba-4341-b176-b12f99458903",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "e2e4f457-8d4f-463b-9082-a1e481600179",
+ "date": "2021-12-08T06:27:33 -06:-30",
+ "amount_deducted": 3437.27,
+ "store_name": "Entogrok",
+ "store_id": "61611cf6-9c48-47c4-963a-7abb4975e76e",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "42158ae5-96b9-47ee-9fc8-1a1c826f55d4",
+ "date": "2021-12-14T06:31:41 -06:-30",
+ "amount_deducted": 1917.68,
+ "store_name": "Nexgene",
+ "store_id": "15c73112-678f-47b4-b9e1-e9f1783ca837",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "da240ed7-fead-4f82-8f5c-80da8e303759",
+ "date": "2021-01-13T08:39:52 -06:-30",
+ "amount_deducted": 3404.75,
+ "store_name": "Combogene",
+ "store_id": "5dee99a1-ef35-41e5-97e0-2c4d98bd211d",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "5c5bf532-6128-48ed-8195-f0e5a628ab1e",
+ "date": "2021-10-16T08:25:59 -06:-30",
+ "amount_deducted": 1748.21,
+ "store_name": "Exposa",
+ "store_id": "fc6c1061-5a31-48f4-a662-f95c156a009e",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "0aca5e81-75c7-4a72-8696-ef54690c3443",
+ "date": "2021-08-16T03:34:05 -06:-30",
+ "amount_deducted": 1170.52,
+ "store_name": "Waterbaby",
+ "store_id": "4f264fbd-c67a-4cb6-aeb5-a26db7f42449",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "5b238eab-49ca-48ed-87d4-9789f6cb72c3",
+ "date": "2021-01-05T12:36:21 -06:-30",
+ "amount_deducted": 2749.30,
+ "store_name": "Mangelica",
+ "store_id": "cfc8a65b-ad66-4bdc-9529-51475de767e1",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "9c30bc7b-92ee-4b9c-aa10-ae48a2978b99",
+ "date": "2021-01-29T12:44:42 -06:-30",
+ "amount_deducted": 3205.38,
+ "store_name": "Steeltab",
+ "store_id": "05f18448-b545-476f-aeff-35b07c9f3d2e",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "4e8d2a81-373a-49ff-8ef8-38aea7a3ead9",
+ "date": "2021-04-27T03:27:54 -06:-30",
+ "amount_deducted": 3801.05,
+ "store_name": "Terragen",
+ "store_id": "bb2a1cd1-1a1d-488f-babb-87c08e2305d3",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "e1b6b629-9b9c-48f2-b711-eebb4bfa9b28",
+ "date": "2021-05-28T06:20:11 -06:-30",
+ "amount_deducted": 1070.28,
+ "store_name": "Parcoe",
+ "store_id": "f4fd8e8a-28b3-4dc5-bf7e-b7c004ad1826",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "3c398b85-d349-4060-9545-034db5440227",
+ "date": "2022-01-15T02:24:03 -06:-30",
+ "amount_deducted": 3889.85,
+ "store_name": "Konnect",
+ "store_id": "cefd82dd-734d-4d67-831f-a0b7f1f4313d",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Dominican Republic",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "c9a6d2c3-fdd5-40f2-93a8-24bb4824639b",
+ "date": "2021-12-13T12:01:02 -06:-30",
+ "amount_deducted": 3724.06,
+ "store_name": "Optique",
+ "store_id": "d73e52e7-18dd-466f-9406-c60e424ae1b4",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Algeria",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "bf670c4f-f1bb-415c-b89c-c444593031fe",
+ "date": "2021-02-10T03:21:19 -06:-30",
+ "amount_deducted": 1832.72,
+ "store_name": "Quizka",
+ "store_id": "cc082cd8-8556-432b-8013-7b0c859404dd",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "af41862e-557e-49f6-a3e3-ab90cd917e7e",
+ "date": "2021-09-12T09:22:33 -06:-30",
+ "amount_deducted": 2089.60,
+ "store_name": "Valreda",
+ "store_id": "ab5acba6-46b1-4d22-80fc-233a126dd613",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "e2123897-bc09-4582-93d4-7003297ff074",
+ "date": "2021-11-23T10:41:44 -06:-30",
+ "amount_deducted": 2414.61,
+ "store_name": "Portaline",
+ "store_id": "08cc5bc0-f39b-42f3-919a-9b06d9e6b316",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "d66316da-06e8-4804-b2d4-7662afd5c195",
+ "date": "2022-02-04T06:10:12 -06:-30",
+ "amount_deducted": 2459.75,
+ "store_name": "Endipin",
+ "store_id": "d71dfe95-3053-455c-83c0-83b8a9856ae7",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "21ac817c-e510-4ec4-ab52-16beeb0b171b",
+ "date": "2022-01-31T06:41:22 -06:-30",
+ "amount_deducted": 1677.17,
+ "store_name": "Vurbo",
+ "store_id": "e49510d7-0ca8-4b16-9c75-59b7590b90e6",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "bb49e7ab-7a09-467a-9ecc-ee770732459c",
+ "date": "2021-04-12T07:43:34 -06:-30",
+ "amount_deducted": 1733.97,
+ "store_name": "Imageflow",
+ "store_id": "29113a8a-4327-4927-a5ad-4855a6f5f911",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "6c731117-9ad7-490e-81ea-808e5bcf34ae",
+ "date": "2021-10-03T10:05:12 -06:-30",
+ "amount_deducted": 1052.92,
+ "store_name": "Gogol",
+ "store_id": "464b7c09-b0f2-4449-a5eb-b36f8893fa95",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "acba4185-a071-4b38-a07e-8e897da4741b",
+ "date": "2021-03-18T03:04:28 -06:-30",
+ "amount_deducted": 3647.03,
+ "store_name": "Zedalis",
+ "store_id": "b0f94ddf-7c65-4ed3-a845-c3dc5838642d",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Bosnia and Herzegovina",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "9ed96df5-5cf5-4d12-884d-8a33d5d574dc",
+ "date": "2021-10-20T10:54:05 -06:-30",
+ "amount_deducted": 2808.74,
+ "store_name": "Thredz",
+ "store_id": "5b1bb543-e255-4466-af2a-a8584ec51cb2",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Bosnia and Herzegovina",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "80774090-b047-4fc8-9941-d0efd1329b54",
+ "date": "2021-02-21T06:53:06 -06:-30",
+ "amount_deducted": 1356.91,
+ "store_name": "Trollery",
+ "store_id": "ec368df5-4b46-47f0-b1de-a77bc0f6d73b",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Faroe Islands",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "a4152ace-79a2-41f1-9546-96c51c9d678c",
+ "date": "2021-04-04T09:46:08 -06:-30",
+ "amount_deducted": 1829.86,
+ "store_name": "Omnigog",
+ "store_id": "e0da54c6-9cc8-4186-872e-75c264151861",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "601cb142-ec2a-4537-b939-58aa8dcc8ca9",
+ "date": "2021-11-19T01:36:48 -06:-30",
+ "amount_deducted": 2629.17,
+ "store_name": "Kengen",
+ "store_id": "390b78d3-74bf-477c-9b73-9274ac1a948f",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Egypt",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "fc238def-8007-4fe1-9782-cd975ff127f6",
+ "date": "2021-06-04T03:57:34 -06:-30",
+ "amount_deducted": 2371.09,
+ "store_name": "Medifax",
+ "store_id": "2ed3a186-eab6-4c8c-84d6-69430554dfd6",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Madagascar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "0282f935-c330-4552-a5c6-7e42b2b6bf9e",
+ "date": "2021-04-23T12:45:12 -06:-30",
+ "amount_deducted": 3717.37,
+ "store_name": "Zepitope",
+ "store_id": "5bb602b7-ff48-48da-ba0d-8511ffcd57f5",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Qatar",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "e30f9d04-8b90-445f-bf2d-9f6d741c70c6",
+ "date": "2021-05-26T05:11:11 -06:-30",
+ "amount_deducted": 1451.47,
+ "store_name": "Hopeli",
+ "store_id": "60929a9c-c835-4cd1-914b-c920a32b8db7",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Guyana",
+ "status": "INITIATED"
+ },
+ {
+ "transaction_id": "b7db34b0-dc2f-4ccb-aa3b-10dc08c0cac8",
+ "date": "2021-11-19T07:39:48 -06:-30",
+ "amount_deducted": 1044.11,
+ "store_name": "Signity",
+ "store_id": "59407a24-2324-4150-bb54-12b8b94ee206",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "transaction_location": "Spain",
+ "status": "INITIATED"
+ }
+]
\ No newline at end of file
diff --git a/spring-reactive-architecture/banking-service/src/main/resources/json/users.json b/spring-reactive-architecture/banking-service/src/main/resources/json/users.json
new file mode 100644
index 0000000..d682830
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/main/resources/json/users.json
@@ -0,0 +1,162 @@
+[
+ {
+ "first_name": "Hattie",
+ "last_name": "Fulton",
+ "email": "searsgates@unia.com",
+ "address": "Calder Place",
+ "home_country": "Algeria",
+ "gender": "male",
+ "mobile": "(832) 486-3736",
+ "card_id": "52844ccf-91ca-48bd-8c30-3516bd640c65",
+ "account_number": "61ff8a47c25ca5108043fa15",
+ "account_type": "salary",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Manning",
+ "last_name": "Chambers",
+ "email": "mercadohorn@dancity.com",
+ "address": "Dennett Place",
+ "home_country": "Botswana",
+ "gender": "male",
+ "mobile": "(860) 464-2346",
+ "card_id": "0b13737f-c95c-4438-83ad-d94d124b05e1",
+ "account_number": "61ff8a47af92b4062a9dc1dd",
+ "account_type": "current",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Barker",
+ "last_name": "Guerrero",
+ "email": "bessiecaldwell@rockabye.com",
+ "address": "Cooper Street",
+ "home_country": "Egypt",
+ "gender": "female",
+ "mobile": "(942) 428-3863",
+ "card_id": "33c2275e-fbac-4c95-ab33-a8f263546541",
+ "account_number": "61ff8a47bfc40a4b35d07895",
+ "account_type": "current",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Hamilton",
+ "last_name": "Blair",
+ "email": "ceciliaquinn@concility.com",
+ "address": "Coleridge Street",
+ "home_country": "Bosnia and Herzegovina",
+ "gender": "female",
+ "mobile": "(850) 510-2489",
+ "card_id": "2f5d3157-58cd-4a89-ae63-1159bd4c64b3",
+ "account_number": "61ff8a47a61ef22e408dda30",
+ "account_type": "savings",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Dana",
+ "last_name": "Villarreal",
+ "email": "jacklyncrosby@euron.com",
+ "address": "Oliver Street",
+ "home_country": "Guyana",
+ "gender": "female",
+ "mobile": "(894) 591-3920",
+ "card_id": "30911fe9-dc3d-4cb7-bc34-e839b8d67a72",
+ "account_number": "61ff8a471c2eaaa7a7d0d89a",
+ "account_type": "current",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Nguyen",
+ "last_name": "Decker",
+ "email": "powellprice@enquility.com",
+ "address": "Harway Avenue",
+ "home_country": "Dominican Republic",
+ "gender": "male",
+ "mobile": "(914) 431-2938",
+ "card_id": "acaf1636-1115-4905-ad21-a93d646a7f84",
+ "account_number": "61ff8a4713660cf537a297f1",
+ "account_type": "salary",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Latasha",
+ "last_name": "Coffey",
+ "email": "laramurphy@genmy.com",
+ "address": "Congress Street",
+ "home_country": "Madagascar",
+ "gender": "male",
+ "mobile": "(992) 548-2974",
+ "card_id": "4f242880-9912-4ad9-a5f2-b46e68073bcf",
+ "account_number": "61ff8a477fb41486d55ae3c1",
+ "account_type": "salary",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Claudia",
+ "last_name": "Manning",
+ "email": "lowerylindsey@oulu.com",
+ "address": "Lawn Court",
+ "home_country": "Spain",
+ "gender": "male",
+ "mobile": "(867) 513-2135",
+ "card_id": "558df3d0-c565-4dfd-b1df-909d985d4799",
+ "account_number": "61ff8a47ef6b552a781f31aa",
+ "account_type": "current",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Joyce",
+ "last_name": "Byers",
+ "email": "elmaowen@anacho.com",
+ "address": "Veronica Place",
+ "home_country": "Faroe Islands",
+ "gender": "female",
+ "mobile": "(866) 553-3339",
+ "card_id": "86a8894c-ef9d-40f6-81dc-e72be1642277",
+ "account_number": "61ff8a47415933d52419165f",
+ "account_type": "savings",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ },
+ {
+ "first_name": "Zamora",
+ "last_name": "Byrd",
+ "email": "whitneyblackwell@buzzness.com",
+ "address": "Elm Place",
+ "home_country": "Qatar",
+ "gender": "female",
+ "mobile": "(915) 437-2947",
+ "card_id": "ba5acc1f-00eb-4522-b6c5-a708d9511e7f",
+ "account_number": "61ff8a470cfd7cc363974358",
+ "account_type": "salary",
+ "account_locked": false,
+ "fraudulent_activity_attempt_count": 0,
+ "valid_transactions": [],
+ "fraudulent_transactions": []
+ }
+]
\ No newline at end of file
diff --git a/spring-reactive-architecture/banking-service/src/test/java/io/reflectoring/banking_service/BankingServiceApplicationTests.java b/spring-reactive-architecture/banking-service/src/test/java/io/reflectoring/banking_service/BankingServiceApplicationTests.java
new file mode 100644
index 0000000..1594380
--- /dev/null
+++ b/spring-reactive-architecture/banking-service/src/test/java/io/reflectoring/banking_service/BankingServiceApplicationTests.java
@@ -0,0 +1,13 @@
+package io.reflectoring.banking_service;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+//@SpringBootTest
+class BankingServiceApplicationTests {
+
+// @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/spring-reactive-architecture/docker-compose.yml b/spring-reactive-architecture/docker-compose.yml
new file mode 100644
index 0000000..4230e71
--- /dev/null
+++ b/spring-reactive-architecture/docker-compose.yml
@@ -0,0 +1,73 @@
+version: '3'
+services:
+ zookeeper:
+ image: wurstmeister/zookeeper
+ ports:
+ - "2181:2181"
+ kafka:
+ image: wurstmeister/kafka
+ ports:
+ - "9092:9092"
+ environment:
+ KAFKA_ADVERTISED_HOST_NAME: 10.204.106.55
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181
+ ALLOW_PLAINTEXT_LISTENER: "yes"
+ KAFKA_CFG_LOG_DIRS: /tmp/kafka_mounts/logs
+ KAFKA_CREATE_TOPICS: "transactions:1:2"
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ kafka-ui:
+ image: provectuslabs/kafka-ui
+ container_name: kafka-ui
+ ports:
+ - "8090:8080"
+ depends_on:
+ - zookeeper
+ - kafka
+ restart: always
+ environment:
+ - KAFKA_CLUSTERS_0_NAME=local
+ - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092
+ - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181
+ mongodb:
+ image: mongo:latest
+ ports:
+ - "27017:27017"
+ volumes:
+ - ~/apps/mongo:/data/db
+ banking-service:
+ build: ./banking-service
+ ports:
+ - "8080:8080"
+ depends_on:
+ - zookeeper
+ - kafka
+ - mongodb
+ - user-notification-service
+ - reporting-service
+ - account-management-service
+ user-notification-service:
+ build: ./user-notification-service
+ ports:
+ - "8081:8081"
+ depends_on:
+ - zookeeper
+ - kafka
+ - mongodb
+ reporting-service:
+ build: ./reporting-service
+ ports:
+ - "8082:8082"
+ depends_on:
+ - zookeeper
+ - kafka
+ - mongodb
+ account-management-service:
+ build: ./account-management-service
+ ports:
+ - "8083:8083"
+ depends_on:
+ - zookeeper
+ - kafka
+ - mongodb
\ No newline at end of file
diff --git a/spring-reactive-architecture/mvnw b/spring-reactive-architecture/mvnw
new file mode 100644
index 0000000..8a8fb22
--- /dev/null
+++ b/spring-reactive-architecture/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-reactive-architecture/mvnw.cmd b/spring-reactive-architecture/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/spring-reactive-architecture/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/spring-reactive-architecture/pom.xml b/spring-reactive-architecture/pom.xml
new file mode 100644
index 0000000..cc40bb0
--- /dev/null
+++ b/spring-reactive-architecture/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.6.6
+
+
+ io.reflectoring
+ spring-reactive-architecture
+ 0.0.1-SNAPSHOT
+ spring-reactive-architecture
+ Spring Boot microservice to demonstrate the advantages of Reactive Architecture over simple blocking architecture
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
diff --git a/spring-reactive-architecture/reporting-service/.gitignore b/spring-reactive-architecture/reporting-service/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-reactive-architecture/reporting-service/.mvn/wrapper/maven-wrapper.jar b/spring-reactive-architecture/reporting-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..c1dd12f
Binary files /dev/null and b/spring-reactive-architecture/reporting-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-reactive-architecture/reporting-service/.mvn/wrapper/maven-wrapper.properties b/spring-reactive-architecture/reporting-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..b7cb93e
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/spring-reactive-architecture/reporting-service/Dockerfile b/spring-reactive-architecture/reporting-service/Dockerfile
new file mode 100644
index 0000000..90fb391
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/Dockerfile
@@ -0,0 +1,3 @@
+FROM openjdk:8-jdk-alpine
+COPY target/reporting-service-0.0.1-SNAPSHOT.jar app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/spring-reactive-architecture/reporting-service/mvnw b/spring-reactive-architecture/reporting-service/mvnw
new file mode 100644
index 0000000..8a8fb22
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-reactive-architecture/reporting-service/mvnw.cmd b/spring-reactive-architecture/reporting-service/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/spring-reactive-architecture/reporting-service/pom.xml b/spring-reactive-architecture/reporting-service/pom.xml
new file mode 100644
index 0000000..dcf06d4
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.3
+
+
+ io.reflectoring
+ reporting-service
+ 0.0.1-SNAPSHOT
+ reporting-service
+ Spring Boot to generate reports for different kind of transactions and alert Bank
+
+
+ 1.8
+ 2020.0.3
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-stream-kafka
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.1.0
+
+
+
+
+
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/ReportingServiceApplication.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/ReportingServiceApplication.java
new file mode 100644
index 0000000..944bfbb
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/ReportingServiceApplication.java
@@ -0,0 +1,13 @@
+package io.reflectoring.reporting_service;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ReportingServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ReportingServiceApplication.class, args);
+ }
+
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/constant/TransactionStatus.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/constant/TransactionStatus.java
new file mode 100644
index 0000000..f2f0911
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/constant/TransactionStatus.java
@@ -0,0 +1,15 @@
+package io.reflectoring.reporting_service.constant;
+
+public enum TransactionStatus {
+ INITIATED,
+ SUCCESS,
+ FAILURE,
+ CANCELLED,
+ VALID,
+ ACCOUNT_BLOCKED,
+ CARD_INVALID,
+ FUNDS_UNAVAILABLE,
+ FRAUDULENT,
+ FRAUDULENT_NOTIFY_SUCCESS,
+ FRAUDULENT_NOTIFY_FAILURE
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/controller/ReportingController.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/controller/ReportingController.java
new file mode 100644
index 0000000..c29bfeb
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/controller/ReportingController.java
@@ -0,0 +1,26 @@
+package io.reflectoring.reporting_service.controller;
+
+import io.reflectoring.reporting_service.model.Transaction;
+import io.reflectoring.reporting_service.service.ReportingService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@RestController
+@RequestMapping("/report")
+public class ReportingController {
+
+ @Autowired
+ private ReportingService reportingService;
+
+ @PostMapping("/")
+ public Mono report(@RequestBody Transaction transaction) {
+ log.info("Process transaction with details in reporting service: {}", transaction);
+ return reportingService.report(transaction);
+ }
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/kafka/consumer/TransactionConsumer.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/kafka/consumer/TransactionConsumer.java
new file mode 100644
index 0000000..0dd95e5
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/kafka/consumer/TransactionConsumer.java
@@ -0,0 +1,19 @@
+package io.reflectoring.reporting_service.kafka.consumer;
+
+import io.reflectoring.reporting_service.model.Transaction;
+import io.reflectoring.reporting_service.service.ReportingService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.function.Consumer;
+
+@Slf4j
+@Configuration
+public class TransactionConsumer {
+
+ @Bean
+ public Consumer consumeTransaction(ReportingService reportingService) {
+ return reportingService::asyncProcess;
+ }
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/kafka/producer/TransactionProducer.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/kafka/producer/TransactionProducer.java
new file mode 100644
index 0000000..82094d2
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/kafka/producer/TransactionProducer.java
@@ -0,0 +1,29 @@
+package io.reflectoring.reporting_service.kafka.producer;
+
+import io.reflectoring.reporting_service.model.Transaction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+@Service
+public class TransactionProducer {
+
+ @Autowired
+ private StreamBridge streamBridge;
+
+ public void sendMessage(Transaction transaction) {
+ Message msg = MessageBuilder.withPayload(transaction)
+ .setHeader(KafkaHeaders.MESSAGE_KEY, transaction.getTransactionId().getBytes(StandardCharsets.UTF_8))
+ .build();
+ log.info("Transaction processed to dispatch: {}; Message dispatch successful: {}",
+ msg,
+ streamBridge.send("transaction-out-0", msg));
+ }
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/model/Transaction.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/model/Transaction.java
new file mode 100644
index 0000000..3b95403
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/model/Transaction.java
@@ -0,0 +1,37 @@
+package io.reflectoring.reporting_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.reflectoring.reporting_service.constant.TransactionStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class Transaction {
+
+ @Id
+ @JsonProperty("transaction_id")
+ private String transactionId;
+ private String date;
+
+ @JsonProperty("amount_deducted")
+ private double amountDeducted;
+
+ @JsonProperty("store_name")
+ private String storeName;
+
+ @JsonProperty("store_id")
+ private String storeId;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("transaction_location")
+ private String transactionLocation;
+ private TransactionStatus status;
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/model/User.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/model/User.java
new file mode 100644
index 0000000..f3a8f9d
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/model/User.java
@@ -0,0 +1,57 @@
+package io.reflectoring.reporting_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Transient;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.List;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class User {
+
+ @Id
+ private String id;
+
+ @JsonProperty("first_name")
+ private String firstName;
+
+ @JsonProperty("last_name")
+ private String lastName;
+ private String email;
+ private String address;
+
+ @JsonProperty("home_country")
+ private String homeCountry;
+ private String gender;
+ private String mobile;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("account_number")
+ private String accountNumber;
+
+ @JsonProperty("account_type")
+ private String accountType;
+
+ @JsonProperty("account_locked")
+ private boolean accountLocked;
+
+ @JsonProperty("fraudulent_activity_attempt_count")
+ private Long fraudulentActivityAttemptCount;
+
+ @Transient
+ @JsonProperty("valid_transactions")
+ private List validTransactions;
+
+ @Transient
+ @JsonProperty("fraudulent_transactions")
+ private List fraudulentTransactions;
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/repository/TransactionRepository.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/repository/TransactionRepository.java
new file mode 100644
index 0000000..09f99d2
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/repository/TransactionRepository.java
@@ -0,0 +1,7 @@
+package io.reflectoring.reporting_service.repository;
+
+import io.reflectoring.reporting_service.model.Transaction;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+
+public interface TransactionRepository extends ReactiveMongoRepository {
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/repository/UserRepository.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/repository/UserRepository.java
new file mode 100644
index 0000000..91cd507
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/repository/UserRepository.java
@@ -0,0 +1,10 @@
+package io.reflectoring.reporting_service.repository;
+
+import io.reflectoring.reporting_service.model.User;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import reactor.core.publisher.Mono;
+
+public interface UserRepository extends ReactiveMongoRepository {
+
+ Mono findByCardId(String cardId);
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/service/ReportingService.java b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/service/ReportingService.java
new file mode 100644
index 0000000..b476157
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/java/io/reflectoring/reporting_service/service/ReportingService.java
@@ -0,0 +1,99 @@
+package io.reflectoring.reporting_service.service;
+
+import io.reflectoring.reporting_service.constant.TransactionStatus;
+import io.reflectoring.reporting_service.kafka.producer.TransactionProducer;
+import io.reflectoring.reporting_service.model.Transaction;
+import io.reflectoring.reporting_service.model.User;
+import io.reflectoring.reporting_service.repository.TransactionRepository;
+import io.reflectoring.reporting_service.repository.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+@Service
+public class ReportingService {
+
+ @Autowired
+ private TransactionRepository transactionRepo;
+
+ @Autowired
+ private UserRepository userRepo;
+
+ @Autowired
+ private TransactionProducer producer;
+
+ public Mono report(Transaction transaction) {
+ return userRepo.findByCardId(transaction.getCardId())
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.FRAUDULENT)
+ || transaction.getStatus().equals(TransactionStatus.FRAUDULENT_NOTIFY_SUCCESS)
+ || transaction.getStatus().equals(TransactionStatus.FRAUDULENT_NOTIFY_FAILURE)) {
+
+ // Report the User's account and take automatic action against User's account or card
+ u.setFraudulentActivityAttemptCount(u.getFraudulentActivityAttemptCount() + 1);
+ u.setAccountLocked(u.getFraudulentActivityAttemptCount() > 3);
+ List newList = new ArrayList<>();
+ newList.add(transaction);
+ if (Objects.isNull(u.getFraudulentTransactions()) || u.getFraudulentTransactions().isEmpty()) {
+ u.setFraudulentTransactions(newList);
+ } else {
+ u.getFraudulentTransactions().add(transaction);
+ }
+ }
+ log.info("User details: {}", u);
+ return u;
+ })
+ .flatMap(userRepo::save)
+ .map(u -> {
+ if (!transaction.getStatus().equals(TransactionStatus.VALID)) {
+ transaction.setStatus(u.isAccountLocked()
+ ? TransactionStatus.ACCOUNT_BLOCKED : TransactionStatus.FAILURE);
+ }
+ return transaction;
+ })
+ .flatMap(transactionRepo::save);
+ }
+
+ public void asyncProcess(Transaction transaction) {
+ userRepo.findByCardId(transaction.getCardId())
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.FRAUDULENT)
+ || transaction.getStatus().equals(TransactionStatus.FRAUDULENT_NOTIFY_SUCCESS)
+ || transaction.getStatus().equals(TransactionStatus.FRAUDULENT_NOTIFY_FAILURE)) {
+
+ // Report the User's account and take automatic action against User's account or card
+ u.setFraudulentActivityAttemptCount(u.getFraudulentActivityAttemptCount() + 1);
+ u.setAccountLocked(u.getFraudulentActivityAttemptCount() > 3);
+ List newList = new ArrayList<>();
+ newList.add(transaction);
+ if (Objects.isNull(u.getFraudulentTransactions()) || u.getFraudulentTransactions().isEmpty()) {
+ u.setFraudulentTransactions(newList);
+ } else {
+ u.getFraudulentTransactions().add(transaction);
+ }
+ }
+ log.info("User details: {}", u);
+ return u;
+ })
+ .flatMap(userRepo::save)
+ .map(u -> {
+ if (!transaction.getStatus().equals(TransactionStatus.VALID)) {
+ transaction.setStatus(u.isAccountLocked()
+ ? TransactionStatus.ACCOUNT_BLOCKED : TransactionStatus.FAILURE);
+ producer.sendMessage(transaction);
+ }
+ return transaction;
+ })
+ .filter(t -> t.getStatus().equals(TransactionStatus.FAILURE)
+ || t.getStatus().equals(TransactionStatus.ACCOUNT_BLOCKED)
+ )
+ .flatMap(transactionRepo::save)
+ .subscribe();
+ }
+}
diff --git a/spring-reactive-architecture/reporting-service/src/main/resources/application.yml b/spring-reactive-architecture/reporting-service/src/main/resources/application.yml
new file mode 100644
index 0000000..3cac518
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/main/resources/application.yml
@@ -0,0 +1,38 @@
+server:
+ port: 8082
+
+# Configure Spring specific properties
+spring:
+
+ # Enable/Disable hot swapping
+ devtools:
+ restart:
+ enabled: true
+ log-condition-evaluation-delta: false
+
+ # Datasource Configurations
+ data:
+ mongodb:
+ authentication-database: admin
+ uri: mongodb://vlab045701.dom045700.lab:27017/reactive
+ database: reactive
+
+ # Kafka Configuration
+ cloud:
+ function:
+ definition: consumeTransaction
+ stream:
+ kafka:
+ binder:
+ brokers: vlab045701.dom045700.lab:9092
+ autoCreateTopics: false
+ bindings:
+ consumeTransaction-in-0:
+ consumer:
+ max-attempts: 3
+ back-off-initial-interval: 100
+ destination: transactions
+ group: reporting
+ concurrency: 1
+ transaction-out-0:
+ destination: transactions
diff --git a/spring-reactive-architecture/reporting-service/src/test/java/io/reflectoring/reporting_service/ReportingServiceApplicationTests.java b/spring-reactive-architecture/reporting-service/src/test/java/io/reflectoring/reporting_service/ReportingServiceApplicationTests.java
new file mode 100644
index 0000000..2934b92
--- /dev/null
+++ b/spring-reactive-architecture/reporting-service/src/test/java/io/reflectoring/reporting_service/ReportingServiceApplicationTests.java
@@ -0,0 +1,13 @@
+package io.reflectoring.reporting_service;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ReportingServiceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/spring-reactive-architecture/user-notification-service/.gitignore b/spring-reactive-architecture/user-notification-service/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/spring-reactive-architecture/user-notification-service/.mvn/wrapper/maven-wrapper.jar b/spring-reactive-architecture/user-notification-service/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..c1dd12f
Binary files /dev/null and b/spring-reactive-architecture/user-notification-service/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-reactive-architecture/user-notification-service/.mvn/wrapper/maven-wrapper.properties b/spring-reactive-architecture/user-notification-service/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..b7cb93e
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/spring-reactive-architecture/user-notification-service/Dockerfile b/spring-reactive-architecture/user-notification-service/Dockerfile
new file mode 100644
index 0000000..4af237a
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/Dockerfile
@@ -0,0 +1,3 @@
+FROM openjdk:8-jdk-alpine
+COPY target/user-notification-service-0.0.1-SNAPSHOT.jar app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]
\ No newline at end of file
diff --git a/spring-reactive-architecture/user-notification-service/mvnw b/spring-reactive-architecture/user-notification-service/mvnw
new file mode 100644
index 0000000..8a8fb22
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-reactive-architecture/user-notification-service/mvnw.cmd b/spring-reactive-architecture/user-notification-service/mvnw.cmd
new file mode 100644
index 0000000..1d8ab01
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/spring-reactive-architecture/user-notification-service/pom.xml b/spring-reactive-architecture/user-notification-service/pom.xml
new file mode 100644
index 0000000..abc9932
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/pom.xml
@@ -0,0 +1,101 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.3
+
+
+ io.reflectoring
+ user-notification-service
+ 0.0.1-SNAPSHOT
+ user-notification-service
+ Spring Boot microservice to notify Users on the transaction information
+
+
+ 1.8
+ 2020.0.3
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.cloud
+ spring-cloud-starter-stream-kafka
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.1.0
+
+
+
+
+
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/UserNotificationServiceApplication.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/UserNotificationServiceApplication.java
new file mode 100644
index 0000000..c826609
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/UserNotificationServiceApplication.java
@@ -0,0 +1,13 @@
+package io.reflectoring.user_notification_service;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class UserNotificationServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(UserNotificationServiceApplication.class, args);
+ }
+
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/config/ApplicationConfiguration.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/config/ApplicationConfiguration.java
new file mode 100644
index 0000000..f4c1cf5
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/config/ApplicationConfiguration.java
@@ -0,0 +1,30 @@
+package io.reflectoring.user_notification_service.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+
+import java.util.Properties;
+
+@Configuration
+public class ApplicationConfiguration {
+
+
+ @Bean
+ public JavaMailSender getJavaMailSender() {
+ JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
+ mailSender.setHost("smtp.gmail.com");
+ mailSender.setPort(587);
+ mailSender.setUsername("my.gmail@gmail.com");
+ mailSender.setPassword("password");
+
+ Properties props = mailSender.getJavaMailProperties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.auth", "true");
+ props.put("mail.smtp.starttls.enable", "true");
+ props.put("mail.debug", "true");
+
+ return mailSender;
+ }
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/constant/TransactionStatus.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/constant/TransactionStatus.java
new file mode 100644
index 0000000..3968f27
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/constant/TransactionStatus.java
@@ -0,0 +1,15 @@
+package io.reflectoring.user_notification_service.constant;
+
+public enum TransactionStatus {
+ INITIATED,
+ SUCCESS,
+ FAILURE,
+ CANCELLED,
+ VALID,
+ ACCOUNT_BLOCKED,
+ CARD_INVALID,
+ FUNDS_UNAVAILABLE,
+ FRAUDULENT,
+ FRAUDULENT_NOTIFY_SUCCESS,
+ FRAUDULENT_NOTIFY_FAILURE
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/controller/UserNotificationController.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/controller/UserNotificationController.java
new file mode 100644
index 0000000..7fe636b
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/controller/UserNotificationController.java
@@ -0,0 +1,26 @@
+package io.reflectoring.user_notification_service.controller;
+
+import io.reflectoring.user_notification_service.model.Transaction;
+import io.reflectoring.user_notification_service.service.UserNotificationService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@RestController
+@RequestMapping("/notify")
+public class UserNotificationController {
+
+ @Autowired
+ private UserNotificationService userNotificationService;
+
+ @PostMapping("/fraudulent-transaction")
+ public Mono notify(@RequestBody Transaction transaction) {
+ log.info("Process transaction with details and notify user: {}", transaction);
+ return userNotificationService.notify(transaction);
+ }
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/kafka/consumer/TransactionConsumer.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/kafka/consumer/TransactionConsumer.java
new file mode 100644
index 0000000..964a369
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/kafka/consumer/TransactionConsumer.java
@@ -0,0 +1,19 @@
+package io.reflectoring.user_notification_service.kafka.consumer;
+
+import io.reflectoring.user_notification_service.model.Transaction;
+import io.reflectoring.user_notification_service.service.UserNotificationService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.function.Consumer;
+
+@Slf4j
+@Configuration
+public class TransactionConsumer {
+
+ @Bean
+ public Consumer consumeTransaction(UserNotificationService userNotificationService) {
+ return userNotificationService::asyncProcess;
+ }
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/kafka/producer/TransactionProducer.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/kafka/producer/TransactionProducer.java
new file mode 100644
index 0000000..c25c633
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/kafka/producer/TransactionProducer.java
@@ -0,0 +1,29 @@
+package io.reflectoring.user_notification_service.kafka.producer;
+
+import io.reflectoring.user_notification_service.model.Transaction;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.stream.function.StreamBridge;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.support.MessageBuilder;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+@Service
+public class TransactionProducer {
+
+ @Autowired
+ private StreamBridge streamBridge;
+
+ public void sendMessage(Transaction transaction) {
+ Message msg = MessageBuilder.withPayload(transaction)
+ .setHeader(KafkaHeaders.MESSAGE_KEY, transaction.getTransactionId().getBytes(StandardCharsets.UTF_8))
+ .build();
+ log.info("Transaction processed to dispatch: {}; Message dispatch successful: {}",
+ msg,
+ streamBridge.send("transaction-out-0", msg));
+ }
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/model/Transaction.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/model/Transaction.java
new file mode 100644
index 0000000..9922de7
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/model/Transaction.java
@@ -0,0 +1,37 @@
+package io.reflectoring.user_notification_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.reflectoring.user_notification_service.constant.TransactionStatus;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class Transaction {
+
+ @Id
+ @JsonProperty("transaction_id")
+ private String transactionId;
+ private String date;
+
+ @JsonProperty("amount_deducted")
+ private double amountDeducted;
+
+ @JsonProperty("store_name")
+ private String storeName;
+
+ @JsonProperty("store_id")
+ private String storeId;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("transaction_location")
+ private String transactionLocation;
+ private TransactionStatus status;
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/model/User.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/model/User.java
new file mode 100644
index 0000000..7a822e8
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/model/User.java
@@ -0,0 +1,57 @@
+package io.reflectoring.user_notification_service.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Transient;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.List;
+
+@Data
+@Document
+@ToString
+@NoArgsConstructor
+public class User {
+
+ @Id
+ private String id;
+
+ @JsonProperty("first_name")
+ private String firstName;
+
+ @JsonProperty("last_name")
+ private String lastName;
+ private String email;
+ private String address;
+
+ @JsonProperty("home_country")
+ private String homeCountry;
+ private String gender;
+ private String mobile;
+
+ @JsonProperty("card_id")
+ private String cardId;
+
+ @JsonProperty("account_number")
+ private String accountNumber;
+
+ @JsonProperty("account_type")
+ private String accountType;
+
+ @JsonProperty("account_locked")
+ private boolean accountLocked;
+
+ @JsonProperty("fraudulent_activity_attempt_count")
+ private Long fraudulentActivityAttemptCount;
+
+ @Transient
+ @JsonProperty("valid_transactions")
+ private List validTransactions;
+
+ @Transient
+ @JsonProperty("fraudulent_transactions")
+ private List fraudulentTransactions;
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/repository/TransactionRepository.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/repository/TransactionRepository.java
new file mode 100644
index 0000000..02074eb
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/repository/TransactionRepository.java
@@ -0,0 +1,7 @@
+package io.reflectoring.user_notification_service.repository;
+
+import io.reflectoring.user_notification_service.model.Transaction;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+
+public interface TransactionRepository extends ReactiveMongoRepository {
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/repository/UserRepository.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/repository/UserRepository.java
new file mode 100644
index 0000000..4c4eec9
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/repository/UserRepository.java
@@ -0,0 +1,10 @@
+package io.reflectoring.user_notification_service.repository;
+
+import io.reflectoring.user_notification_service.model.User;
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import reactor.core.publisher.Mono;
+
+public interface UserRepository extends ReactiveMongoRepository {
+
+ Mono findByCardId(String cardId);
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/service/UserNotificationService.java b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/service/UserNotificationService.java
new file mode 100644
index 0000000..b7449ea
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/java/io/reflectoring/user_notification_service/service/UserNotificationService.java
@@ -0,0 +1,92 @@
+package io.reflectoring.user_notification_service.service;
+
+import io.reflectoring.user_notification_service.constant.TransactionStatus;
+import io.reflectoring.user_notification_service.kafka.producer.TransactionProducer;
+import io.reflectoring.user_notification_service.model.Transaction;
+import io.reflectoring.user_notification_service.repository.TransactionRepository;
+import io.reflectoring.user_notification_service.repository.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.MailException;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@Service
+public class UserNotificationService {
+
+ @Autowired
+ private TransactionRepository transactionRepo;
+
+ @Autowired
+ private UserRepository userRepo;
+
+ @Autowired
+ private JavaMailSender emailSender;
+
+ @Autowired
+ private TransactionProducer producer;
+
+ public Mono notify(Transaction transaction) {
+ return userRepo.findByCardId(transaction.getCardId())
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.FRAUDULENT)) {
+
+ // Notify user by sending email
+ SimpleMailMessage message = new SimpleMailMessage();
+ message.setFrom("noreply@baeldung.com");
+ message.setTo(u.getEmail());
+ message.setSubject("Fraudulent transaction attempt from your card");
+ message.setText("An attempt has been made to pay " + transaction.getStoreName()
+ + " from card " + transaction.getCardId() + " in the country "
+ + transaction.getTransactionLocation() + "." +
+ " Please report to your bank or block your card.");
+ emailSender.send(message);
+ transaction.setStatus(TransactionStatus.FRAUDULENT_NOTIFY_SUCCESS);
+ } else {
+ transaction.setStatus(TransactionStatus.FRAUDULENT_NOTIFY_FAILURE);
+ }
+ return transaction;
+ })
+ .onErrorReturn(transaction)
+ .flatMap(transactionRepo::save);
+ }
+
+ public void asyncProcess(Transaction transaction) {
+ userRepo.findByCardId(transaction.getCardId())
+ .map(u -> {
+ if (transaction.getStatus().equals(TransactionStatus.FRAUDULENT)) {
+
+ try {
+ // Notify user by sending email
+ SimpleMailMessage message = new SimpleMailMessage();
+ message.setFrom("noreply@baeldung.com");
+ message.setTo(u.getEmail());
+ message.setSubject("Fraudulent transaction attempt from your card");
+ message.setText("An attempt has been made to pay " + transaction.getStoreName()
+ + " from card " + transaction.getCardId() + " in the country "
+ + transaction.getTransactionLocation() + "." +
+ " Please report to your bank or block your card.");
+ emailSender.send(message);
+ transaction.setStatus(TransactionStatus.FRAUDULENT_NOTIFY_SUCCESS);
+ } catch (MailException e) {
+ transaction.setStatus(TransactionStatus.FRAUDULENT_NOTIFY_FAILURE);
+ }
+ }
+ return transaction;
+ })
+ .onErrorReturn(transaction)
+ .filter(t -> t.getStatus().equals(TransactionStatus.FRAUDULENT)
+ || t.getStatus().equals(TransactionStatus.FRAUDULENT_NOTIFY_SUCCESS)
+ || t.getStatus().equals(TransactionStatus.FRAUDULENT_NOTIFY_FAILURE)
+ )
+ .map(t -> {
+ producer.sendMessage(t);
+ return t;
+ })
+ .flatMap(transactionRepo::save)
+ .subscribe();
+ }
+}
diff --git a/spring-reactive-architecture/user-notification-service/src/main/resources/application.yml b/spring-reactive-architecture/user-notification-service/src/main/resources/application.yml
new file mode 100644
index 0000000..95b847f
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/main/resources/application.yml
@@ -0,0 +1,38 @@
+server:
+ port: 8081
+
+# Configure Spring specific properties
+spring:
+
+ # Enable/Disable hot swapping
+ devtools:
+ restart:
+ enabled: true
+ log-condition-evaluation-delta: false
+
+ # Datasource Configurations
+ data:
+ mongodb:
+ authentication-database: admin
+ uri: mongodb://vlab045701.dom045700.lab:27017/reactive
+ database: reactive
+
+ # Kafka Configuration
+ cloud:
+ function:
+ definition: consumeTransaction
+ stream:
+ kafka:
+ binder:
+ brokers: vlab045701.dom045700.lab:9092
+ autoCreateTopics: false
+ bindings:
+ consumeTransaction-in-0:
+ consumer:
+ max-attempts: 3
+ back-off-initial-interval: 100
+ destination: transactions
+ group: user-notification
+ concurrency: 1
+ transaction-out-0:
+ destination: transactions
\ No newline at end of file
diff --git a/spring-reactive-architecture/user-notification-service/src/test/java/io/reflectoring/user_notification_service/UserNotificationServiceApplicationTests.java b/spring-reactive-architecture/user-notification-service/src/test/java/io/reflectoring/user_notification_service/UserNotificationServiceApplicationTests.java
new file mode 100644
index 0000000..66c188d
--- /dev/null
+++ b/spring-reactive-architecture/user-notification-service/src/test/java/io/reflectoring/user_notification_service/UserNotificationServiceApplicationTests.java
@@ -0,0 +1,13 @@
+package io.reflectoring.user_notification_service;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class UserNotificationServiceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}