├── service-registry ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.properties │ │ │ └── bootstrap.properties │ │ └── java │ │ │ └── microservices │ │ │ └── book │ │ │ └── serviceregistry │ │ │ └── ServiceRegistryApplication.java │ └── test │ │ └── java │ │ └── microservices │ │ └── book │ │ └── serviceregistry │ │ └── ServiceRegistryApplicationTests.java ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.properties │ │ └── maven-wrapper.jar ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── gamification ├── src │ ├── main │ │ ├── resources │ │ │ ├── bootstrap.properties │ │ │ ├── application-test.properties │ │ │ └── application.properties │ │ └── java │ │ │ └── microservices │ │ │ └── book │ │ │ ├── gamification │ │ │ ├── domain │ │ │ │ ├── Badge.java │ │ │ │ ├── LeaderBoardRow.java │ │ │ │ ├── BadgeCard.java │ │ │ │ ├── ScoreCard.java │ │ │ │ └── GameStats.java │ │ │ ├── service │ │ │ │ ├── AdminService.java │ │ │ │ ├── LeaderBoardService.java │ │ │ │ ├── LeaderBoardServiceImpl.java │ │ │ │ ├── AdminServiceImpl.java │ │ │ │ ├── GameService.java │ │ │ │ └── GameServiceImpl.java │ │ │ ├── client │ │ │ │ ├── MultiplicationResultAttemptClient.java │ │ │ │ ├── dto │ │ │ │ │ └── MultiplicationResultAttempt.java │ │ │ │ ├── MultiplicationResultAttemptDeserializer.java │ │ │ │ └── MultiplicationResultAttemptClientImpl.java │ │ │ ├── configuration │ │ │ │ ├── RestClientConfiguration.java │ │ │ │ ├── WebConfiguration.java │ │ │ │ └── RabbitMQConfiguration.java │ │ │ ├── repository │ │ │ │ ├── BadgeCardRepository.java │ │ │ │ └── ScoreCardRepository.java │ │ │ ├── event │ │ │ │ ├── MultiplicationSolvedEvent.java │ │ │ │ └── EventHandler.java │ │ │ └── controller │ │ │ │ ├── UserStatsController.java │ │ │ │ ├── AdminController.java │ │ │ │ ├── LeaderBoardController.java │ │ │ │ └── ScoreController.java │ │ │ └── GamificationApplication.java │ └── test │ │ └── java │ │ └── microservices │ │ └── book │ │ └── gamification │ │ ├── event │ │ └── EventHandlerTest.java │ │ ├── service │ │ ├── LeaderBoardServiceImplTest.java │ │ └── GameServiceImplTest.java │ │ └── controller │ │ ├── AdminControllerDisabledTest.java │ │ ├── AdminControllerEnabledTest.java │ │ ├── ScoreControllerTest.java │ │ ├── UserStatsControllerTest.java │ │ └── LeaderBoardControllerTest.java ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── .gitignore ├── pom.xml └── mvnw.cmd ├── gateway ├── src │ ├── main │ │ ├── resources │ │ │ ├── bootstrap.yml │ │ │ └── application.yml │ │ └── java │ │ │ └── microservices │ │ │ └── book │ │ │ └── gateway │ │ │ ├── configuration │ │ │ ├── RibbonConfiguration.java │ │ │ ├── WebConfiguration.java │ │ │ └── HystrixFallbackConfiguration.java │ │ │ └── GatewayApplication.java │ └── test │ │ └── java │ │ └── microservices │ │ └── book │ │ └── gateway │ │ └── GatewayApplicationTests.java ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── .gitignore ├── pom.xml ├── mvnw.cmd └── mvnw ├── social-multiplication ├── src │ ├── main │ │ ├── resources │ │ │ ├── bootstrap.properties │ │ │ ├── application-test.properties │ │ │ └── application.properties │ │ └── java │ │ │ └── microservices │ │ │ └── book │ │ │ ├── multiplication │ │ │ ├── service │ │ │ │ ├── RandomGeneratorService.java │ │ │ │ ├── AdminService.java │ │ │ │ ├── RandomGeneratorServiceImpl.java │ │ │ │ ├── AdminServiceImpl.java │ │ │ │ ├── MultiplicationService.java │ │ │ │ └── MultiplicationServiceImpl.java │ │ │ ├── repository │ │ │ │ ├── MultiplicationRepository.java │ │ │ │ ├── UserRepository.java │ │ │ │ └── MultiplicationResultAttemptRepository.java │ │ │ ├── event │ │ │ │ ├── MultiplicationSolvedEvent.java │ │ │ │ └── EventDispatcher.java │ │ │ ├── domain │ │ │ │ ├── Multiplication.java │ │ │ │ ├── User.java │ │ │ │ └── MultiplicationResultAttempt.java │ │ │ ├── configuration │ │ │ │ ├── WebConfiguration.java │ │ │ │ └── RabbitMQConfiguration.java │ │ │ └── controller │ │ │ │ ├── UserController.java │ │ │ │ ├── AdminController.java │ │ │ │ ├── MultiplicationController.java │ │ │ │ └── MultiplicationResultAttemptController.java │ │ │ └── SocialMultiplicationApplication.java │ └── test │ │ └── java │ │ └── microservices │ │ └── book │ │ └── multiplication │ │ ├── service │ │ ├── RandomGeneratorServiceImplTest.java │ │ └── MultiplicationServiceImplTest.java │ │ └── controller │ │ ├── AdminControllerDisabledTest.java │ │ ├── AdminControllerEnabledTest.java │ │ ├── MultiplicationControllerTest.java │ │ ├── UserControllerTest.java │ │ └── MultiplicationResultAttemptControllerTest.java ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.properties │ │ └── maven-wrapper.jar ├── .gitignore ├── pom.xml └── mvnw.cmd ├── clean.sh ├── resources └── logical_view_v8.png ├── tests_e2e ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── .gitignore ├── src │ └── test │ │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ ├── testutils │ │ │ ├── beans │ │ │ │ ├── User.java │ │ │ │ ├── LeaderBoardPosition.java │ │ │ │ ├── Stats.java │ │ │ │ ├── ScoreResponse.java │ │ │ │ └── AttemptResponse.java │ │ │ ├── http │ │ │ │ └── ApplicationHttpUtils.java │ │ │ └── MultiplicationApplication.java │ │ │ ├── LeaderboardFeatureTest.java │ │ │ ├── MultiplicationFeatureTest.java │ │ │ ├── LeaderboardFeatureSteps.java │ │ │ └── MultiplicationFeatureSteps.java │ │ └── resources │ │ ├── leaderboard.feature │ │ └── multiplication.feature ├── pom.xml └── mvnw.cmd ├── ui ├── webapps │ └── ui │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── styles.css │ │ ├── js │ │ └── npm.js │ │ ├── gamification-client.js │ │ ├── multiplication-client.js │ │ └── index.html └── start.d │ ├── deploy.ini │ └── http.ini └── README.md /service-registry/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8761 -------------------------------------------------------------------------------- /gamification/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=gamification -------------------------------------------------------------------------------- /gateway/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: gateway -------------------------------------------------------------------------------- /service-registry/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=service-registry -------------------------------------------------------------------------------- /social-multiplication/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=multiplication -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | find . -name '.idea' -exec rm -vr {} + 2 | find . -name 'target' -exec rm -vr {} + 3 | find . -name '*.iml' -delete -------------------------------------------------------------------------------- /resources/logical_view_v8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/resources/logical_view_v8.png -------------------------------------------------------------------------------- /gamification/src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:h2:file:~/gamification-test;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE -------------------------------------------------------------------------------- /gateway/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/gateway/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /tests_e2e/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/tests_e2e/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /gamification/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/gamification/.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/service-registry/.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/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/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/social-multiplication/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/ui/webapps/ui/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/ui/webapps/ui/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/ui/webapps/ui/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v9/HEAD/ui/webapps/ui/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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') -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 -------------------------------------------------------------------------------- /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 | return new PingUrl(false,"/health"); 15 | } 16 | 17 | @Bean 18 | public IRule ribbonRule(final IClientConfig config) { 19 | return new AvailabilityFilteringRule(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/ -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.WebMvcConfigurerAdapter; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | public class WebConfiguration extends WebMvcConfigurerAdapter { 14 | 15 | /** 16 | * Enables Cross-Origin Resource Sharing (CORS) 17 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 18 | * @param registry 19 | */ 20 | @Override 21 | public void addCorsMappings(final CorsRegistry registry) { 22 | registry.addMapping("/**"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /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.WebMvcConfigurerAdapter; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | public class WebConfiguration extends WebMvcConfigurerAdapter { 14 | 15 | /** 16 | * Enables Cross-Origin Resource Sharing (CORS) 17 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 18 | * @param registry 19 | */ 20 | @Override 21 | public void addCorsMappings(final CorsRegistry registry) { 22 | registry.addMapping("/**"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /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/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.WebMvcConfigurerAdapter; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | public class WebConfiguration extends WebMvcConfigurerAdapter { 14 | 15 | /** 16 | * Enables Cross-Origin Resource Sharing (CORS) 17 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 18 | * @param registry 19 | */ 20 | @Override 21 | public void addCorsMappings(final CorsRegistry registry) { 22 | registry.addMapping("/**"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /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/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 | return userRepository.findOne(userId); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /service-registry/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | service-registry-v9 8 | 0.9.0-SNAPSHOT 9 | jar 10 | 11 | service-registry-v9 12 | Multiplication App - Service Registry (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-eureka-server 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-actuator 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.springframework.cloud 50 | spring-cloud-dependencies 51 | ${spring-cloud.version} 52 | pom 53 | import 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /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/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/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 | } -------------------------------------------------------------------------------- /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/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 static org.assertj.core.api.Assertions.assertThat; 21 | import static org.mockito.BDDMockito.given; 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 23 | 24 | @RunWith(SpringRunner.class) 25 | @WebMvcTest(UserController.class) 26 | public class UserControllerTest { 27 | 28 | @MockBean 29 | private UserRepository userRepository; 30 | 31 | @Autowired 32 | private MockMvc mvc; 33 | 34 | // This object will be magically initialized by the initFields method below. 35 | private JacksonTester json; 36 | 37 | @Before 38 | public void setup() { 39 | JacksonTester.initFields(this, new ObjectMapper()); 40 | } 41 | 42 | @Test 43 | public void getUserByIdTest() throws Exception { 44 | // given 45 | long userId = 1; 46 | String userAlias = "john"; 47 | given(userRepository.findOne(userId)) 48 | .willReturn(new User(userId, userAlias)); 49 | 50 | // when 51 | MockHttpServletResponse response = mvc.perform( 52 | get("/users/" + userId) 53 | .accept(MediaType.APPLICATION_JSON)) 54 | .andReturn().getResponse(); 55 | 56 | // then 57 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 58 | assertThat(response.getContentAsString()) 59 | .isEqualTo(json.write(new User(userId, userAlias)).getJson()); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | gateway-v9 8 | 0.9.0-SNAPSHOT 9 | jar 10 | 11 | gateway-v9 12 | Multiplication App - Gateway (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-zuul 32 | 33 | 34 | 35 | org.springframework.cloud 36 | spring-cloud-starter-eureka 37 | 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-starter-ribbon 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-actuator 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.springframework.cloud 60 | spring-cloud-dependencies 61 | ${spring-cloud.version} 62 | pom 63 | import 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /social-multiplication/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | social-multiplication-v9 8 | 0.9.0-SNAPSHOT 9 | jar 10 | 11 | social-multiplication-v9 12 | Social Multiplication App (Learn Microservices with Spring Boot) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-dependencies 33 | ${spring-cloud.version} 34 | pom 35 | import 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-amqp 49 | 50 | 51 | 52 | org.springframework.cloud 53 | spring-cloud-starter-eureka 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-actuator 59 | 60 | 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 1.16.18 66 | provided 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-data-jpa 72 | 73 | 74 | 75 | com.h2database 76 | h2 77 | runtime 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-test 84 | test 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-maven-plugin 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn Microservices with Spring Boot - v9 2 | 3 | This project contains the version 9 of the application that is developed under the scope of the book *Learn Microservices with Spring Boot*. You can get a copy of the book on [Amazon](http://amzn.to/2FSB2ME) or [Apress](http://www.apress.com/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 introduces end to end testing with Cucumber JVM. 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/book/9781484231647) or [Amazon](http://amzn.to/2FSB2ME). 37 | -------------------------------------------------------------------------------- /gamification/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | gamification-v9 8 | 0.9.0-SNAPSHOT 9 | jar 10 | 11 | gamification-v9 12 | Social Multiplication App - Gamification (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-dependencies 33 | ${spring-cloud.version} 34 | pom 35 | import 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-amqp 49 | 50 | 51 | 52 | org.springframework.cloud 53 | spring-cloud-starter-eureka 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-actuator 59 | 60 | 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 1.16.18 66 | provided 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-data-jpa 72 | 73 | 74 | 75 | org.springframework.cloud 76 | spring-cloud-starter-hystrix 77 | 78 | 79 | 80 | com.h2database 81 | h2 82 | runtime 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-starter-test 88 | test 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-maven-plugin 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests_e2e/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | microservices.book 5 | tests-e2e-v9 6 | jar 7 | 0.9.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-v9 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 | -------------------------------------------------------------------------------- /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 | return attemptRepository.findOne(resultId); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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% -------------------------------------------------------------------------------- /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% -------------------------------------------------------------------------------- /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/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/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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/service/GameServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.client.MultiplicationResultAttemptClient; 4 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 5 | import microservices.book.gamification.domain.*; 6 | import microservices.book.gamification.repository.BadgeCardRepository; 7 | import microservices.book.gamification.repository.ScoreCardRepository; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.mockito.Mock; 11 | import org.mockito.MockitoAnnotations; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.IntStream; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.BDDMockito.given; 20 | import static org.mockito.Matchers.anyLong; 21 | 22 | /** 23 | * @author moises.macero 24 | */ 25 | public class GameServiceImplTest { 26 | 27 | private GameServiceImpl gameService; 28 | 29 | @Mock 30 | private ScoreCardRepository scoreCardRepository; 31 | 32 | @Mock 33 | private BadgeCardRepository badgeCardRepository; 34 | 35 | @Mock 36 | private MultiplicationResultAttemptClient multiplicationClient; 37 | 38 | @Before 39 | public void setUp() { 40 | // With this call to initMocks we tell Mockito to process the annotations 41 | MockitoAnnotations.initMocks(this); 42 | gameService = new GameServiceImpl(scoreCardRepository, badgeCardRepository, multiplicationClient); 43 | 44 | // Common given - attempt does not contain a lucky number by default 45 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 46 | "john_doe", 20, 70, 1400, true); 47 | given(multiplicationClient.retrieveMultiplicationResultAttemptbyId(anyLong())) 48 | .willReturn(attempt); 49 | } 50 | 51 | @Test 52 | public void processFirstCorrectAttemptTest() { 53 | // given 54 | Long userId = 1L; 55 | Long attemptId = 8L; 56 | int totalScore = 10; 57 | ScoreCard scoreCard = new ScoreCard(userId, attemptId); 58 | given(scoreCardRepository.getTotalScoreForUser(userId)) 59 | .willReturn(totalScore); 60 | // this repository will return the just-won score card 61 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 62 | .willReturn(Collections.singletonList(scoreCard)); 63 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 64 | .willReturn(Collections.emptyList()); 65 | 66 | 67 | // when 68 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, true); 69 | 70 | // assert - should score one card and win the badge FIRST_WON 71 | assertThat(iteration.getScore()).isEqualTo(scoreCard.getScore()); 72 | assertThat(iteration.getBadges()).containsOnly(Badge.FIRST_WON); 73 | } 74 | 75 | @Test 76 | public void processCorrectAttemptForScoreBadgeTest() { 77 | // given 78 | Long userId = 1L; 79 | Long attemptId = 29L; 80 | int totalScore = 100; 81 | BadgeCard firstWonBadge = new BadgeCard(userId, Badge.FIRST_WON); 82 | given(scoreCardRepository.getTotalScoreForUser(userId)) 83 | .willReturn(totalScore); 84 | // this repository will return the just-won score card 85 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 86 | .willReturn(createNScoreCards(10, userId)); 87 | // the first won badge is already there 88 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 89 | .willReturn(Collections.singletonList(firstWonBadge)); 90 | 91 | 92 | // when 93 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, true); 94 | 95 | // assert - should score one card and win the badge BRONZE 96 | assertThat(iteration.getScore()).isEqualTo(ScoreCard.DEFAULT_SCORE); 97 | assertThat(iteration.getBadges()).containsOnly(Badge.BRONZE_MULTIPLICATOR); 98 | } 99 | 100 | @Test 101 | public void processCorrectAttemptForLuckyNumberBadgeTest() { 102 | // given 103 | Long userId = 1L; 104 | Long attemptId = 29L; 105 | int totalScore = 10; 106 | BadgeCard firstWonBadge = new BadgeCard(userId, Badge.FIRST_WON); 107 | given(scoreCardRepository.getTotalScoreForUser(userId)) 108 | .willReturn(totalScore); 109 | // this repository will return the just-won score card 110 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 111 | .willReturn(createNScoreCards(1, userId)); 112 | // the first won badge is already there 113 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 114 | .willReturn(Collections.singletonList(firstWonBadge)); 115 | // the attempt includes the lucky number 116 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 117 | "john_doe", 42, 10, 420, true); 118 | given(multiplicationClient.retrieveMultiplicationResultAttemptbyId(attemptId)) 119 | .willReturn(attempt); 120 | 121 | // when 122 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, true); 123 | 124 | // assert - should score one card and win the badge LUCKY NUMBER 125 | assertThat(iteration.getScore()).isEqualTo(ScoreCard.DEFAULT_SCORE); 126 | assertThat(iteration.getBadges()).containsOnly(Badge.LUCKY_NUMBER); 127 | } 128 | 129 | @Test 130 | public void processWrongAttemptTest() { 131 | // given 132 | Long userId = 1L; 133 | Long attemptId = 8L; 134 | int totalScore = 10; 135 | ScoreCard scoreCard = new ScoreCard(userId, attemptId); 136 | given(scoreCardRepository.getTotalScoreForUser(userId)) 137 | .willReturn(totalScore); 138 | // this repository will return the just-won score card 139 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 140 | .willReturn(Collections.singletonList(scoreCard)); 141 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 142 | .willReturn(Collections.emptyList()); 143 | 144 | 145 | // when 146 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, false); 147 | 148 | // assert - shouldn't score anything 149 | assertThat(iteration.getScore()).isEqualTo(0); 150 | assertThat(iteration.getBadges()).isEmpty(); 151 | } 152 | 153 | @Test 154 | public void retrieveStatsForUserTest() { 155 | // given 156 | Long userId = 1L; 157 | int totalScore = 1000; 158 | BadgeCard badgeCard = new BadgeCard(userId, Badge.SILVER_MULTIPLICATOR); 159 | given(scoreCardRepository.getTotalScoreForUser(userId)) 160 | .willReturn(totalScore); 161 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 162 | .willReturn(Collections.singletonList(badgeCard)); 163 | 164 | // when 165 | GameStats stats = gameService.retrieveStatsForUser(userId); 166 | 167 | // assert - should score one card and win the badge FIRST_WON 168 | assertThat(stats.getScore()).isEqualTo(totalScore); 169 | assertThat(stats.getBadges()).containsOnly(Badge.SILVER_MULTIPLICATOR); 170 | } 171 | 172 | private List createNScoreCards(int n, Long userId) { 173 | return IntStream.range(0, n) 174 | .mapToObj(i -> new ScoreCard(userId, (long)i)) 175 | .collect(Collectors.toList()); 176 | } 177 | } --------------------------------------------------------------------------------