├── .gitignore ├── README.md ├── gamification ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ ├── GamificationApplication.java │ │ │ └── gamification │ │ │ ├── client │ │ │ ├── MultiplicationResultAttemptClient.java │ │ │ ├── MultiplicationResultAttemptClientImpl.java │ │ │ ├── MultiplicationResultAttemptDeserializer.java │ │ │ └── dto │ │ │ │ └── MultiplicationResultAttempt.java │ │ │ ├── configuration │ │ │ ├── RabbitMQConfiguration.java │ │ │ ├── RestClientConfiguration.java │ │ │ └── WebConfiguration.java │ │ │ ├── controller │ │ │ ├── AdminController.java │ │ │ ├── LeaderBoardController.java │ │ │ ├── ScoreController.java │ │ │ └── UserStatsController.java │ │ │ ├── domain │ │ │ ├── Badge.java │ │ │ ├── BadgeCard.java │ │ │ ├── GameStats.java │ │ │ ├── LeaderBoardRow.java │ │ │ └── ScoreCard.java │ │ │ ├── event │ │ │ ├── EventHandler.java │ │ │ └── MultiplicationSolvedEvent.java │ │ │ ├── repository │ │ │ ├── BadgeCardRepository.java │ │ │ └── ScoreCardRepository.java │ │ │ └── service │ │ │ ├── AdminService.java │ │ │ ├── AdminServiceImpl.java │ │ │ ├── GameService.java │ │ │ ├── GameServiceImpl.java │ │ │ ├── LeaderBoardService.java │ │ │ └── LeaderBoardServiceImpl.java │ └── resources │ │ ├── application-test.properties │ │ ├── application.properties │ │ └── bootstrap.properties │ └── test │ └── java │ └── microservices │ └── book │ └── gamification │ ├── controller │ ├── AdminControllerDisabledTest.java │ ├── AdminControllerEnabledTest.java │ ├── LeaderBoardControllerTest.java │ ├── ScoreControllerTest.java │ └── UserStatsControllerTest.java │ ├── event │ └── EventHandlerTest.java │ └── service │ ├── GameServiceImplTest.java │ └── LeaderBoardServiceImplTest.java ├── gateway ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ └── gateway │ │ │ ├── GatewayApplication.java │ │ │ └── configuration │ │ │ ├── HystrixFallbackConfiguration.java │ │ │ ├── RibbonConfiguration.java │ │ │ └── WebConfiguration.java │ └── resources │ │ ├── application.yml │ │ └── bootstrap.yml │ └── test │ └── java │ └── microservices │ └── book │ └── gateway │ └── GatewayApplicationTests.java ├── resources └── logical_view_v8.png ├── service-registry ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ └── serviceregistry │ │ │ └── ServiceRegistryApplication.java │ └── resources │ │ ├── application.properties │ │ └── bootstrap.properties │ └── test │ └── java │ └── microservices │ └── book │ └── serviceregistry │ └── ServiceRegistryApplicationTests.java ├── social-multiplication ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ ├── SocialMultiplicationApplication.java │ │ │ └── multiplication │ │ │ ├── configuration │ │ │ ├── RabbitMQConfiguration.java │ │ │ └── WebConfiguration.java │ │ │ ├── controller │ │ │ ├── AdminController.java │ │ │ ├── MultiplicationController.java │ │ │ ├── MultiplicationResultAttemptController.java │ │ │ └── UserController.java │ │ │ ├── domain │ │ │ ├── Multiplication.java │ │ │ ├── MultiplicationResultAttempt.java │ │ │ └── User.java │ │ │ ├── event │ │ │ ├── EventDispatcher.java │ │ │ └── MultiplicationSolvedEvent.java │ │ │ ├── repository │ │ │ ├── MultiplicationRepository.java │ │ │ ├── MultiplicationResultAttemptRepository.java │ │ │ └── UserRepository.java │ │ │ └── service │ │ │ ├── AdminService.java │ │ │ ├── AdminServiceImpl.java │ │ │ ├── MultiplicationService.java │ │ │ ├── MultiplicationServiceImpl.java │ │ │ ├── RandomGeneratorService.java │ │ │ └── RandomGeneratorServiceImpl.java │ └── resources │ │ ├── application-test.properties │ │ ├── application.properties │ │ └── bootstrap.properties │ └── test │ └── java │ └── microservices │ └── book │ └── multiplication │ ├── controller │ ├── AdminControllerDisabledTest.java │ ├── AdminControllerEnabledTest.java │ ├── MultiplicationControllerTest.java │ ├── MultiplicationResultAttemptControllerTest.java │ └── UserControllerTest.java │ └── service │ ├── MultiplicationServiceImplTest.java │ └── RandomGeneratorServiceImplTest.java ├── tests_e2e ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ └── test │ ├── java │ └── microservices │ │ └── book │ │ ├── LeaderboardFeatureSteps.java │ │ ├── LeaderboardFeatureTest.java │ │ ├── MultiplicationFeatureSteps.java │ │ ├── MultiplicationFeatureTest.java │ │ └── testutils │ │ ├── MultiplicationApplication.java │ │ ├── beans │ │ ├── AttemptResponse.java │ │ ├── LeaderBoardPosition.java │ │ ├── ScoreResponse.java │ │ ├── Stats.java │ │ └── User.java │ │ └── http │ │ └── ApplicationHttpUtils.java │ └── resources │ ├── leaderboard.feature │ └── multiplication.feature └── ui ├── start.d ├── deploy.ini └── http.ini └── webapps └── ui ├── css ├── bootstrap-theme.css ├── bootstrap-theme.css.map ├── bootstrap-theme.min.css ├── bootstrap-theme.min.css.map ├── bootstrap.css ├── bootstrap.css.map ├── bootstrap.min.css └── bootstrap.min.css.map ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 ├── gamification-client.js ├── index.html ├── js ├── bootstrap.js ├── bootstrap.min.js └── npm.js ├── multiplication-client.js └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn Microservices with Spring Boot - v10 2 | 3 | This project contains the version 10 of the application that is developed under the scope of the book *Learn Microservices with Spring Boot*. You can buy the book on [Amazon](http://amzn.to/2FSB2ME) or [Apress](http://www.apress.com/gp/book/9781484231647). 4 | 5 | The book shows you how to evolve a simple Spring Boot application to become a full Microservices Architecture, using Spring Cloud Eureka, Ribbon, Zuul and Hystrix to implement Service Discovery, Load Balancing, the API Gateway pattern and a Circuit Breaker. Besides, you'll learn how to implement End-to-End tests with Cucumber, an Event-Driven system and the best practices when building Microservices. 6 | 7 | ## Idea 8 | 9 | These projects, included in [Microservices-Practical repositories](https://github.com/microservices-practical), illustrate how to start an application from scratch and then evolve it to become a full microservices environment. 10 | 11 | This version has exactly the same functionality as the v9 version, but the projects have been upgraded to Spring Boot 2.0, thus applying some minor changes in code to make them work. 12 | 13 | ## Contents 14 | 15 | The repository contains six folders, one for each component of the system: 16 | 17 | * **social-multiplication** is one of the backend services. It has a REST API to get and provide results to simple multiplications. When an attempt is sent, it triggers an *event*. 18 | * **gamification** is the second backend service. It provides a REST API to get game stats and reacts to the event sent by the multiplication service, updating the figures. 19 | * **service-registry** is the Eureka Server, which is contacted by backend services and the gateway. 20 | * **gateway** is the Routing Service, implemented with Zuul. It connects with Eureka for service discovery, and performs load balancing with Ribbon. 21 | * **ui** contains the static files of the frontend application. It's configured to be started with Jetty. 22 | * **tests_e2e** contains the end to end test cases, developed with Cucumber (Gherkin notation) 23 | 24 | ## How to execute the application 25 | 26 | These are the instructions: 27 | 28 | * **UI**. Download and install [Jetty](http://www.eclipse.org/jetty/download.html). From the command line, execute `java -jar [JETTY_HOME]/jetty-[VERSION]/start.jar` (replace the values between brackets accordingly). You also need Java. 29 | * **RabbitMQ**. Download and install [RabbitMQ](https://www.rabbitmq.com/download.html). We use it as Event bus. When you have installed it, you need to run the RabbitMQ server (as a service or as a process, whatever you prefer). 30 | * **Multiplication, Gamification, Service Registry and Gateway**. You need to start all these services using the command line. Navigate to each folder and execute for every one of them: `./mvnw spring-boot:run`. If you want to try load balancing you can execute more than one instance of the Multiplication or Gamification service. To do that, override the port number to avoid clashing: `./mvnw spring-boot:run -Drun.arguments="--server.port=A_FREE_PORT]"` 31 | 32 | ![Application - version 8](resources/logical_view_v8.png) 33 | 34 | ## Do you want to know more? 35 | 36 | If you want more details about the different application parts and how to get there from scratch, you can buy the book on [Apress](http://www.apress.com/gp/book/9781484231647) on [Amazon](http://amzn.to/2FSB2ME). 37 | 38 | -------------------------------------------------------------------------------- /gamification/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /gamification/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/gamification/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /gamification/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /gamification/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /gamification/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | gamification-v10 8 | 0.10.0-SNAPSHOT 9 | jar 10 | 11 | gamification-v10 12 | Social Multiplication App - Gamification (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.0.M2 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | Finchley.M2 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-dependencies 32 | ${spring-cloud.version} 33 | pom 34 | import 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-amqp 48 | 49 | 50 | 51 | org.springframework.cloud 52 | spring-cloud-starter-eureka 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-actuator 58 | 59 | 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | 1.16.18 65 | provided 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-data-jpa 71 | 72 | 73 | 74 | org.springframework.cloud 75 | spring-cloud-starter-hystrix 76 | 77 | 78 | 79 | com.h2database 80 | h2 81 | runtime 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-test 87 | test 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-maven-plugin 96 | 97 | 98 | 99 | 100 | 101 | 102 | spring-snapshots 103 | Spring Snapshots 104 | https://repo.spring.io/snapshot 105 | 106 | true 107 | 108 | 109 | 110 | spring-milestones 111 | Spring Milestones 112 | https://repo.spring.io/milestone 113 | 114 | false 115 | 116 | 117 | 118 | 119 | 120 | 121 | spring-snapshots 122 | Spring Snapshots 123 | https://repo.spring.io/snapshot 124 | 125 | true 126 | 127 | 128 | 129 | spring-milestones 130 | Spring Milestones 131 | https://repo.spring.io/milestone 132 | 133 | false 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/GamificationApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | 8 | @EnableEurekaClient 9 | @EnableCircuitBreaker 10 | @SpringBootApplication 11 | public class GamificationApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(GamificationApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/MultiplicationResultAttemptClient.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client; 2 | 3 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 4 | 5 | /** 6 | * This interface allows us to connect to the Multiplication microservice. 7 | * Note that it's agnostic to the way of communication. 8 | */ 9 | public interface MultiplicationResultAttemptClient { 10 | 11 | MultiplicationResultAttempt retrieveMultiplicationResultAttemptbyId(final Long multiplicationId); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/MultiplicationResultAttemptClientImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client; 2 | 3 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 4 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | /** 11 | * This implementation of MultiplicationResultAttemptClient interface connects to 12 | * the Multiplication microservice via REST. 13 | */ 14 | @Component 15 | class MultiplicationResultAttemptClientImpl implements MultiplicationResultAttemptClient { 16 | 17 | private final RestTemplate restTemplate; 18 | private final String multiplicationHost; 19 | 20 | @Autowired 21 | public MultiplicationResultAttemptClientImpl(final RestTemplate restTemplate, 22 | @Value("${multiplicationHost}") final String multiplicationHost) { 23 | this.restTemplate = restTemplate; 24 | this.multiplicationHost = multiplicationHost; 25 | } 26 | 27 | @HystrixCommand(fallbackMethod = "defaultResult") 28 | @Override 29 | public MultiplicationResultAttempt retrieveMultiplicationResultAttemptbyId(final Long multiplicationResultAttemptId) { 30 | return restTemplate.getForObject( 31 | multiplicationHost + "/results/" + multiplicationResultAttemptId, 32 | MultiplicationResultAttempt.class); 33 | } 34 | 35 | private MultiplicationResultAttempt defaultResult(final Long multiplicationResultAttemptId) { 36 | return new MultiplicationResultAttempt("fakeAlias", 37 | 10, 10, 100, true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/MultiplicationResultAttemptDeserializer.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.core.ObjectCodec; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.JsonDeserializer; 8 | import com.fasterxml.jackson.databind.JsonNode; 9 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 10 | 11 | import java.io.IOException; 12 | 13 | /** 14 | * Deserializes an attempt coming from the Multiplication microservice 15 | * into the Gamification's representation of an attempt. 16 | */ 17 | public class MultiplicationResultAttemptDeserializer 18 | extends JsonDeserializer { 19 | 20 | @Override 21 | public MultiplicationResultAttempt deserialize(JsonParser jsonParser, 22 | DeserializationContext deserializationContext) 23 | throws IOException, JsonProcessingException { 24 | ObjectCodec oc = jsonParser.getCodec(); 25 | JsonNode node = oc.readTree(jsonParser); 26 | return new MultiplicationResultAttempt(node.get("user").get("alias").asText(), 27 | node.get("multiplication").get("factorA").asInt(), 28 | node.get("multiplication").get("factorB").asInt(), 29 | node.get("resultAttempt").asInt(), 30 | node.get("correct").asBoolean()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/dto/MultiplicationResultAttempt.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client.dto; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.ToString; 8 | import microservices.book.gamification.client.MultiplicationResultAttemptDeserializer; 9 | 10 | /** 11 | * Identifies the attempt from a user to solve a multiplication. 12 | */ 13 | @RequiredArgsConstructor 14 | @Getter 15 | @ToString 16 | @EqualsAndHashCode 17 | @JsonDeserialize(using = MultiplicationResultAttemptDeserializer.class) 18 | public final class MultiplicationResultAttempt { 19 | 20 | private final String userAlias; 21 | 22 | private final int multiplicationFactorA; 23 | private final int multiplicationFactorB; 24 | private final int resultAttempt; 25 | 26 | private final boolean correct; 27 | 28 | // Empty constructor for JSON/JPA 29 | MultiplicationResultAttempt() { 30 | userAlias = null; 31 | multiplicationFactorA = -1; 32 | multiplicationFactorB = -1; 33 | resultAttempt = -1; 34 | correct = false; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/configuration/RabbitMQConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.configuration; 2 | 3 | import org.springframework.amqp.core.Binding; 4 | import org.springframework.amqp.core.BindingBuilder; 5 | import org.springframework.amqp.core.Queue; 6 | import org.springframework.amqp.core.TopicExchange; 7 | import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer; 8 | import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.messaging.converter.MappingJackson2MessageConverter; 13 | import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; 14 | 15 | /** 16 | * Configures RabbitMQ to use events in our application. 17 | */ 18 | @Configuration 19 | public class RabbitMQConfiguration implements RabbitListenerConfigurer { 20 | 21 | @Bean 22 | public TopicExchange multiplicationExchange(@Value("${multiplication.exchange}") final String exchangeName) { 23 | return new TopicExchange(exchangeName); 24 | } 25 | 26 | @Bean 27 | public Queue gamificationMultiplicationQueue(@Value("${multiplication.queue}") final String queueName) { 28 | return new Queue(queueName, true); 29 | } 30 | 31 | @Bean 32 | Binding binding(final Queue queue, final TopicExchange exchange, 33 | @Value("${multiplication.anything.routing-key}") final String routingKey) { 34 | return BindingBuilder.bind(queue).to(exchange).with(routingKey); 35 | } 36 | 37 | @Bean 38 | public MappingJackson2MessageConverter consumerJackson2MessageConverter() { 39 | return new MappingJackson2MessageConverter(); 40 | } 41 | 42 | @Bean 43 | public DefaultMessageHandlerMethodFactory messageHandlerMethodFactory() { 44 | DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); 45 | factory.setMessageConverter(consumerJackson2MessageConverter()); 46 | return factory; 47 | } 48 | 49 | @Override 50 | public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) { 51 | registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/configuration/RestClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.configuration; 2 | 3 | import org.springframework.boot.web.client.RestTemplateBuilder; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | /** 9 | * Configures the REST client in our application 10 | */ 11 | @Configuration 12 | public class RestClientConfiguration { 13 | 14 | @Bean 15 | public RestTemplate restTemplate(RestTemplateBuilder builder) { 16 | return builder.build(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | //BOOT2 changed to interface WebMvcConfigurer instead of subclass of WebMvcConfigurerAdapter 14 | public class WebConfiguration implements WebMvcConfigurer { 15 | 16 | /** 17 | * Enables Cross-Origin Resource Sharing (CORS) 18 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 19 | * @param registry 20 | */ 21 | @Override 22 | public void addCorsMappings(final CorsRegistry registry) { 23 | registry.addMapping("/**"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/controller/AdminController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import microservices.book.gamification.service.AdminService; 4 | import org.springframework.context.annotation.Profile; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * @author moises.macero 12 | */ 13 | @Profile("test") 14 | @RestController 15 | @RequestMapping("/gamification/admin") 16 | class AdminController { 17 | 18 | private final AdminService adminService; 19 | 20 | public AdminController(final AdminService adminService) { 21 | this.adminService = adminService; 22 | } 23 | 24 | @PostMapping("/delete-db") 25 | public ResponseEntity deleteDatabase() { 26 | adminService.deleteDatabaseContents(); 27 | return ResponseEntity.ok().build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/controller/LeaderBoardController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.service.LeaderBoardService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * This class implements a REST API for the Gamification LeaderBoard service. 13 | */ 14 | @RestController 15 | @RequestMapping("/leaders") 16 | class LeaderBoardController { 17 | 18 | private final LeaderBoardService leaderBoardService; 19 | 20 | public LeaderBoardController(final LeaderBoardService leaderBoardService) { 21 | this.leaderBoardService = leaderBoardService; 22 | } 23 | 24 | @GetMapping 25 | public List getLeaderBoard() { 26 | return leaderBoardService.getCurrentLeaderBoard(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/controller/ScoreController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import microservices.book.gamification.domain.ScoreCard; 4 | import microservices.book.gamification.service.GameService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * This class implements a REST API for the Gamification User Statistics service. 12 | */ 13 | @RestController 14 | @RequestMapping("/scores") 15 | class ScoreController { 16 | 17 | private final GameService gameService; 18 | 19 | public ScoreController(final GameService gameService) { 20 | this.gameService = gameService; 21 | } 22 | 23 | @GetMapping("/{attemptId}") 24 | public ScoreCard getScoreForAttempt( 25 | @PathVariable("attemptId") final Long attemptId) { 26 | return gameService.getScoreForAttempt(attemptId); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/controller/UserStatsController.java: -------------------------------------------------------------------------------- 1 | 2 | package microservices.book.gamification.controller; 3 | 4 | import microservices.book.gamification.domain.GameStats; 5 | import microservices.book.gamification.service.GameService; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | /** 10 | * This class implements a REST API for the Gamification User Statistics service. 11 | */ 12 | @RestController 13 | @RequestMapping("/stats") 14 | class UserStatsController { 15 | 16 | private final GameService gameService; 17 | 18 | public UserStatsController(final GameService gameService) { 19 | this.gameService = gameService; 20 | } 21 | 22 | @GetMapping 23 | public GameStats getStatsForUser(@RequestParam("userId") final Long userId) { 24 | return gameService.retrieveStatsForUser(userId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/Badge.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | /** 4 | * Enumeration with the different types of Badges that a user can win. 5 | */ 6 | public enum Badge { 7 | 8 | // Badges depending on score 9 | BRONZE_MULTIPLICATOR, 10 | SILVER_MULTIPLICATOR, 11 | GOLD_MULTIPLICATOR, 12 | 13 | // Other badges won for different conditions 14 | FIRST_ATTEMPT, 15 | FIRST_WON, 16 | LUCKY_NUMBER 17 | 18 | } 19 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/BadgeCard.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * This class links a Badge to a User. Contains also a timestamp with the moment in which the user got it. 15 | */ 16 | @RequiredArgsConstructor 17 | @Getter 18 | @ToString 19 | @EqualsAndHashCode 20 | @Entity 21 | public final class BadgeCard { 22 | 23 | @Id 24 | @GeneratedValue 25 | @Column(name = "BADGE_ID") 26 | private final Long badgeId; 27 | 28 | private final Long userId; 29 | private final long badgeTimestamp; 30 | private final Badge badge; 31 | 32 | // Empty constructor for JSON / JPA 33 | public BadgeCard() { 34 | this(null, null, 0, null); 35 | } 36 | 37 | public BadgeCard(final Long userId, final Badge badge) { 38 | this(null, userId, System.currentTimeMillis(), badge); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/GameStats.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | /** 13 | * This object contains the result of one or many iterations of the game. 14 | * It may contain any combination of {@link ScoreCard} objects and {@link BadgeCard} objects. 15 | * 16 | * It can be used as a delta (as a single game iteration) or to represent the total amount of score / badges. 17 | */ 18 | @RequiredArgsConstructor 19 | @Getter 20 | @ToString 21 | @EqualsAndHashCode 22 | public final class GameStats { 23 | 24 | private final Long userId; 25 | private final int score; 26 | private final List badges; 27 | 28 | // Empty constructor for JSON / JPA 29 | public GameStats() { 30 | this.userId = 0L; 31 | this.score = 0; 32 | this.badges = new ArrayList<>(); 33 | } 34 | 35 | /** 36 | * Factory method to build an empty instance (zero points and no badges) 37 | * @param userId the user's id 38 | * @return a {@link GameStats} object with zero score and no badges 39 | */ 40 | public static GameStats emptyStats(final Long userId) { 41 | return new GameStats(userId, 0, Collections.emptyList()); 42 | } 43 | 44 | /** 45 | * @return an unmodifiable view of the badge cards list 46 | */ 47 | public List getBadges() { 48 | return Collections.unmodifiableList(badges); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/LeaderBoardRow.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | /** 9 | * Represents a line in our Leaderboard: it links a user to a total score. 10 | */ 11 | @RequiredArgsConstructor 12 | @Getter 13 | @ToString 14 | @EqualsAndHashCode 15 | public final class LeaderBoardRow { 16 | 17 | private final Long userId; 18 | private final Long totalScore; 19 | 20 | // Empty constructor for JSON / JPA 21 | public LeaderBoardRow() { 22 | this(0L, 0L); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/ScoreCard.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * This class represents the Score linked to an attempt in the game, 15 | * with an associated user and the timestamp in which the score 16 | * is registered. 17 | */ 18 | @RequiredArgsConstructor 19 | @Getter 20 | @ToString 21 | @EqualsAndHashCode 22 | @Entity 23 | public final class ScoreCard { 24 | 25 | // The default score assigned to this card, if not specified. 26 | public static final int DEFAULT_SCORE = 10; 27 | 28 | @Id 29 | @GeneratedValue 30 | @Column(name = "CARD_ID") 31 | private final Long cardId; 32 | 33 | @Column(name = "USER_ID") 34 | private final Long userId; 35 | 36 | @Column(name = "ATTEMPT_ID") 37 | private final Long attemptId; 38 | 39 | @Column(name = "SCORE_TS") 40 | private final long scoreTimestamp; 41 | 42 | @Column(name = "SCORE") 43 | private final int score; 44 | 45 | // Empty constructor for JSON / JPA 46 | public ScoreCard() { 47 | this(null, null, null, 0, 0); 48 | } 49 | 50 | public ScoreCard(final Long userId, final Long attemptId) { 51 | this(null, userId, attemptId, System.currentTimeMillis(), DEFAULT_SCORE); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/event/EventHandler.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.event; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.gamification.service.GameService; 5 | import org.springframework.amqp.AmqpRejectAndDontRequeueException; 6 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * This class receives the events and triggers the associated 11 | * business logic. 12 | */ 13 | @Slf4j 14 | @Component 15 | class EventHandler { 16 | 17 | private GameService gameService; 18 | 19 | EventHandler(final GameService gameService) { 20 | this.gameService = gameService; 21 | } 22 | 23 | @RabbitListener(queues = "${multiplication.queue}") 24 | void handleMultiplicationSolved(final MultiplicationSolvedEvent event) { 25 | log.info("Multiplication Solved Event received: {}", event.getMultiplicationResultAttemptId()); 26 | try { 27 | gameService.newAttemptForUser(event.getUserId(), 28 | event.getMultiplicationResultAttemptId(), 29 | event.isCorrect()); 30 | } catch (final Exception e) { 31 | log.error("Error when trying to process MultiplicationSolvedEvent", e); 32 | // Avoids the event to be re-queued and reprocessed. 33 | throw new AmqpRejectAndDontRequeueException(e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/event/MultiplicationSolvedEvent.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.event; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Event received when a multiplication has been solved in the system. 12 | * Provides some context information about the multiplication. 13 | */ 14 | @RequiredArgsConstructor 15 | @Getter 16 | @ToString 17 | @EqualsAndHashCode 18 | class MultiplicationSolvedEvent implements Serializable { 19 | 20 | private final Long multiplicationResultAttemptId; 21 | private final Long userId; 22 | private final boolean correct; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/repository/BadgeCardRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.repository; 2 | 3 | import microservices.book.gamification.domain.BadgeCard; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Handles data operations with BadgeCards 10 | */ 11 | public interface BadgeCardRepository extends CrudRepository { 12 | 13 | /** 14 | * Retrieves all BadgeCards for a given user. 15 | * @param userId the id of the user to look for BadgeCards 16 | * @return the list of BadgeCards, sorted by most recent. 17 | */ 18 | List findByUserIdOrderByBadgeTimestampDesc(final Long userId); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/repository/ScoreCardRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.repository; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.domain.ScoreCard; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.repository.CrudRepository; 7 | import org.springframework.data.repository.query.Param; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Handles CRUD operations with ScoreCards 13 | */ 14 | public interface ScoreCardRepository extends CrudRepository { 15 | 16 | /** 17 | * Gets the total score for a given user, being the sum of the scores of all his ScoreCards. 18 | * @param userId the id of the user for which the total score should be retrieved 19 | * @return the total score for the given user, null if the user could not be found 20 | */ 21 | @Query("SELECT SUM(s.score) FROM microservices.book.gamification.domain.ScoreCard s WHERE s.userId = :userId GROUP BY s.userId") 22 | Integer getTotalScoreForUser(@Param("userId") final Long userId); 23 | 24 | /** 25 | * Retrieves a list of {@link LeaderBoardRow}s representing the Leader Board of users and their total score. 26 | * @return the leader board, sorted by highest score first. 27 | */ 28 | @Query("SELECT NEW microservices.book.gamification.domain.LeaderBoardRow(s.userId, SUM(s.score)) " + 29 | "FROM microservices.book.gamification.domain.ScoreCard s " + 30 | "GROUP BY s.userId ORDER BY SUM(s.score) DESC") 31 | List findFirst10(); 32 | 33 | /** 34 | * Retrieves all the ScoreCards for a given user, identified by his user id. 35 | * @param userId the id of the user 36 | * @return a list containing all the ScoreCards for the given user, sorted by most recent. 37 | */ 38 | List findByUserIdOrderByScoreTimestampDesc(final Long userId); 39 | 40 | /** 41 | * Retrieves a ScoreCard using the unique id 42 | * @param attemptId the unique id of the scorecard 43 | * @return the {@link ScoreCard} object matching the id 44 | */ 45 | ScoreCard findByAttemptId(final Long attemptId); 46 | } 47 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/AdminService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | /** 4 | * This service provides methods to an administrator of the application to perform some high risk operations. 5 | * It should only be used when the application is being tested, never during runtime. 6 | * 7 | * @author moises.macero 8 | */ 9 | public interface AdminService { 10 | 11 | /** 12 | * Deletes all the database contents 13 | */ 14 | void deleteDatabaseContents(); 15 | } 16 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/AdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.repository.BadgeCardRepository; 4 | import microservices.book.gamification.repository.ScoreCardRepository; 5 | import org.springframework.context.annotation.Profile; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Profile("test") 12 | @Service 13 | class AdminServiceImpl implements AdminService { 14 | 15 | private BadgeCardRepository badgeCardRepository; 16 | private ScoreCardRepository scoreCardRepository; 17 | 18 | public AdminServiceImpl(final BadgeCardRepository badgeCardRepository, 19 | final ScoreCardRepository scoreCardRepository) { 20 | this.badgeCardRepository = badgeCardRepository; 21 | this.scoreCardRepository = scoreCardRepository; 22 | } 23 | 24 | @Override 25 | public void deleteDatabaseContents() { 26 | scoreCardRepository.deleteAll(); 27 | badgeCardRepository.deleteAll(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/GameService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.GameStats; 4 | import microservices.book.gamification.domain.ScoreCard; 5 | 6 | /** 7 | * This service includes the main logic for gamifying the system. 8 | */ 9 | public interface GameService { 10 | 11 | /** 12 | * Process a new attempt from a given user. 13 | * 14 | * @param userId the user's unique id 15 | * @param attemptId the attempt id, can be used to retrieve extra data if needed 16 | * @param correct indicates if the attempt was correct 17 | * 18 | * @return a {@link GameStats} object containing the new score and badge cards obtained 19 | */ 20 | GameStats newAttemptForUser(Long userId, Long attemptId, boolean correct); 21 | 22 | /** 23 | * Gets the game statistics for a given user 24 | * @param userId the user 25 | * @return the total statistics for that user 26 | */ 27 | GameStats retrieveStatsForUser(Long userId); 28 | 29 | /** 30 | * Gets the score for a given attempt 31 | * @param attemptId the attempt unique id 32 | * @return a {@link ScoreCard} with the details of the score for that attempt 33 | */ 34 | ScoreCard getScoreForAttempt(Long attemptId); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/GameServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.gamification.client.MultiplicationResultAttemptClient; 5 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 6 | import microservices.book.gamification.domain.*; 7 | import microservices.book.gamification.repository.BadgeCardRepository; 8 | import microservices.book.gamification.repository.ScoreCardRepository; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Optional; 15 | import java.util.stream.Collectors; 16 | 17 | /** 18 | * @author moises.macero 19 | */ 20 | @Service 21 | @Slf4j 22 | class GameServiceImpl implements GameService { 23 | 24 | public static final int LUCKY_NUMBER = 42; 25 | 26 | private ScoreCardRepository scoreCardRepository; 27 | private BadgeCardRepository badgeCardRepository; 28 | private MultiplicationResultAttemptClient attemptClient; 29 | 30 | GameServiceImpl(ScoreCardRepository scoreCardRepository, 31 | BadgeCardRepository badgeCardRepository, 32 | MultiplicationResultAttemptClient attemptClient) { 33 | this.scoreCardRepository = scoreCardRepository; 34 | this.badgeCardRepository = badgeCardRepository; 35 | this.attemptClient = attemptClient; 36 | } 37 | 38 | @Override 39 | public GameStats newAttemptForUser(final Long userId, 40 | final Long attemptId, 41 | final boolean correct) { 42 | // For the first version we'll give points only if it's correct 43 | if(correct) { 44 | ScoreCard scoreCard = new ScoreCard(userId, attemptId); 45 | scoreCardRepository.save(scoreCard); 46 | log.info("User with id {} scored {} points for attempt id {}", 47 | userId, scoreCard.getScore(), attemptId); 48 | List badgeCards = processForBadges(userId, attemptId); 49 | return new GameStats(userId, scoreCard.getScore(), 50 | badgeCards.stream().map(BadgeCard::getBadge) 51 | .collect(Collectors.toList())); 52 | } 53 | return GameStats.emptyStats(userId); 54 | } 55 | 56 | /** 57 | * Checks the total score and the different score cards obtained 58 | * to give new badges in case their conditions are met. 59 | */ 60 | private List processForBadges(final Long userId, 61 | final Long attemptId) { 62 | List badgeCards = new ArrayList<>(); 63 | 64 | int totalScore = scoreCardRepository.getTotalScoreForUser(userId); 65 | log.info("New score for user {} is {}", userId, totalScore); 66 | 67 | List scoreCardList = scoreCardRepository 68 | .findByUserIdOrderByScoreTimestampDesc(userId); 69 | List badgeCardList = badgeCardRepository 70 | .findByUserIdOrderByBadgeTimestampDesc(userId); 71 | 72 | // Badges depending on score 73 | checkAndGiveBadgeBasedOnScore(badgeCardList, 74 | Badge.BRONZE_MULTIPLICATOR, totalScore, 100, userId) 75 | .ifPresent(badgeCards::add); 76 | checkAndGiveBadgeBasedOnScore(badgeCardList, 77 | Badge.SILVER_MULTIPLICATOR, totalScore, 500, userId) 78 | .ifPresent(badgeCards::add); 79 | checkAndGiveBadgeBasedOnScore(badgeCardList, 80 | Badge.GOLD_MULTIPLICATOR, totalScore, 999, userId) 81 | .ifPresent(badgeCards::add); 82 | 83 | // First won badge 84 | if(scoreCardList.size() == 1 && 85 | !containsBadge(badgeCardList, Badge.FIRST_WON)) { 86 | BadgeCard firstWonBadge = giveBadgeToUser(Badge.FIRST_WON, userId); 87 | badgeCards.add(firstWonBadge); 88 | } 89 | 90 | // Lucky number badge 91 | MultiplicationResultAttempt attempt = attemptClient 92 | .retrieveMultiplicationResultAttemptbyId(attemptId); 93 | if(!containsBadge(badgeCardList, Badge.LUCKY_NUMBER) && 94 | (LUCKY_NUMBER == attempt.getMultiplicationFactorA() || 95 | LUCKY_NUMBER == attempt.getMultiplicationFactorB())) { 96 | BadgeCard luckyNumberBadge = giveBadgeToUser( 97 | Badge.LUCKY_NUMBER, userId); 98 | badgeCards.add(luckyNumberBadge); 99 | } 100 | 101 | return badgeCards; 102 | } 103 | 104 | @Override 105 | public GameStats retrieveStatsForUser(final Long userId) { 106 | Integer score = scoreCardRepository.getTotalScoreForUser(userId); 107 | // If the user does not exist yet, it means it has 0 score 108 | if(score == null) { 109 | return new GameStats(userId, 0, Collections.emptyList()); 110 | } 111 | List badgeCards = badgeCardRepository 112 | .findByUserIdOrderByBadgeTimestampDesc(userId); 113 | return new GameStats(userId, score, badgeCards.stream() 114 | .map(BadgeCard::getBadge).collect(Collectors.toList())); 115 | } 116 | 117 | @Override 118 | public ScoreCard getScoreForAttempt(final Long attemptId) { 119 | return scoreCardRepository.findByAttemptId(attemptId); 120 | } 121 | 122 | /** 123 | * Convenience method to check the current score against 124 | * the different thresholds to gain badges. 125 | * It also assigns badge to user if the conditions are met. 126 | */ 127 | private Optional checkAndGiveBadgeBasedOnScore( 128 | final List badgeCards, final Badge badge, 129 | final int score, final int scoreThreshold, final Long userId) { 130 | if(score >= scoreThreshold && !containsBadge(badgeCards, badge)) { 131 | return Optional.of(giveBadgeToUser(badge, userId)); 132 | } 133 | return Optional.empty(); 134 | } 135 | 136 | /** 137 | * Checks if the passed list of badges includes the one being checked 138 | */ 139 | private boolean containsBadge(final List badgeCards, 140 | final Badge badge) { 141 | return badgeCards.stream().anyMatch(b -> b.getBadge().equals(badge)); 142 | } 143 | 144 | /** 145 | * Assigns a new badge to the given user 146 | */ 147 | private BadgeCard giveBadgeToUser(final Badge badge, final Long userId) { 148 | BadgeCard badgeCard = new BadgeCard(userId, badge); 149 | badgeCardRepository.save(badgeCard); 150 | log.info("User with id {} won a new badge: {}", userId, badge); 151 | return badgeCard; 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/LeaderBoardService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Provides methods to access the LeaderBoard with users and scores. 9 | */ 10 | public interface LeaderBoardService { 11 | 12 | /** 13 | * Retrieves the current leader board with the top score users 14 | * @return the users with the highest score 15 | */ 16 | List getCurrentLeaderBoard(); 17 | } 18 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/LeaderBoardServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.repository.ScoreCardRepository; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.List; 8 | 9 | @Service 10 | class LeaderBoardServiceImpl implements LeaderBoardService { 11 | 12 | private ScoreCardRepository scoreCardRepository; 13 | 14 | LeaderBoardServiceImpl(ScoreCardRepository scoreCardRepository) { 15 | this.scoreCardRepository = scoreCardRepository; 16 | } 17 | 18 | @Override 19 | public List getCurrentLeaderBoard() { 20 | return scoreCardRepository.findFirst10(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gamification/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:h2:file:~/gamification-test;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE -------------------------------------------------------------------------------- /gamification/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8081 2 | 3 | # Gives us access to the H2 database web console 4 | spring.h2.console.enabled=true 5 | # Generates the database *only* if it's not there yet 6 | spring.jpa.hibernate.ddl-auto=update 7 | # Creates the database in a file 8 | spring.datasource.url=jdbc:h2:file:~/gamification;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE 9 | # For educational purposes we will show the SQL in console 10 | spring.jpa.properties.hibernate.show_sql=true 11 | 12 | ## RabbitMQ configuration 13 | multiplication.exchange=multiplication_exchange 14 | multiplication.solved.key=multiplication.solved 15 | multiplication.queue=gamification_multiplication_queue 16 | multiplication.anything.routing-key=multiplication.* 17 | 18 | # REST client settings 19 | multiplicationHost=http://localhost:8000/api 20 | 21 | # Service Discovery configuration 22 | eureka.client.service-url.default-zone=http://localhost:8761/eureka/ 23 | 24 | endpoints.actuator.enabled=true -------------------------------------------------------------------------------- /gamification/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=gamification -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/controller/AdminControllerDisabledTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import microservices.book.gamification.service.AdminService; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.mock.web.MockHttpServletResponse; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.mockito.Mockito.verifyZeroInteractions; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 18 | 19 | /** 20 | * @author moises.macero 21 | */ 22 | @RunWith(SpringRunner.class) 23 | @WebMvcTest(AdminController.class) 24 | public class AdminControllerDisabledTest { 25 | 26 | @MockBean 27 | private AdminService adminService; 28 | 29 | @Autowired 30 | private MockMvc mvc; 31 | 32 | /** 33 | * This test checks that the controller is NOT ACCESSIBLE 34 | * when profile is not set to test 35 | * 36 | * @throws Exception if any error occurs 37 | */ 38 | @Test 39 | public void deleteDatabaseTest() throws Exception { 40 | // when 41 | MockHttpServletResponse response = mvc.perform( 42 | post("/gamification/admin/delete-db") 43 | .accept(MediaType.APPLICATION_JSON)) 44 | .andReturn().getResponse(); 45 | 46 | // then 47 | assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); 48 | verifyZeroInteractions(adminService); 49 | } 50 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/controller/AdminControllerEnabledTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import microservices.book.gamification.service.AdminService; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.mock.web.MockHttpServletResponse; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | import org.springframework.test.web.servlet.MockMvc; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.mockito.Mockito.verify; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 19 | 20 | /** 21 | * @author moises.macero 22 | */ 23 | @RunWith(SpringRunner.class) 24 | @ActiveProfiles(profiles = "test") 25 | @WebMvcTest(AdminController.class) 26 | public class AdminControllerEnabledTest { 27 | 28 | @MockBean 29 | private AdminService adminService; 30 | 31 | @Autowired 32 | private MockMvc mvc; 33 | 34 | /** 35 | * This test checks that the controller is working as expected when 36 | * the profile is set to test (see annotation in class declaration) 37 | * @throws Exception if any error occurs 38 | */ 39 | @Test 40 | public void deleteDatabaseTest() throws Exception { 41 | // when 42 | MockHttpServletResponse response = mvc.perform( 43 | post("/gamification/admin/delete-db") 44 | .accept(MediaType.APPLICATION_JSON)) 45 | .andReturn().getResponse(); 46 | 47 | // then 48 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 49 | verify(adminService).deleteDatabaseContents(); 50 | } 51 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/controller/LeaderBoardControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.gamification.domain.LeaderBoardRow; 5 | import microservices.book.gamification.service.LeaderBoardService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 11 | import org.springframework.boot.test.json.JacksonTester; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.mock.web.MockHttpServletResponse; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | import static org.mockito.BDDMockito.given; 25 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 26 | 27 | /** 28 | * @author moises.macero 29 | */ 30 | @RunWith(SpringRunner.class) 31 | @WebMvcTest(LeaderBoardController.class) 32 | public class LeaderBoardControllerTest { 33 | 34 | @MockBean 35 | private LeaderBoardService leaderBoardService; 36 | 37 | @Autowired 38 | private MockMvc mvc; 39 | 40 | private JacksonTester> json; 41 | 42 | @Before 43 | public void setup() { 44 | JacksonTester.initFields(this, new ObjectMapper()); 45 | } 46 | 47 | @Test 48 | public void getLeaderBoardTest() throws Exception{ 49 | // given 50 | LeaderBoardRow leaderBoardRow1 = new LeaderBoardRow(1L, 500L); 51 | LeaderBoardRow leaderBoardRow2 = new LeaderBoardRow(2L, 400L); 52 | List leaderBoard = new ArrayList<>(); 53 | Collections.addAll(leaderBoard, leaderBoardRow1, leaderBoardRow2); 54 | given(leaderBoardService.getCurrentLeaderBoard()).willReturn(leaderBoard); 55 | 56 | // when 57 | MockHttpServletResponse response = mvc.perform( 58 | get("/leaders") 59 | .accept(MediaType.APPLICATION_JSON)) 60 | .andReturn().getResponse(); 61 | 62 | // then 63 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 64 | assertThat(response.getContentAsString()) 65 | .isEqualTo(json.write(leaderBoard).getJson()); 66 | } 67 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/controller/ScoreControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.gamification.domain.ScoreCard; 5 | import microservices.book.gamification.service.GameService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 11 | import org.springframework.boot.test.json.JacksonTester; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.mock.web.MockHttpServletResponse; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.mockito.BDDMockito.given; 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 22 | 23 | /** 24 | * @author moises.macero 25 | */ 26 | @RunWith(SpringRunner.class) 27 | @WebMvcTest(ScoreController.class) 28 | public class ScoreControllerTest { 29 | 30 | @MockBean 31 | private GameService gameService; 32 | 33 | @Autowired 34 | private MockMvc mvc; 35 | 36 | private JacksonTester json; 37 | 38 | @Before 39 | public void setup() { 40 | JacksonTester.initFields(this, new ObjectMapper()); 41 | } 42 | 43 | @Test 44 | public void getScoreForAttemptTest() throws Exception { 45 | // given 46 | ScoreCard scoreCard = new ScoreCard(1L, 5L, 10L, System.currentTimeMillis(), 100); 47 | given(gameService.getScoreForAttempt(10L)).willReturn(scoreCard); 48 | 49 | // when 50 | MockHttpServletResponse response = mvc.perform( 51 | get("/scores/10") 52 | .accept(MediaType.APPLICATION_JSON)) 53 | .andReturn().getResponse(); 54 | 55 | // then 56 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 57 | assertThat(response.getContentAsString()) 58 | .isEqualTo(json.write(scoreCard).getJson()); 59 | } 60 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/controller/UserStatsControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.gamification.domain.Badge; 5 | import microservices.book.gamification.domain.GameStats; 6 | import microservices.book.gamification.service.GameService; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.json.JacksonTester; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.mock.web.MockHttpServletResponse; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | 20 | import java.util.Collections; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | import static org.mockito.BDDMockito.given; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 25 | 26 | /** 27 | * @author moises.macero 28 | */ 29 | @RunWith(SpringRunner.class) 30 | @WebMvcTest(UserStatsController.class) 31 | public class UserStatsControllerTest { 32 | 33 | @MockBean 34 | private GameService gameService; 35 | 36 | @Autowired 37 | private MockMvc mvc; 38 | 39 | private JacksonTester json; 40 | 41 | @Before 42 | public void setup() { 43 | JacksonTester.initFields(this, new ObjectMapper()); 44 | } 45 | 46 | @Test 47 | public void getUserStatsTest() throws Exception{ 48 | // given 49 | GameStats gameStats = new GameStats(1L, 2000, Collections.singletonList(Badge.GOLD_MULTIPLICATOR)); 50 | given(gameService.retrieveStatsForUser(1L)).willReturn(gameStats); 51 | 52 | // when 53 | MockHttpServletResponse response = mvc.perform( 54 | get("/stats?userId=1") 55 | .accept(MediaType.APPLICATION_JSON)) 56 | .andReturn().getResponse(); 57 | 58 | // then 59 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 60 | assertThat(response.getContentAsString()) 61 | .isEqualTo(json.write(gameStats).getJson()); 62 | } 63 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/event/EventHandlerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.event; 2 | 3 | import microservices.book.gamification.domain.GameStats; 4 | import microservices.book.gamification.service.GameService; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import static org.mockito.BDDMockito.given; 11 | import static org.mockito.Mockito.verify; 12 | 13 | /** 14 | * @author moises.macero 15 | */ 16 | public class EventHandlerTest { 17 | 18 | private EventHandler eventHandler; 19 | 20 | @Mock 21 | private GameService gameService; 22 | 23 | @Before 24 | public void setUp() { 25 | MockitoAnnotations.initMocks(this); 26 | eventHandler = new EventHandler(gameService); 27 | } 28 | 29 | @Test 30 | public void multiplicationAttemptReceivedTest() { 31 | // given 32 | Long userId = 1L; 33 | Long attemptId = 8L; 34 | boolean correct = true; 35 | GameStats gameStatsExpected = new GameStats(); 36 | MultiplicationSolvedEvent event = new MultiplicationSolvedEvent(attemptId, userId, correct); 37 | given(gameService.newAttemptForUser(userId, attemptId, correct)).willReturn(gameStatsExpected); 38 | 39 | // when 40 | eventHandler.handleMultiplicationSolved(event); 41 | 42 | // then 43 | verify(gameService).newAttemptForUser(userId, attemptId, correct); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/service/LeaderBoardServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.repository.ScoreCardRepository; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.mockito.BDDMockito.given; 15 | 16 | /** 17 | * @author moises.macero 18 | */ 19 | public class LeaderBoardServiceImplTest { 20 | 21 | private LeaderBoardServiceImpl leaderBoardService; 22 | 23 | @Mock 24 | private ScoreCardRepository scoreCardRepository; 25 | 26 | @Before 27 | public void setUp() { 28 | MockitoAnnotations.initMocks(this); 29 | leaderBoardService = new LeaderBoardServiceImpl(scoreCardRepository); 30 | } 31 | 32 | @Test 33 | public void retrieveLeaderBoardTest() { 34 | // given 35 | Long userId = 1L; 36 | LeaderBoardRow leaderRow1 = new LeaderBoardRow(userId, 300L); 37 | List expectedLeaderBoard = Collections.singletonList(leaderRow1); 38 | given(scoreCardRepository.findFirst10()).willReturn(expectedLeaderBoard); 39 | 40 | // when 41 | List leaderBoard = leaderBoardService.getCurrentLeaderBoard(); 42 | 43 | // then 44 | assertThat(leaderBoard).isEqualTo(expectedLeaderBoard); 45 | } 46 | } -------------------------------------------------------------------------------- /gateway/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /gateway/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/gateway/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /gateway/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /gateway/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /gateway/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | gateway-v10 8 | 0.10.0-SNAPSHOT 9 | jar 10 | 11 | gateway-v10 12 | Multiplication App - Gateway (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.0.M2 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | Finchley.M2 25 | 26 | 27 | 28 | 29 | org.springframework.cloud 30 | spring-cloud-starter-zuul 31 | 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-eureka 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-starter-ribbon 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-actuator 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-test 51 | test 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.cloud 59 | spring-cloud-dependencies 60 | ${spring-cloud.version} 61 | pom 62 | import 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-maven-plugin 72 | 73 | 74 | 75 | 76 | 77 | 78 | spring-snapshots 79 | Spring Snapshots 80 | https://repo.spring.io/snapshot 81 | 82 | true 83 | 84 | 85 | 86 | spring-milestones 87 | Spring Milestones 88 | https://repo.spring.io/milestone 89 | 90 | false 91 | 92 | 93 | 94 | 95 | 96 | 97 | spring-snapshots 98 | Spring Snapshots 99 | https://repo.spring.io/snapshot 100 | 101 | true 102 | 103 | 104 | 105 | spring-milestones 106 | Spring Milestones 107 | https://repo.spring.io/milestone 108 | 109 | false 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway; 2 | 3 | import microservices.book.gateway.configuration.RibbonConfiguration; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | import org.springframework.cloud.netflix.ribbon.RibbonClients; 8 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 9 | 10 | @EnableZuulProxy 11 | @EnableEurekaClient 12 | @RibbonClients(defaultConfiguration = RibbonConfiguration.class) 13 | @SpringBootApplication 14 | public class GatewayApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(GatewayApplication.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/configuration/HystrixFallbackConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway.configuration; 2 | 3 | import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.client.ClientHttpResponse; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | 15 | /** 16 | * @author moises.macero 17 | */ 18 | @Configuration 19 | public class HystrixFallbackConfiguration { 20 | 21 | @Bean 22 | public ZuulFallbackProvider zuulFallbackProvider() { 23 | return new ZuulFallbackProvider() { 24 | 25 | @Override 26 | public String getRoute() { 27 | // Might be confusing: it's the serviceId property and not the route 28 | return "multiplication"; 29 | } 30 | 31 | @Override 32 | public ClientHttpResponse fallbackResponse() { 33 | return new ClientHttpResponse() { 34 | @Override 35 | public HttpStatus getStatusCode() throws IOException { 36 | return HttpStatus.OK; 37 | } 38 | 39 | @Override 40 | public int getRawStatusCode() throws IOException { 41 | return HttpStatus.OK.value(); 42 | } 43 | 44 | @Override 45 | public String getStatusText() throws IOException { 46 | return HttpStatus.OK.toString(); 47 | } 48 | 49 | @Override 50 | public void close() {} 51 | 52 | @Override 53 | public InputStream getBody() throws IOException { 54 | return new ByteArrayInputStream("{\"factorA\":\"Sorry, Service is Down!\",\"factorB\":\"?\",\"id\":null}".getBytes()); 55 | } 56 | 57 | @Override 58 | public HttpHeaders getHeaders() { 59 | HttpHeaders headers = new HttpHeaders(); 60 | headers.setContentType(MediaType.APPLICATION_JSON); 61 | headers.setAccessControlAllowCredentials(true); 62 | headers.setAccessControlAllowOrigin("*"); 63 | return headers; 64 | } 65 | }; 66 | } 67 | }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/configuration/RibbonConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway.configuration; 2 | 3 | import com.netflix.client.config.IClientConfig; 4 | import com.netflix.loadbalancer.*; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | /** 8 | * @author moises.macero 9 | */ 10 | public class RibbonConfiguration { 11 | 12 | @Bean 13 | public IPing ribbonPing(final IClientConfig config) { 14 | // BOOT2: changed from /health to /application/health 15 | return new PingUrl(false,"/application/health"); 16 | } 17 | 18 | @Bean 19 | public IRule ribbonRule(final IClientConfig config) { 20 | return new AvailabilityFilteringRule(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | //BOOT2 changed to interface WebMvcConfigurer instead of subclass of WebMvcConfigurerAdapter 14 | public class WebConfiguration implements WebMvcConfigurer { 15 | 16 | /** 17 | * Enables Cross-Origin Resource Sharing (CORS) 18 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 19 | * @param registry 20 | */ 21 | @Override 22 | public void addCorsMappings(final CorsRegistry registry) { 23 | registry.addMapping("/**"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | 4 | zuul: 5 | ignoredServices: '*' 6 | prefix: /api 7 | routes: 8 | multiplications: 9 | path: /multiplications/** 10 | serviceId: multiplication 11 | strip-prefix: false 12 | results: 13 | path: /results/** 14 | serviceId: multiplication 15 | strip-prefix: false 16 | users: 17 | path: /users/** 18 | serviceId: multiplication 19 | strip-prefix: false 20 | leaders: 21 | path: /leaders/** 22 | serviceId: gamification 23 | strip-prefix: false 24 | scores: 25 | path: /scores/** 26 | serviceId: gamification 27 | strip-prefix: false 28 | stats: 29 | path: /stats/** 30 | serviceId: gamification 31 | strip-prefix: false 32 | 33 | endpoints: 34 | routes: 35 | sensitive: false 36 | trace: 37 | sensitive: false 38 | 39 | eureka: 40 | client: 41 | service-url: 42 | default-zone: http://localhost:8761/eureka/ 43 | 44 | 45 | --- 46 | # Adds admin routes for testing purposes 47 | spring: 48 | profiles: test 49 | zuul: 50 | routes: 51 | gamification-admin: 52 | path: /gamification/admin/** 53 | serviceId: gamification 54 | strip-prefix: false 55 | multiplication-admin: 56 | path: /multiplication/admin/** 57 | serviceId: multiplication 58 | strip-prefix: false 59 | 60 | -------------------------------------------------------------------------------- /gateway/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: gateway -------------------------------------------------------------------------------- /gateway/src/test/java/microservices/book/gateway/GatewayApplicationTests.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class GatewayApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /resources/logical_view_v8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/resources/logical_view_v8.png -------------------------------------------------------------------------------- /service-registry/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /service-registry/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/service-registry/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /service-registry/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /service-registry/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /service-registry/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /service-registry/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | service-registry-v10 8 | 0.10.0-SNAPSHOT 9 | jar 10 | 11 | service-registry-v10 12 | Multiplication App - Service Registry (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.0.M2 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | Finchley.M2 25 | 26 | 27 | 28 | 29 | org.springframework.cloud 30 | spring-cloud-starter-eureka-server 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-actuator 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.springframework.cloud 49 | spring-cloud-dependencies 50 | ${spring-cloud.version} 51 | pom 52 | import 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-maven-plugin 62 | 63 | 64 | 65 | 66 | 67 | 68 | spring-snapshots 69 | Spring Snapshots 70 | https://repo.spring.io/snapshot 71 | 72 | true 73 | 74 | 75 | 76 | spring-milestones 77 | Spring Milestones 78 | https://repo.spring.io/milestone 79 | 80 | false 81 | 82 | 83 | 84 | 85 | 86 | 87 | spring-snapshots 88 | Spring Snapshots 89 | https://repo.spring.io/snapshot 90 | 91 | true 92 | 93 | 94 | 95 | spring-milestones 96 | Spring Milestones 97 | https://repo.spring.io/milestone 98 | 99 | false 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /service-registry/src/main/java/microservices/book/serviceregistry/ServiceRegistryApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book.serviceregistry; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class ServiceRegistryApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ServiceRegistryApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /service-registry/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8761 -------------------------------------------------------------------------------- /service-registry/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=service-registry -------------------------------------------------------------------------------- /service-registry/src/test/java/microservices/book/serviceregistry/ServiceRegistryApplicationTests.java: -------------------------------------------------------------------------------- 1 | package microservices.book.serviceregistry; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ServiceRegistryApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /social-multiplication/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /social-multiplication/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/social-multiplication/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /social-multiplication/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /social-multiplication/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /social-multiplication/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | social-multiplication-v10 8 | 0.10.0-SNAPSHOT 9 | jar 10 | 11 | social-multiplication-v10 12 | Social Multiplication App (Learn Microservices with Spring Boot) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.0.M2 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | Finchley.M2 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-dependencies 32 | ${spring-cloud.version} 33 | pom 34 | import 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-amqp 48 | 49 | 50 | 51 | org.springframework.cloud 52 | spring-cloud-starter-eureka 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-actuator 58 | 59 | 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | 1.16.18 65 | provided 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-data-jpa 71 | 72 | 73 | 74 | com.h2database 75 | h2 76 | runtime 77 | 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-test 83 | test 84 | 85 | 86 | 87 | 88 | 89 | 90 | org.springframework.boot 91 | spring-boot-maven-plugin 92 | 93 | 94 | 95 | 96 | 97 | 98 | spring-snapshots 99 | Spring Snapshots 100 | https://repo.spring.io/snapshot 101 | 102 | true 103 | 104 | 105 | 106 | spring-milestones 107 | Spring Milestones 108 | https://repo.spring.io/milestone 109 | 110 | false 111 | 112 | 113 | 114 | 115 | 116 | 117 | spring-snapshots 118 | Spring Snapshots 119 | https://repo.spring.io/snapshot 120 | 121 | true 122 | 123 | 124 | 125 | spring-milestones 126 | Spring Milestones 127 | https://repo.spring.io/milestone 128 | 129 | false 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/SocialMultiplicationApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | 8 | @EnableEurekaClient 9 | @SpringBootApplication 10 | public class SocialMultiplicationApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(SocialMultiplicationApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/configuration/RabbitMQConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.configuration; 2 | 3 | import org.springframework.amqp.core.TopicExchange; 4 | import org.springframework.amqp.rabbit.connection.ConnectionFactory; 5 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 6 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * Configures RabbitMQ to use events in our application. 13 | */ 14 | @Configuration 15 | public class RabbitMQConfiguration { 16 | 17 | @Bean 18 | public TopicExchange multiplicationExchange(@Value("${multiplication.exchange}") final String exchangeName) { 19 | return new TopicExchange(exchangeName); 20 | } 21 | 22 | @Bean 23 | public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) { 24 | final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); 25 | rabbitTemplate.setMessageConverter(producerJackson2MessageConverter()); 26 | return rabbitTemplate; 27 | } 28 | 29 | @Bean 30 | public Jackson2JsonMessageConverter producerJackson2MessageConverter() { 31 | return new Jackson2JsonMessageConverter(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | //BOOT2 changed to interface WebMvcConfigurer instead of subclass of WebMvcConfigurerAdapter 14 | public class WebConfiguration implements WebMvcConfigurer { 15 | 16 | /** 17 | * Enables Cross-Origin Resource Sharing (CORS) 18 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 19 | * @param registry 20 | */ 21 | @Override 22 | public void addCorsMappings(final CorsRegistry registry) { 23 | registry.addMapping("/**"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/controller/AdminController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import microservices.book.multiplication.service.AdminService; 4 | import org.springframework.context.annotation.Profile; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * @author moises.macero 12 | */ 13 | @Profile("test") 14 | @RestController 15 | @RequestMapping("/multiplication/admin") 16 | public class AdminController { 17 | 18 | private final AdminService adminService; 19 | 20 | public AdminController(AdminService adminService) { 21 | this.adminService = adminService; 22 | } 23 | 24 | @PostMapping("/delete-db") 25 | public ResponseEntity deleteDatabase() { 26 | adminService.deleteDatabaseContents(); 27 | return ResponseEntity.ok().build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/controller/MultiplicationController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.multiplication.domain.Multiplication; 5 | import microservices.book.multiplication.service.MultiplicationService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | /** 13 | * This class implements a REST API for our Multiplication application. 14 | */ 15 | @Slf4j 16 | @RestController 17 | @RequestMapping("/multiplications") 18 | final class MultiplicationController { 19 | 20 | private final MultiplicationService multiplicationService; 21 | 22 | private final int serverPort; 23 | 24 | @Autowired 25 | public MultiplicationController(final MultiplicationService multiplicationService, @Value("${server.port}") int serverPort) { 26 | this.multiplicationService = multiplicationService; 27 | this.serverPort = serverPort; 28 | } 29 | 30 | @GetMapping("/random") 31 | Multiplication getRandomMultiplication() { 32 | log.info("Generating a random multiplication from server @ {}", serverPort); 33 | return multiplicationService.createRandomMultiplication(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/controller/MultiplicationResultAttemptController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | import microservices.book.multiplication.service.MultiplicationService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * This class provides a REST API to POST the attempts from users. 15 | */ 16 | @Slf4j 17 | @RestController 18 | @RequestMapping("/results") 19 | final class MultiplicationResultAttemptController { 20 | 21 | private final MultiplicationService multiplicationService; 22 | 23 | private final int serverPort; 24 | 25 | @Autowired 26 | MultiplicationResultAttemptController( 27 | final MultiplicationService multiplicationService, 28 | @Value("${server.port}") int serverPort) { 29 | this.multiplicationService = multiplicationService; 30 | this.serverPort = serverPort; 31 | } 32 | 33 | @PostMapping 34 | ResponseEntity postResult(@RequestBody MultiplicationResultAttempt multiplicationResultAttempt) { 35 | return ResponseEntity.ok(multiplicationService.checkAttempt(multiplicationResultAttempt)); 36 | } 37 | 38 | @GetMapping 39 | ResponseEntity> getStatistics(@RequestParam("alias") String alias) { 40 | return ResponseEntity.ok( 41 | multiplicationService.getStatsForUser(alias) 42 | ); 43 | } 44 | 45 | @GetMapping("/{resultId}") 46 | ResponseEntity getResultById(final @PathVariable("resultId") Long resultId) { 47 | log.info("Retrieving result {} from server @ {}", resultId, serverPort); 48 | return ResponseEntity.ok( 49 | multiplicationService.getResultById(resultId) 50 | ); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import microservices.book.multiplication.domain.User; 4 | import microservices.book.multiplication.repository.UserRepository; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | /** 11 | * @author moises.macero 12 | */ 13 | @RestController 14 | @RequestMapping("/users") 15 | public class UserController { 16 | 17 | private final UserRepository userRepository; 18 | 19 | public UserController(UserRepository userRepository) { 20 | this.userRepository = userRepository; 21 | } 22 | 23 | @GetMapping("/{userId}") 24 | public User getUserById(@PathVariable("userId") final Long userId){ 25 | // BOOT2: changed from findOne 26 | return userRepository.findById(userId) 27 | .orElseThrow(() -> new IllegalArgumentException( 28 | "The requested userId [" + userId + 29 | "] does not exist.")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/domain/Multiplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * This class represents a Multiplication (a * b). 15 | */ 16 | @RequiredArgsConstructor 17 | @Getter 18 | @ToString 19 | @EqualsAndHashCode 20 | @Entity 21 | public final class Multiplication { 22 | 23 | @Id 24 | @GeneratedValue 25 | @Column(name = "MULTIPLICATION_ID") 26 | private Long id; 27 | 28 | // Both factors 29 | private final int factorA; 30 | private final int factorB; 31 | 32 | // Empty constructor for JSON/JPA 33 | Multiplication() { 34 | this(0, 0); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/domain/MultiplicationResultAttempt.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.*; 9 | 10 | /** 11 | * Identifies the attempt from a {@link User} to solve a 12 | * {@link Multiplication}. 13 | */ 14 | @RequiredArgsConstructor 15 | @Getter 16 | @ToString 17 | @EqualsAndHashCode 18 | @Entity 19 | public final class MultiplicationResultAttempt { 20 | 21 | @Id 22 | @GeneratedValue 23 | private Long id; 24 | 25 | @ManyToOne(cascade = CascadeType.PERSIST) 26 | @JoinColumn(name = "USER_ID") 27 | private final User user; 28 | 29 | @ManyToOne(cascade = CascadeType.PERSIST) 30 | @JoinColumn(name = "MULTIPLICATION_ID") 31 | private final Multiplication multiplication; 32 | private final int resultAttempt; 33 | 34 | private final boolean correct; 35 | 36 | // Empty constructor for JSON/JPA 37 | MultiplicationResultAttempt() { 38 | user = null; 39 | multiplication = null; 40 | resultAttempt = -1; 41 | correct = false; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/domain/User.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * Stores information to identify the user. 15 | */ 16 | @RequiredArgsConstructor 17 | @Getter 18 | @ToString 19 | @EqualsAndHashCode 20 | @Entity 21 | public final class User { 22 | 23 | @Id 24 | @GeneratedValue 25 | @Column(name = "USER_ID") 26 | private Long id; 27 | 28 | private final String alias; 29 | 30 | // Empty constructor for JSON/JPA 31 | protected User() { 32 | alias = null; 33 | } 34 | 35 | public User(final long userId, final String userAlias) { 36 | this.id = userId; 37 | this.alias = userAlias; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/event/EventDispatcher.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.event; 2 | 3 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Handles the communication with the Event Bus. 10 | */ 11 | @Component 12 | public class EventDispatcher { 13 | 14 | private RabbitTemplate rabbitTemplate; 15 | 16 | // The exchange to use to send anything related to Multiplication 17 | private String multiplicationExchange; 18 | 19 | // The routing key to use to send this particular event 20 | private String multiplicationSolvedRoutingKey; 21 | 22 | @Autowired 23 | EventDispatcher(final RabbitTemplate rabbitTemplate, 24 | @Value("${multiplication.exchange}") final String multiplicationExchange, 25 | @Value("${multiplication.solved.key}") final String multiplicationSolvedRoutingKey) { 26 | this.rabbitTemplate = rabbitTemplate; 27 | this.multiplicationExchange = multiplicationExchange; 28 | this.multiplicationSolvedRoutingKey = multiplicationSolvedRoutingKey; 29 | } 30 | 31 | public void send(final MultiplicationSolvedEvent multiplicationSolvedEvent) { 32 | rabbitTemplate.convertAndSend( 33 | multiplicationExchange, 34 | multiplicationSolvedRoutingKey, 35 | multiplicationSolvedEvent); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/event/MultiplicationSolvedEvent.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.event; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Event that models the fact that a {@link microservices.book.multiplication.domain.Multiplication} 12 | * has been solved in the system. Provides some context information about the multiplication. 13 | */ 14 | @RequiredArgsConstructor 15 | @Getter 16 | @ToString 17 | @EqualsAndHashCode 18 | public class MultiplicationSolvedEvent implements Serializable { 19 | 20 | private final Long multiplicationResultAttemptId; 21 | private final Long userId; 22 | private final boolean correct; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/repository/MultiplicationRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.repository; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | /** 7 | * This interface allows us to save and retrieve Multiplications 8 | */ 9 | public interface MultiplicationRepository extends CrudRepository { 10 | } 11 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/repository/MultiplicationResultAttemptRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.repository; 2 | 3 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * This interface allow us to store and retrieve attempts 10 | */ 11 | public interface MultiplicationResultAttemptRepository 12 | extends CrudRepository { 13 | 14 | /** 15 | * @return the latest 5 attempts for a given user, identified by their alias. 16 | */ 17 | List findTop5ByUserAliasOrderByIdDesc(String userAlias); 18 | } 19 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.repository; 2 | 3 | import microservices.book.multiplication.domain.User; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * This interface allows us to save and retrieve Users 10 | */ 11 | public interface UserRepository extends CrudRepository { 12 | 13 | Optional findByAlias(final String alias); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/AdminService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | /** 4 | * This service provides methods to an administrator of the application to perform some high risk operations. 5 | * It should only be used when the application is being tested, never during runtime. 6 | * 7 | * @author moises.macero 8 | */ 9 | public interface AdminService { 10 | 11 | /** 12 | * Deletes all the database contents 13 | */ 14 | void deleteDatabaseContents(); 15 | } 16 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/AdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import microservices.book.multiplication.repository.MultiplicationRepository; 4 | import microservices.book.multiplication.repository.MultiplicationResultAttemptRepository; 5 | import microservices.book.multiplication.repository.UserRepository; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | * @author moises.macero 11 | */ 12 | @Profile("test") 13 | @Service 14 | public class AdminServiceImpl implements AdminService { 15 | 16 | private MultiplicationRepository multiplicationRepository; 17 | private MultiplicationResultAttemptRepository attemptRepository; 18 | private UserRepository userRepository; 19 | 20 | public AdminServiceImpl(final MultiplicationRepository multiplicationRepository, 21 | final UserRepository userRepository, 22 | final MultiplicationResultAttemptRepository attemptRepository) { 23 | this.multiplicationRepository = multiplicationRepository; 24 | this.userRepository = userRepository; 25 | this.attemptRepository = attemptRepository; 26 | } 27 | 28 | @Override 29 | public void deleteDatabaseContents() { 30 | attemptRepository.deleteAll(); 31 | multiplicationRepository.deleteAll(); 32 | userRepository.deleteAll(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/MultiplicationService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | 6 | import java.util.List; 7 | 8 | public interface MultiplicationService { 9 | 10 | /** 11 | * Creates a Multiplication object with two randomly-generated factors 12 | * between 11 and 99. 13 | * 14 | * @return a Multiplication object with random factors 15 | */ 16 | Multiplication createRandomMultiplication(); 17 | 18 | /** 19 | * @return true if the attempt matches the result of the 20 | * multiplication, false otherwise. 21 | */ 22 | MultiplicationResultAttempt checkAttempt(final MultiplicationResultAttempt resultAttempt); 23 | 24 | /** 25 | * Gets the statistics for a given user. 26 | * 27 | * @param userAlias the user's alias 28 | * @return a list of {@link MultiplicationResultAttempt} objects, being the past attempts of the user. 29 | */ 30 | List getStatsForUser(final String userAlias); 31 | 32 | /** 33 | * Gets an attempt by its id 34 | * 35 | * @param resultId the identifier of the attempt 36 | * @return the {@link MultiplicationResultAttempt} object matching the id, otherwise null. 37 | */ 38 | MultiplicationResultAttempt getResultById(final Long resultId); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/MultiplicationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | import microservices.book.multiplication.domain.User; 6 | import microservices.book.multiplication.event.EventDispatcher; 7 | import microservices.book.multiplication.event.MultiplicationSolvedEvent; 8 | import microservices.book.multiplication.repository.MultiplicationResultAttemptRepository; 9 | import microservices.book.multiplication.repository.UserRepository; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.util.Assert; 13 | 14 | import javax.transaction.Transactional; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | @Service 19 | class MultiplicationServiceImpl implements MultiplicationService { 20 | 21 | private RandomGeneratorService randomGeneratorService; 22 | private MultiplicationResultAttemptRepository attemptRepository; 23 | private UserRepository userRepository; 24 | private EventDispatcher eventDispatcher; 25 | 26 | @Autowired 27 | public MultiplicationServiceImpl(final RandomGeneratorService randomGeneratorService, 28 | final MultiplicationResultAttemptRepository attemptRepository, 29 | final UserRepository userRepository, 30 | final EventDispatcher eventDispatcher) { 31 | this.randomGeneratorService = randomGeneratorService; 32 | this.attemptRepository = attemptRepository; 33 | this.userRepository = userRepository; 34 | this.eventDispatcher = eventDispatcher; 35 | } 36 | 37 | @Override 38 | public Multiplication createRandomMultiplication() { 39 | int factorA = randomGeneratorService.generateRandomFactor(); 40 | int factorB = randomGeneratorService.generateRandomFactor(); 41 | return new Multiplication(factorA, factorB); 42 | } 43 | 44 | @Transactional 45 | @Override 46 | public MultiplicationResultAttempt checkAttempt(final MultiplicationResultAttempt attempt) { 47 | // Check if the user already exists for that alias 48 | Optional user = userRepository.findByAlias(attempt.getUser().getAlias()); 49 | 50 | // Avoids 'hack' attempts 51 | Assert.isTrue(!attempt.isCorrect(), "You can't send an attempt marked as correct!!"); 52 | 53 | // Check if the attempt is correct 54 | boolean isCorrect = attempt.getResultAttempt() == 55 | attempt.getMultiplication().getFactorA() * 56 | attempt.getMultiplication().getFactorB(); 57 | 58 | MultiplicationResultAttempt checkedAttempt = new MultiplicationResultAttempt( 59 | user.orElse(attempt.getUser()), 60 | attempt.getMultiplication(), 61 | attempt.getResultAttempt(), 62 | isCorrect 63 | ); 64 | 65 | // Stores the attempt 66 | MultiplicationResultAttempt storedAttempt = attemptRepository.save(checkedAttempt); 67 | 68 | // Communicates the result via Event 69 | eventDispatcher.send( 70 | new MultiplicationSolvedEvent(checkedAttempt.getId(), 71 | checkedAttempt.getUser().getId(), 72 | checkedAttempt.isCorrect()) 73 | ); 74 | 75 | return storedAttempt; 76 | } 77 | 78 | @Override 79 | public List getStatsForUser(final String userAlias) { 80 | return attemptRepository.findTop5ByUserAliasOrderByIdDesc(userAlias); 81 | } 82 | 83 | @Override 84 | public MultiplicationResultAttempt getResultById(final Long resultId) { 85 | // BOOT2: changed from findOne 86 | return attemptRepository.findById(resultId) 87 | .orElseThrow(() -> new IllegalArgumentException( 88 | "The requested resultId [" + resultId + 89 | "] does not exist.")); 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/RandomGeneratorService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | public interface RandomGeneratorService { 4 | 5 | /** 6 | * @return a randomly-generated factor. It's always a number between 11 and 99. 7 | */ 8 | int generateRandomFactor(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/RandomGeneratorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | import java.util.Random; 5 | 6 | @Service 7 | final class RandomGeneratorServiceImpl implements RandomGeneratorService { 8 | 9 | final static int MINIMUM_FACTOR = 11; 10 | final static int MAXIMUM_FACTOR = 99; 11 | 12 | @Override 13 | public int generateRandomFactor() { 14 | return new Random().nextInt((MAXIMUM_FACTOR - MINIMUM_FACTOR) + 1) + MINIMUM_FACTOR; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /social-multiplication/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:h2:file:~/social-multiplication-test;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE -------------------------------------------------------------------------------- /social-multiplication/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | 3 | # Gives us access to the H2 database web console 4 | spring.h2.console.enabled=true 5 | # Generates the database *only* if it's not there yet 6 | spring.jpa.hibernate.ddl-auto=update 7 | # Creates the database in a file 8 | spring.datasource.url=jdbc:h2:file:~/social-multiplication;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE 9 | # For educational purposes we will show the SQL in console 10 | spring.jpa.properties.hibernate.show_sql=true 11 | 12 | ## RabbitMQ configuration 13 | multiplication.exchange=multiplication_exchange 14 | multiplication.solved.key=multiplication.solved -------------------------------------------------------------------------------- /social-multiplication/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=multiplication -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/controller/AdminControllerDisabledTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import microservices.book.multiplication.service.AdminService; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.mock.web.MockHttpServletResponse; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.mockito.Mockito.verifyZeroInteractions; 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 18 | 19 | /** 20 | * @author moises.macero 21 | */ 22 | @RunWith(SpringRunner.class) 23 | @WebMvcTest(AdminController.class) 24 | public class AdminControllerDisabledTest { 25 | 26 | @MockBean 27 | private AdminService adminService; 28 | 29 | @Autowired 30 | private MockMvc mvc; 31 | 32 | /** 33 | * This test checks that the controller is NOT ACCESSIBLE 34 | * when profile is not set to test 35 | * 36 | * @throws Exception if any error occurs 37 | */ 38 | @Test 39 | public void deleteDatabaseTest() throws Exception { 40 | // when 41 | MockHttpServletResponse response = mvc.perform( 42 | post("/multiplication/admin/delete-db") 43 | .accept(MediaType.APPLICATION_JSON)) 44 | .andReturn().getResponse(); 45 | 46 | // then 47 | assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); 48 | verifyZeroInteractions(adminService); 49 | } 50 | } -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/controller/AdminControllerEnabledTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import microservices.book.multiplication.service.AdminService; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.mock.web.MockHttpServletResponse; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | import org.springframework.test.web.servlet.MockMvc; 15 | 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.mockito.Mockito.verify; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 19 | 20 | /** 21 | * @author moises.macero 22 | */ 23 | @RunWith(SpringRunner.class) 24 | @ActiveProfiles(profiles = "test") 25 | @WebMvcTest(AdminController.class) 26 | public class AdminControllerEnabledTest { 27 | 28 | @MockBean 29 | private AdminService adminService; 30 | 31 | @Autowired 32 | private MockMvc mvc; 33 | 34 | /** 35 | * This test checks that the controller is working as expected when 36 | * the profile is set to test (see annotation in class declaration) 37 | * @throws Exception if any error occurs 38 | */ 39 | @Test 40 | public void deleteDatabaseTest() throws Exception { 41 | // when 42 | MockHttpServletResponse response = mvc.perform( 43 | post("/multiplication/admin/delete-db") 44 | .accept(MediaType.APPLICATION_JSON)) 45 | .andReturn().getResponse(); 46 | 47 | // then 48 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 49 | verify(adminService).deleteDatabaseContents(); 50 | } 51 | } -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/controller/MultiplicationControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.multiplication.domain.Multiplication; 5 | import microservices.book.multiplication.service.MultiplicationService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 11 | import org.springframework.boot.test.json.JacksonTester; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.mock.web.MockHttpServletResponse; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.mockito.BDDMockito.given; 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 22 | 23 | @RunWith(SpringRunner.class) 24 | @WebMvcTest(MultiplicationController.class) 25 | public class MultiplicationControllerTest { 26 | 27 | @MockBean 28 | private MultiplicationService multiplicationService; 29 | 30 | @Autowired 31 | private MockMvc mvc; 32 | 33 | // This object will be magically initialized by the initFields method below. 34 | private JacksonTester json; 35 | 36 | @Before 37 | public void setup() { 38 | JacksonTester.initFields(this, new ObjectMapper()); 39 | } 40 | 41 | @Test 42 | public void getRandomMultiplicationTest() throws Exception{ 43 | // given 44 | given(multiplicationService.createRandomMultiplication()) 45 | .willReturn(new Multiplication(70, 20)); 46 | 47 | // when 48 | MockHttpServletResponse response = mvc.perform( 49 | get("/multiplications/random") 50 | .accept(MediaType.APPLICATION_JSON)) 51 | .andReturn().getResponse(); 52 | 53 | // then 54 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 55 | assertThat(response.getContentAsString()) 56 | .isEqualTo(json.write(new Multiplication(70, 20)).getJson()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/controller/MultiplicationResultAttemptControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.multiplication.domain.Multiplication; 5 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 6 | import microservices.book.multiplication.domain.User; 7 | import microservices.book.multiplication.service.MultiplicationService; 8 | import org.assertj.core.util.Lists; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 14 | import org.springframework.boot.test.json.JacksonTester; 15 | import org.springframework.boot.test.mock.mockito.MockBean; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.mock.web.MockHttpServletResponse; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | import org.springframework.test.web.servlet.MockMvc; 21 | 22 | import java.util.List; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.mockito.BDDMockito.given; 26 | import static org.mockito.Matchers.any; 27 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 28 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 29 | 30 | @RunWith(SpringRunner.class) 31 | @WebMvcTest(MultiplicationResultAttemptController.class) 32 | public class MultiplicationResultAttemptControllerTest { 33 | 34 | @MockBean 35 | private MultiplicationService multiplicationService; 36 | 37 | @Autowired 38 | private MockMvc mvc; 39 | 40 | // These objects will be magically initialized by the initFields method below. 41 | private JacksonTester jsonResultAttempt; 42 | private JacksonTester> jsonResultAttemptList; 43 | 44 | @Before 45 | public void setup() { 46 | JacksonTester.initFields(this, new ObjectMapper()); 47 | } 48 | 49 | @Test 50 | public void postResultReturnCorrect() throws Exception { 51 | genericParameterizedTest(true); 52 | } 53 | 54 | @Test 55 | public void postResultReturnNotCorrect() throws Exception { 56 | genericParameterizedTest(false); 57 | } 58 | 59 | void genericParameterizedTest(final boolean correct) throws Exception { 60 | // given (remember we're not testing here the service itself) 61 | User user = new User("john"); 62 | Multiplication multiplication = new Multiplication(50, 70); 63 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 64 | user, multiplication, 3500, correct); 65 | given(multiplicationService 66 | .checkAttempt(any(MultiplicationResultAttempt.class))) 67 | .willReturn(attempt); 68 | 69 | // when 70 | MockHttpServletResponse response = mvc.perform( 71 | post("/results").contentType(MediaType.APPLICATION_JSON) 72 | .content(jsonResultAttempt.write(attempt).getJson())) 73 | .andReturn().getResponse(); 74 | 75 | // then 76 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 77 | assertThat(response.getContentAsString()).isEqualTo( 78 | jsonResultAttempt.write( 79 | new MultiplicationResultAttempt(attempt.getUser(), 80 | attempt.getMultiplication(), 81 | attempt.getResultAttempt(), 82 | correct) 83 | ).getJson()); 84 | } 85 | 86 | @Test 87 | public void getUserStats() throws Exception { 88 | // given 89 | User user = new User("john_doe"); 90 | Multiplication multiplication = new Multiplication(50, 70); 91 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 92 | user, multiplication, 3500, true); 93 | List recentAttempts = Lists.newArrayList(attempt, attempt); 94 | given(multiplicationService 95 | .getStatsForUser("john_doe")) 96 | .willReturn(recentAttempts); 97 | 98 | // when 99 | MockHttpServletResponse response = mvc.perform( 100 | get("/results").param("alias", "john_doe")) 101 | .andReturn().getResponse(); 102 | 103 | // then 104 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 105 | assertThat(response.getContentAsString()).isEqualTo( 106 | jsonResultAttemptList.write( 107 | recentAttempts 108 | ).getJson()); 109 | } 110 | 111 | @Test 112 | public void getResultByIdTest() throws Exception { 113 | // given 114 | User user = new User("john_doe"); 115 | Multiplication multiplication = new Multiplication(50, 70); 116 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 117 | user, multiplication, 3500, true); 118 | given(multiplicationService.getResultById(4L)).willReturn(attempt); 119 | 120 | // when 121 | MockHttpServletResponse response = mvc.perform( 122 | get("/results/4")) 123 | .andReturn().getResponse(); 124 | 125 | // then 126 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 127 | assertThat(response.getContentAsString()).isEqualTo( 128 | jsonResultAttempt.write(attempt).getJson()); 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/controller/UserControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.multiplication.domain.Multiplication; 5 | import microservices.book.multiplication.domain.User; 6 | import microservices.book.multiplication.repository.UserRepository; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.json.JacksonTester; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.mock.web.MockHttpServletResponse; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | 20 | import java.util.Optional; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | import static org.mockito.BDDMockito.given; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 25 | 26 | @RunWith(SpringRunner.class) 27 | @WebMvcTest(UserController.class) 28 | public class UserControllerTest { 29 | 30 | @MockBean 31 | private UserRepository userRepository; 32 | 33 | @Autowired 34 | private MockMvc mvc; 35 | 36 | // This object will be magically initialized by the initFields method below. 37 | private JacksonTester json; 38 | 39 | @Before 40 | public void setup() { 41 | JacksonTester.initFields(this, new ObjectMapper()); 42 | } 43 | 44 | @Test 45 | public void getUserByIdTest() throws Exception { 46 | // given 47 | long userId = 1; 48 | String userAlias = "john"; 49 | // BOOT2: changed from findOne 50 | given(userRepository.findById(userId)) 51 | .willReturn(Optional.of(new User(userId, userAlias))); 52 | 53 | // when 54 | MockHttpServletResponse response = mvc.perform( 55 | get("/users/" + userId) 56 | .accept(MediaType.APPLICATION_JSON)) 57 | .andReturn().getResponse(); 58 | 59 | // then 60 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 61 | assertThat(response.getContentAsString()) 62 | .isEqualTo(json.write(new User(userId, userAlias)).getJson()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/service/MultiplicationServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | import microservices.book.multiplication.domain.User; 6 | import microservices.book.multiplication.event.EventDispatcher; 7 | import microservices.book.multiplication.event.MultiplicationSolvedEvent; 8 | import microservices.book.multiplication.repository.MultiplicationResultAttemptRepository; 9 | import microservices.book.multiplication.repository.UserRepository; 10 | import org.assertj.core.util.Lists; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.mockito.Mock; 14 | import org.mockito.MockitoAnnotations; 15 | 16 | import java.util.List; 17 | import java.util.Optional; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.mockito.BDDMockito.given; 21 | import static org.mockito.Matchers.eq; 22 | import static org.mockito.Mockito.verify; 23 | 24 | public class MultiplicationServiceImplTest { 25 | 26 | private MultiplicationServiceImpl multiplicationServiceImpl; 27 | 28 | @Mock 29 | private RandomGeneratorService randomGeneratorService; 30 | 31 | @Mock 32 | private MultiplicationResultAttemptRepository attemptRepository; 33 | 34 | @Mock 35 | private UserRepository userRepository; 36 | 37 | @Mock 38 | private EventDispatcher eventDispatcher; 39 | 40 | @Before 41 | public void setUp() { 42 | // With this call to initMocks we tell Mockito to process the annotations 43 | MockitoAnnotations.initMocks(this); 44 | multiplicationServiceImpl = new MultiplicationServiceImpl(randomGeneratorService, attemptRepository, 45 | userRepository, eventDispatcher); 46 | } 47 | 48 | @Test 49 | public void createRandomMultiplicationTest() { 50 | // given (our mocked Random Generator service will return first 50, then 30) 51 | given(randomGeneratorService.generateRandomFactor()).willReturn(50, 30); 52 | 53 | // when 54 | Multiplication multiplication = multiplicationServiceImpl.createRandomMultiplication(); 55 | 56 | // then 57 | assertThat(multiplication.getFactorA()).isEqualTo(50); 58 | assertThat(multiplication.getFactorB()).isEqualTo(30); 59 | } 60 | 61 | @Test 62 | public void checkCorrectAttemptTest() { 63 | // given 64 | Multiplication multiplication = new Multiplication(50, 60); 65 | User user = new User("john_doe"); 66 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 67 | user, multiplication, 3000, false); 68 | MultiplicationResultAttempt verifiedAttempt = new MultiplicationResultAttempt( 69 | user, multiplication, 3000, true); 70 | MultiplicationSolvedEvent event = new MultiplicationSolvedEvent(attempt.getId(), 71 | attempt.getUser().getId(), true); 72 | given(userRepository.findByAlias("john_doe")).willReturn(Optional.empty()); 73 | // Note: the service will set correct to true 74 | given(attemptRepository.save(verifiedAttempt)).willReturn(verifiedAttempt); 75 | 76 | // when 77 | MultiplicationResultAttempt resultAttempt = multiplicationServiceImpl.checkAttempt(attempt); 78 | 79 | // then 80 | assertThat(resultAttempt.isCorrect()).isTrue(); 81 | verify(attemptRepository).save(verifiedAttempt); 82 | verify(eventDispatcher).send(eq(event)); 83 | } 84 | 85 | @Test 86 | public void checkWrongAttemptTest() { 87 | // given 88 | Multiplication multiplication = new Multiplication(50, 60); 89 | User user = new User("john_doe"); 90 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 91 | user, multiplication, 3010, false); 92 | MultiplicationResultAttempt storedAttempt = new MultiplicationResultAttempt( 93 | user, multiplication, 3010, false); 94 | MultiplicationSolvedEvent event = new MultiplicationSolvedEvent(attempt.getId(), 95 | attempt.getUser().getId(), false); 96 | given(userRepository.findByAlias("john_doe")).willReturn(Optional.empty()); 97 | given(attemptRepository.save(attempt)).willReturn(storedAttempt); 98 | 99 | // when 100 | MultiplicationResultAttempt resultAttempt = multiplicationServiceImpl.checkAttempt(attempt); 101 | 102 | // then 103 | assertThat(resultAttempt.isCorrect()).isFalse(); 104 | verify(attemptRepository).save(attempt); 105 | verify(eventDispatcher).send(eq(event)); 106 | } 107 | 108 | @Test 109 | public void retrieveStatsTest() { 110 | // given 111 | Multiplication multiplication = new Multiplication(50, 60); 112 | User user = new User("john_doe"); 113 | MultiplicationResultAttempt attempt1 = new MultiplicationResultAttempt( 114 | user, multiplication, 3010, false); 115 | MultiplicationResultAttempt attempt2 = new MultiplicationResultAttempt( 116 | user, multiplication, 3051, false); 117 | List latestAttempts = Lists.newArrayList(attempt1, attempt2); 118 | given(userRepository.findByAlias("john_doe")).willReturn(Optional.empty()); 119 | given(attemptRepository.findTop5ByUserAliasOrderByIdDesc("john_doe")) 120 | .willReturn(latestAttempts); 121 | 122 | // when 123 | List latestAttemptsResult = 124 | multiplicationServiceImpl.getStatsForUser("john_doe"); 125 | 126 | // then 127 | assertThat(latestAttemptsResult).isEqualTo(latestAttempts); 128 | } 129 | } -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/service/RandomGeneratorServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.IntStream; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | public class RandomGeneratorServiceImplTest { 13 | 14 | private RandomGeneratorServiceImpl randomGeneratorServiceImpl; 15 | 16 | @Before 17 | public void setUp() { 18 | randomGeneratorServiceImpl = new RandomGeneratorServiceImpl(); 19 | } 20 | 21 | @Test 22 | public void generateRandomFactorIsBetweenExpectedLimits() throws Exception { 23 | // when a good sample of randomly generated factors is generated 24 | List randomFactors = IntStream.range(0, 1000) 25 | .map(i -> randomGeneratorServiceImpl.generateRandomFactor()) 26 | .boxed().collect(Collectors.toList()); 27 | 28 | // then all of them should be between 11 and 100 29 | // because we want a middle-complexity calculation 30 | assertThat(randomFactors).containsOnlyElementsOf(IntStream.range(11, 100) 31 | .boxed().collect(Collectors.toList())); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /tests_e2e/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /tests_e2e/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/tests_e2e/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tests_e2e/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /tests_e2e/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /tests_e2e/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | microservices.book 5 | tests-e2e-v10 6 | jar 7 | 0.10.0-SNAPSHOT 8 | 9 | 10 | 11 | org.apache.maven.plugins 12 | maven-compiler-plugin 13 | 14 | 1.8 15 | 1.8 16 | 17 | 18 | 19 | 20 | tests-e2e-v10 21 | End to End tests - Microservices - The Practical Way (Book) 22 | 23 | 24 | UTF-8 25 | UTF-8 26 | 1.8 27 | 2.8.9 28 | 1.2.5 29 | 30 | 31 | http://maven.apache.org 32 | 33 | 34 | info.cukes 35 | cucumber-java 36 | ${cucumber-version} 37 | test 38 | 39 | 40 | info.cukes 41 | cucumber-junit 42 | ${cucumber-version} 43 | test 44 | 45 | 46 | info.cukes 47 | cucumber-picocontainer 48 | ${cucumber-version} 49 | test 50 | 51 | 52 | org.apache.httpcomponents 53 | fluent-hc 54 | 4.5.3 55 | test 56 | 57 | 58 | junit 59 | junit 60 | 4.12 61 | test 62 | 63 | 64 | org.assertj 65 | assertj-core 66 | 3.8.0 67 | test 68 | 69 | 70 | 71 | com.fasterxml.jackson.core 72 | jackson-core 73 | ${jackson-2-version} 74 | test 75 | 76 | 77 | com.fasterxml.jackson.core 78 | jackson-annotations 79 | ${jackson-2-version} 80 | test 81 | 82 | 83 | com.fasterxml.jackson.core 84 | jackson-databind 85 | ${jackson-2-version} 86 | test 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/LeaderboardFeatureSteps.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import cucumber.api.java.en.Then; 4 | import microservices.book.testutils.beans.LeaderBoardPosition; 5 | 6 | import java.util.List; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | /** 11 | * @author moises.macero 12 | */ 13 | public class LeaderboardFeatureSteps { 14 | 15 | private MultiplicationFeatureSteps mSteps; 16 | 17 | public LeaderboardFeatureSteps(final MultiplicationFeatureSteps mSteps) { 18 | this.mSteps = mSteps; 19 | } 20 | 21 | @Then("^the user ([^\\s]+) is the number (\\d+) on the leaderboard$") 22 | public void the_user_is_the_number_on_the_leaderboard(final String user, final int position) throws Throwable { 23 | Thread.currentThread().sleep(500); 24 | List leaderBoard = mSteps.getApp().getLeaderboard(); 25 | assertThat(leaderBoard).isNotEmpty(); 26 | long userId = leaderBoard.get(position - 1).getUserId(); 27 | String userAlias = mSteps.getApp().getUser(userId).getAlias(); 28 | assertThat(userAlias).isEqualTo(user); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/LeaderboardFeatureTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.junit.Cucumber; 5 | import org.junit.runner.RunWith; 6 | 7 | /** 8 | * @author moises.macero 9 | */ 10 | @RunWith(Cucumber.class) 11 | @CucumberOptions(plugin = { "pretty", "html:target/cucumber", "junit:target/junit-report.xml" }, 12 | features = "src/test/resources/leaderboard.feature") 13 | public class LeaderboardFeatureTest { 14 | } 15 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/MultiplicationFeatureSteps.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import cucumber.api.java.Before; 4 | import cucumber.api.java.en.Given; 5 | import cucumber.api.java.en.Then; 6 | import microservices.book.testutils.beans.AttemptResponse; 7 | import microservices.book.testutils.MultiplicationApplication; 8 | import microservices.book.testutils.beans.Stats; 9 | 10 | import java.util.List; 11 | import java.util.stream.IntStream; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | /** 16 | * @author moises.macero 17 | */ 18 | public class MultiplicationFeatureSteps { 19 | 20 | private MultiplicationApplication app; 21 | private AttemptResponse lastAttemptResponse; 22 | private Stats lastStatsResponse; 23 | 24 | public MultiplicationFeatureSteps() { 25 | this.app = new MultiplicationApplication(); 26 | } 27 | 28 | @Before 29 | public void cleanUp() { 30 | app.deleteData(); 31 | } 32 | 33 | @Given("^the user ([^\\s]+) sends (\\d+) ([^\\s]+) attempts") 34 | public void the_user_sends_attempts(final String userAlias, 35 | final int attempts, 36 | final String rightOrWrong) 37 | throws Throwable { 38 | int attemptsSent = IntStream.range(0, attempts) 39 | .mapToObj(i -> app.sendAttempt(userAlias, 10, 10, 40 | "right".equals(rightOrWrong) ? 41 | 100 : 258)) 42 | // store last attempt for later use 43 | .peek(response -> lastAttemptResponse = response) 44 | .mapToInt(response -> response.isCorrect() ? 1 : 0) 45 | .sum(); 46 | assertThat(attemptsSent).isEqualTo("right".equals(rightOrWrong) ? attempts : 0) 47 | .withFailMessage("Error sending attempts to the application"); 48 | } 49 | 50 | @Then("^the user gets a response indicating the attempt is ([^\\s]+)$") 51 | public void the_user_gets_a_response_indicating_the_attempt_is( 52 | final String rightOrWrong) throws Throwable { 53 | assertThat(lastAttemptResponse.isCorrect()) 54 | .isEqualTo("right".equals(rightOrWrong)) 55 | .withFailMessage("Expecting a response with a " 56 | + rightOrWrong + " attempt"); 57 | } 58 | 59 | @Then("^the user gets (\\d+) points for the attempt$") 60 | public void the_user_gets_points_for_the_attempt( 61 | final int points) throws Throwable { 62 | long attemptId = lastAttemptResponse.getId(); 63 | Thread.currentThread().sleep(2000); 64 | int score = app.getScoreForAttempt(attemptId).getScore(); 65 | assertThat(score).isEqualTo(points); 66 | } 67 | 68 | @Then("^the user gets the ([^\\s]+) badge$") 69 | public void the_user_gets_the_type_badge( 70 | final String badgeType) throws Throwable { 71 | long userId = lastAttemptResponse.getUser().getId(); 72 | Thread.currentThread().sleep(200); 73 | lastStatsResponse = app.getStatsForUser(userId); 74 | List userBadges = lastStatsResponse.getBadges(); 75 | assertThat(userBadges).contains(badgeType); 76 | } 77 | 78 | @Then("^the user does not get any badge$") 79 | public void the_user_does_not_get_any_badge() throws Throwable { 80 | long userId = lastAttemptResponse.getUser().getId(); 81 | Stats stats = app.getStatsForUser(userId); 82 | List userBadges = stats.getBadges(); 83 | if (stats.getScore() == 0) { 84 | assertThat(stats.getBadges()).isNullOrEmpty(); 85 | } else { 86 | assertThat(userBadges).isEqualTo(lastStatsResponse.getBadges()); 87 | } 88 | } 89 | 90 | @Given("^the user has (\\d+) points$") 91 | public void the_user_has_points(final int points) throws Throwable { 92 | long userId = lastAttemptResponse.getUser().getId(); 93 | int statPoints = app.getStatsForUser(userId).getScore(); 94 | assertThat(points).isEqualTo(statPoints); 95 | } 96 | 97 | public AttemptResponse getLastAttemptResponse() { 98 | return lastAttemptResponse; 99 | } 100 | 101 | public Stats getLastStatsResponse() { 102 | return lastStatsResponse; 103 | } 104 | 105 | public MultiplicationApplication getApp() { 106 | return app; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/MultiplicationFeatureTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.junit.Cucumber; 5 | import org.junit.runner.RunWith; 6 | 7 | /** 8 | * @author moises.macero 9 | */ 10 | @RunWith(Cucumber.class) 11 | @CucumberOptions(plugin = { "pretty", "html:target/cucumber", "junit:target/junit-report.xml" }, 12 | features = "src/test/resources/multiplication.feature") 13 | public class MultiplicationFeatureTest { 14 | } 15 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/testutils/MultiplicationApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book.testutils; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationFeature; 4 | import com.fasterxml.jackson.databind.JavaType; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import microservices.book.testutils.beans.AttemptResponse; 7 | import microservices.book.testutils.beans.User; 8 | import microservices.book.testutils.beans.LeaderBoardPosition; 9 | import microservices.book.testutils.beans.Stats; 10 | import microservices.book.testutils.beans.ScoreResponse; 11 | import microservices.book.testutils.http.ApplicationHttpUtils; 12 | 13 | import java.io.IOException; 14 | import java.util.List; 15 | 16 | /** 17 | * @author moises.macero 18 | */ 19 | public class MultiplicationApplication { 20 | 21 | private static final String APPLICATION_BASE_URL = "http://localhost:8000/api"; 22 | private static final String CONTEXT_ATTEMPTS = "/results"; 23 | private static final String CONTEXT_SCORE = "/scores/"; 24 | private static final String CONTEXT_STATS = "/stats"; 25 | private static final String CONTEXT_USERS = "/users/"; 26 | private static final String CONTEXT_LEADERBOARD = "/leaders"; 27 | private static final String CONTEXT_DELETE_DATA_GAM = "/gamification/admin/delete-db"; 28 | private static final String CONTEXT_DELETE_DATA_MULT = "/multiplication/admin/delete-db"; 29 | 30 | private ApplicationHttpUtils httpUtils; 31 | 32 | public MultiplicationApplication() { 33 | this.httpUtils = new ApplicationHttpUtils(APPLICATION_BASE_URL); 34 | } 35 | 36 | public AttemptResponse sendAttempt(String userAlias, int factorA, int factorB, int result) { 37 | String attemptJson = "{\"user\":{\"alias\":\"" + userAlias + "\"}," + 38 | "\"multiplication\":{\"factorA\":\"" + factorA + "\",\"factorB\":\"" + factorB + "\"}," + 39 | "\"resultAttempt\":\"" + result + "\"}"; 40 | String response = httpUtils.post(CONTEXT_ATTEMPTS, attemptJson); 41 | ObjectMapper objectMapper = new ObjectMapper(); 42 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 43 | try { 44 | return objectMapper.readValue(response, AttemptResponse.class); 45 | } catch (IOException e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | 50 | public ScoreResponse getScoreForAttempt(long attemptId) { 51 | String response = httpUtils.get(CONTEXT_SCORE + attemptId); 52 | if (response.isEmpty()) { 53 | return new ScoreResponse(0); 54 | } else { 55 | ObjectMapper objectMapper = new ObjectMapper(); 56 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 57 | try { 58 | return objectMapper.readValue(response, ScoreResponse.class); 59 | } catch (IOException e) { 60 | throw new RuntimeException(e); 61 | } 62 | } 63 | } 64 | 65 | public Stats getStatsForUser(long userId) { 66 | String response = httpUtils.get(CONTEXT_STATS + "?userId=" + userId); 67 | ObjectMapper objectMapper = new ObjectMapper(); 68 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 69 | try { 70 | return objectMapper.readValue(response, Stats.class); 71 | } catch (IOException e) { 72 | throw new RuntimeException(e); 73 | } 74 | } 75 | 76 | public User getUser(long userId) { 77 | String response = httpUtils.get(CONTEXT_USERS + userId); 78 | ObjectMapper objectMapper = new ObjectMapper(); 79 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 80 | try { 81 | return objectMapper.readValue(response, User.class); 82 | } catch (IOException e) { 83 | throw new RuntimeException(e); 84 | } 85 | } 86 | 87 | public List getLeaderboard() { 88 | String response = httpUtils.get(CONTEXT_LEADERBOARD); 89 | ObjectMapper objectMapper = new ObjectMapper(); 90 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 91 | try { 92 | JavaType javaType = objectMapper.getTypeFactory().constructCollectionType(List.class, LeaderBoardPosition.class); 93 | return objectMapper.readValue(response, javaType); 94 | } catch (IOException e) { 95 | throw new RuntimeException(e); 96 | } 97 | } 98 | 99 | public void deleteData() { 100 | httpUtils.post(CONTEXT_DELETE_DATA_GAM, ""); 101 | httpUtils.post(CONTEXT_DELETE_DATA_MULT, ""); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/testutils/beans/AttemptResponse.java: -------------------------------------------------------------------------------- 1 | package microservices.book.testutils.beans; 2 | 3 | /** 4 | * @author moises.macero 5 | */ 6 | public class AttemptResponse { 7 | 8 | private boolean correct; 9 | private long id; 10 | private User user; 11 | 12 | public AttemptResponse() { 13 | } 14 | 15 | public boolean isCorrect() { 16 | return correct; 17 | } 18 | 19 | public long getId() { 20 | return id; 21 | } 22 | 23 | public User getUser() { 24 | return user; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/testutils/beans/LeaderBoardPosition.java: -------------------------------------------------------------------------------- 1 | package microservices.book.testutils.beans; 2 | 3 | /** 4 | * @author moises.macero 5 | */ 6 | public class LeaderBoardPosition { 7 | private Long userId; 8 | private Long totalScore; 9 | 10 | public Long getUserId() { 11 | return userId; 12 | } 13 | 14 | public Long getTotalScore() { 15 | return totalScore; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/testutils/beans/ScoreResponse.java: -------------------------------------------------------------------------------- 1 | package microservices.book.testutils.beans; 2 | 3 | /** 4 | * @author moises.macero 5 | */ 6 | public class ScoreResponse { 7 | private long userId; 8 | private int score; 9 | 10 | public ScoreResponse() { 11 | } 12 | 13 | public ScoreResponse(final int score) { 14 | this.score = score; 15 | } 16 | 17 | public long getUserId() { 18 | return userId; 19 | } 20 | 21 | public int getScore() { 22 | return score; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/testutils/beans/Stats.java: -------------------------------------------------------------------------------- 1 | package microservices.book.testutils.beans; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author moises.macero 7 | */ 8 | public class Stats { 9 | private List badges; 10 | private int score; 11 | 12 | public List getBadges() { 13 | return badges; 14 | } 15 | 16 | public int getScore() { 17 | return score; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/testutils/beans/User.java: -------------------------------------------------------------------------------- 1 | package microservices.book.testutils.beans; 2 | 3 | /** 4 | * @author moises.macero 5 | */ 6 | public class User { 7 | 8 | private long id; 9 | private String alias; 10 | 11 | public User() { 12 | } 13 | 14 | public long getId() { 15 | return id; 16 | } 17 | 18 | public String getAlias() { 19 | return alias; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests_e2e/src/test/java/microservices/book/testutils/http/ApplicationHttpUtils.java: -------------------------------------------------------------------------------- 1 | package microservices.book.testutils.http; 2 | 3 | import org.apache.http.HttpResponse; 4 | import org.apache.http.client.fluent.Request; 5 | import org.apache.http.entity.ContentType; 6 | import org.apache.http.util.EntityUtils; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | /** 13 | * @author moises.macero 14 | */ 15 | public class ApplicationHttpUtils { 16 | 17 | private final String baseUrl; 18 | 19 | public ApplicationHttpUtils(final String baseUrl) { 20 | this.baseUrl = baseUrl; 21 | } 22 | 23 | public String post(final String context, final String body) { 24 | try { 25 | HttpResponse response = Request.Post(baseUrl + context) 26 | .bodyString(body, ContentType.APPLICATION_JSON) 27 | .execute().returnResponse(); 28 | assertIs200(response); 29 | return EntityUtils.toString(response.getEntity()); 30 | } catch (IOException e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | 35 | public String get(final String context) { 36 | try { 37 | HttpResponse response = Request.Get(baseUrl + context) 38 | .execute().returnResponse(); 39 | assertIs200(response); 40 | return EntityUtils.toString(response.getEntity()); 41 | } catch (IOException e) { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | 46 | private void assertIs200(final HttpResponse httpResponse) { 47 | assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(200); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests_e2e/src/test/resources/leaderboard.feature: -------------------------------------------------------------------------------- 1 | Feature: Users are listed from highest score to lowest, and when 2 | they get points they can move up on the ranking. 3 | 4 | Scenario: A user sends a higher number of right attempts and 5 | it's positioned at the first place in the ranking. 6 | When the user john sends 2 right attempts 7 | And the user peter sends 1 right attempts 8 | Then the user john is the number 1 on the leaderboard 9 | And the user peter is the number 2 on the leaderboard 10 | 11 | Scenario: A user passes another one when gets higher score. 12 | Given the user john sends 3 right attempts 13 | And the user peter sends 2 right attempts 14 | And the user john is the number 1 on the leaderboard 15 | When the user peter sends 2 right attempts 16 | Then the user peter is the number 1 on the leaderboard 17 | And the user john is the number 2 on the leaderboard 18 | -------------------------------------------------------------------------------- /tests_e2e/src/test/resources/multiplication.feature: -------------------------------------------------------------------------------- 1 | Feature: Users are able to send their multiplication 2 | attempts, which may be correct or not. When users 3 | send a correct attempt, they get a response indicating 4 | that the result is the right one. Also, they get points 5 | and potentially some badges when they are right, so they 6 | get motivation to come back and keep playing. Badges are 7 | won for the first right attempt and when the user gets 100, 8 | 500 and 999 points respectively. If users send a wrong 9 | attempt, they don't get any point or badge. 10 | 11 | Scenario: The user sends a first right attempt and gets a badge 12 | When the user john_snow sends 1 right attempts 13 | Then the user gets a response indicating the attempt is right 14 | And the user gets 10 points for the attempt 15 | And the user gets the FIRST_WON badge 16 | 17 | Scenario: The user sends a second right attempt and gets points only 18 | Given the user john_snow sends 1 right attempts 19 | And the user gets the FIRST_WON badge 20 | When the user john_snow sends 1 right attempts 21 | Then the user gets a response indicating the attempt is right 22 | And the user gets 10 points for the attempt 23 | And the user does not get any badge 24 | 25 | Scenario: The user sends a wrong attempt and gets nothing 26 | When the user john_snow sends 1 wrong attempts 27 | Then the user gets a response indicating the attempt is wrong 28 | And the user gets 0 points for the attempt 29 | And the user does not get any badge 30 | 31 | # Checks the Bronze, Silver and Gold badges 32 | Scenario Outline: The user sends a right attempt after right attempts and then gets a badge 33 | Given the user john_snow sends right attempts 34 | When the user john_snow sends 1 right attempts 35 | Then the user gets a response indicating the attempt is right 36 | And the user gets 10 points for the attempt 37 | And the user gets the badge 38 | 39 | Examples: 40 | | previous_attempts | badge_name | 41 | | 9 | BRONZE_MULTIPLICATOR | 42 | | 49 | SILVER_MULTIPLICATOR | 43 | | 99 | GOLD_MULTIPLICATOR | 44 | -------------------------------------------------------------------------------- /ui/start.d/deploy.ini: -------------------------------------------------------------------------------- 1 | # --------------------------------------- 2 | # Module: deploy 3 | # Enables webapplication deployment from the webapps directory. 4 | # --------------------------------------- 5 | --module=deploy 6 | 7 | # Monitored directory name (relative to $jetty.base) 8 | # jetty.deploy.monitoredDir=webapps 9 | # - OR - 10 | # Monitored directory path (fully qualified) 11 | # jetty.deploy.monitoredPath=/var/www/webapps 12 | 13 | # Defaults Descriptor for all deployed webapps 14 | # jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault.xml 15 | 16 | # Monitored directory scan period (seconds) 17 | # jetty.deploy.scanInterval=1 18 | 19 | # Whether to extract *.war files 20 | # jetty.deploy.extractWars=true 21 | 22 | -------------------------------------------------------------------------------- /ui/start.d/http.ini: -------------------------------------------------------------------------------- 1 | # --------------------------------------- 2 | # Module: http 3 | # Enables a HTTP connector on the server. 4 | # By default HTTP/1 is support, but HTTP2C can 5 | # be added to the connector with the http2c module. 6 | # --------------------------------------- 7 | --module=http 8 | 9 | ### HTTP Connector Configuration 10 | 11 | ## Connector host/address to bind to 12 | # jetty.http.host=0.0.0.0 13 | 14 | ## Connector port to listen on 15 | jetty.http.port=9090 16 | 17 | ## Connector idle timeout in milliseconds 18 | # jetty.http.idleTimeout=30000 19 | 20 | ## Connector socket linger time in seconds (-1 to disable) 21 | # jetty.http.soLingerTime=-1 22 | 23 | ## Number of acceptors (-1 picks default based on number of cores) 24 | # jetty.http.acceptors=-1 25 | 26 | ## Number of selectors (-1 picks default based on number of cores) 27 | # jetty.http.selectors=-1 28 | 29 | ## ServerSocketChannel backlog (0 picks platform default) 30 | # jetty.http.acceptorQueueSize=0 31 | 32 | ## Thread priority delta to give to acceptor threads 33 | # jetty.http.acceptorPriorityDelta=0 34 | 35 | ## HTTP Compliance: RFC7230, RFC2616, LEGACY 36 | # jetty.http.compliance=RFC7230 37 | 38 | -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/ui/webapps/ui/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/ui/webapps/ui/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/ui/webapps/ui/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v10/2397a423fa0cbb2dcd111a05963aa5b625cbf71f/ui/webapps/ui/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /ui/webapps/ui/gamification-client.js: -------------------------------------------------------------------------------- 1 | var SERVER_URL = "http://localhost:8000/api"; 2 | 3 | function updateLeaderBoard() { 4 | $.ajax({ 5 | url: SERVER_URL + "/leaders" 6 | }).then(function(data) { 7 | $('#leaderboard-body').empty(); 8 | data.forEach(function(row) { 9 | $('#leaderboard-body').append('' + row.userId + '' + 10 | '' + row.totalScore + ''); 11 | }); 12 | }); 13 | } 14 | 15 | function updateStats(userId) { 16 | $.ajax({ 17 | url: SERVER_URL + "/stats?userId=" + userId, 18 | success: function(data) { 19 | $('#stats-div').show(); 20 | $('#stats-user-id').empty().append(userId); 21 | $('#stats-score').empty().append(data.score); 22 | $('#stats-badges').empty().append(data.badges.join()); 23 | }, 24 | error: function(data) { 25 | $('#stats-div').show(); 26 | $('#stats-user-id').empty().append(userId); 27 | $('#stats-score').empty().append(0); 28 | $('#stats-badges').empty(); 29 | } 30 | }); 31 | } 32 | 33 | $(document).ready(function() { 34 | 35 | updateLeaderBoard(); 36 | 37 | $("#refresh-leaderboard").click(function( event ) { 38 | updateLeaderBoard(); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /ui/webapps/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Multiplication v1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |

Welcome to Social Multiplication

17 |
18 |
19 |
20 |
21 |

Your new challenge is

22 |

23 | x 24 |

25 |

26 |

27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 |

38 |
39 | 40 | 59 | 60 |
61 |
62 |

Leaderboard

63 | 64 | 65 | 66 | 67 | 68 | 69 |
User IDScore
70 |
71 | 72 |
73 | 74 | 86 |
87 |
88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /ui/webapps/ui/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /ui/webapps/ui/multiplication-client.js: -------------------------------------------------------------------------------- 1 | var SERVER_URL = "http://localhost:8000/api"; 2 | 3 | function updateMultiplication() { 4 | $.ajax({ 5 | url: SERVER_URL + "/multiplications/random" 6 | }).then(function(data) { 7 | // Cleans the form 8 | $("#attempt-form").find( "input[name='result-attempt']" ).val(""); 9 | $("#attempt-form").find( "input[name='user-alias']" ).val(""); 10 | // Gets a random challenge from API and loads the data in the HTML 11 | $('.multiplication-a').empty().append(data.factorA); 12 | $('.multiplication-b').empty().append(data.factorB); 13 | }); 14 | } 15 | 16 | function updateResults(alias) { 17 | var userId = -1; 18 | $.ajax({ 19 | url: SERVER_URL + "/results?alias=" + alias, 20 | async: false, 21 | success: function(data) { 22 | $('#results-div').show(); 23 | $('#results-body').empty(); 24 | data.forEach(function(row) { 25 | $('#results-body').append('' + row.id + '' + 26 | '' + row.multiplication.factorA + ' x ' + row.multiplication.factorB + '' + 27 | '' + row.resultAttempt + '' + 28 | '' + (row.correct === true ? 'YES' : 'NO') + ''); 29 | }); 30 | userId = data[0].user.id; 31 | } 32 | }); 33 | return userId; 34 | } 35 | 36 | $(document).ready(function() { 37 | 38 | updateMultiplication(); 39 | 40 | $("#attempt-form").submit(function( event ) { 41 | 42 | // Don't submit the form normally 43 | event.preventDefault(); 44 | 45 | // Get some values from elements on the page 46 | var a = $('.multiplication-a').text(); 47 | var b = $('.multiplication-b').text(); 48 | var $form = $( this ), 49 | attempt = $form.find( "input[name='result-attempt']" ).val(), 50 | userAlias = $form.find( "input[name='user-alias']" ).val(); 51 | 52 | // Compose the data in the format that the API is expecting 53 | var data = { user: { alias: userAlias}, multiplication: {factorA: a, factorB: b}, resultAttempt: attempt}; 54 | 55 | // Send the data using post 56 | $.ajax({ 57 | url: SERVER_URL + '/results', 58 | type: 'POST', 59 | data: JSON.stringify(data), 60 | contentType: "application/json; charset=utf-8", 61 | dataType: "json", 62 | async: false, 63 | success: function(result){ 64 | if(result.correct) { 65 | $('.result-message').empty() 66 | .append("

The result is correct! Congratulations!

"); 67 | } else { 68 | $('.result-message').empty() 69 | .append("

Ooops that's not correct! But keep trying!

"); 70 | } 71 | } 72 | }); 73 | 74 | updateMultiplication(); 75 | 76 | setTimeout(function(){ 77 | var userId = updateResults(userAlias); 78 | updateStats(userId); 79 | updateLeaderBoard(); 80 | }, 300); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /ui/webapps/ui/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | html { 6 | display: table; 7 | margin: auto; 8 | } 9 | 10 | body { 11 | display: table-cell; 12 | vertical-align: middle; 13 | } --------------------------------------------------------------------------------