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