A simple Spring Cloud "Slot Machine" 🍒 🍒 🍒 application, demonstrating Spring Boot development and common cloud / microservice patterns.
The lab consists of a number of interconntected components including:
- A Slot Machine Service
- A Random Number Service
- A Service Registry (using Eureka)
- A Configuration Setting Server
- A Circuit Breaker implementation (using Hystrix)
- A Hystrix Dashboard for monitoring applications
If your computer has firewall restrictions, this may require updates to the following file:
${user.home}/.m2/settings.xml
Consult your boss / lead if needed ..
You can check by running mvn from the commandline.
In-lieu of installing the Maven tool, you can run the Maven Wrapper .. though note that it requires non-firewalled access to pull down it's dependencies,
$ ./mvnw spring-boot:run
I recommend Intellij .. The Community addition is free.
This is most likley caused by firewall issues on your computer (espeically likley with corporate provided laptops). Your company has likely provided you with work arounds, probably involing firewall configuration in the ${user.home}/.m2/settings.xml .. Consult your boss.
Something else is already using the specified port in the application.properties file. You can either terminate it, or change the port to something else in the properties file. Via a browser, you can check what is currently using the port.
Ensure that your are importing the project via Maven Pom.xml , instead of just opening files in your IDE. Importing will ensure that the IDE correctly configured the project for Java Development. Using Eclipse , you will need to open up the Maven subcategory when importing the project.
$ mkdir spring-cloud-lab
Subsequent projects / services will be places within this folder as they are generated using the Spring Initlizer quickstart generator.
A number of seperate Java projects will be created.
To faciliate testing, it is a good idea to keep a seperate console and IDE window open for each Java project.
1.1 - Generate a Spring Boot Template from https://start.spring.io
Stick to the default settings, however update:
- artifact name to random-number-service
- for dependencies add Web, Actuator
Implement a /randomNumber endpoint that returns a random integer.
This can be done by creating a RandomNumberController Java class file with:
@RestController
public class RandomNumberController {
@RequestMapping
public int getRandomNumber(){
return new Random().nextInt();
}
}
$ mvn spring-boot:run
You should see a randomly generated number response.
2.1 - Generate a Spring Boot Template from https://start.spring.io
Stick to the default settings, however update:
- artifact name to slot-machine-service
- for dependencies add Web, Actuator
Update the application.properties file to not have a conflicting port with our Random Number Service.
server.port=8081
Implement a /spin endpoint that returns 3 random slot machine symbols. i.e. Cherry, Bar, Orange, Plum, etc
For example:
- Cherry Cherry Cherry
- Bar Orange Plum
- Orange Orange Cherry
This can be done by creating a SlotMachineController class file with:
@RestController
public class SlotMachineController {
private RestTemplate restTemplate;
private static final String[] slotMachineSymbols = {"Cherry", "Bar", "Orange", "Plum"};
@Autowired
public SlotMachineController(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
@RequestMapping
public String spin(){
return "? ? ? "; //TODO implement me !
//Example Rest Call:
//restTemplate.getForObject("http://localhost:8080/randomNumber", Integer.class);
}
}
Also add a RestTemplate Bean to our SlotMachineServiceApplication class:
public class SlotMachineServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SlotMachineServiceApplication.class, args);
}
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
Implement your own solution to generate the random slot spin result at the TODO mark (in SlotMachineController) .. or scroll down for one such solution.
@RequestMapping
public String spin(){
return String.format("%s %s %s", getSingleSpinResult(), getSingleSpinResult(), getSingleSpinResult());
}
private String getSingleSpinResult(){
int randomNumber = restTemplate.getForObject("http://localhost:8080/randomNumber", Integer.class);
return slotMachineSymbols[Math.abs(randomNumber % slotMachineSymbols.length)];
}
BONUS - Add additional code to append winning status - for example "Cherry Cherry Cherry - You're a winner!!"
$ mvn spring-boot:run
You should get a randomly generated slot machine response.
3.1 - Generate a Spring Boot Template from https://start.spring.io
Stick to the default settings, however update:
- artifact name to service-registry
- for dependencies add EurekaServer, Actuator
We need to add the @EnableEurekaServer annoation to the ServiceRegistryApplication class file
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistryApplication {
Update the application.properties file to turn-off self-registry and configure Euruka to use the standard Euraka port of 8761 instead of the Spring Boot default of 8080
server.port=8761
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
$ mvn spring-boot:run
You should see the Eureka main page.
Add the Eureka dependency:
<dependencies>
<!-- exisitng dependencies are here -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
Ensure the Spring-Cloud dependency block is present:
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
Addd the spring-cloud-version definition if needed:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.SR2</spring-cloud.version>
</properties>
Add the @EnableDiscoveryClient annoation to both service's Application class files i.e. :
@SpringBootApplication
@EnableDiscoveryClient
public class SlotMachineServiceApplication {
Explicity set the spring.application.name property in the application.properties files for each service:
Random Number Service
spring.application.name=random-number-service
Slot Machine Service
spring.application.name=slot-machine-service
Update the Slot Machine Service RestTemplate code to enable Service Discovery:
Add the @LoadBalanced annoation to the RestTemplate Bean.
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
Update the RestTemplate call to use the service name (random-number-service) instead of address and port (localhost:8080).
restTemplate.getForObject("http://random-number-service/randomNumber"
For both service directories .. kill the existing processes (Ctrl-C) and restart:
$ mvn spring-boot:run
You should see both the Random-Number-Service and Slot-Machine-Service listed under Instances currently registered with Eureka.
You should see a randomly generated slot machine response.
<dependencies>
<!-- exisitng dependencies are here -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
Add the @EnableCircuitBreaker annotation to the SlotMachineServiceApplication class
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class SlotMachineServiceApplication {
Add the @HystrixCommand(fallbackMethod = "defaultResult") annotation the the spin method
@HystrixCommand(fallbackMethod = "defaultSpinResult")
@RequestMapping
public String spin(){
Implement a default result method in the Slot Machine Controller
private String defaultSpinResult() {
return "? ? ?";
}
$ mvn spring-boot:run
Terminate the Random Number Service via the command line (Ctrl-C).
Attempt to call the localhost:8081/spin endpoint on the Slot Machine Service.
You should see the default fail back response of "? ? ?" in-lieu of a complete failure.
Restart the Random Number Service.
Try the /spin endpoint again .. eventually it will re-enable communication with the now healthly Random Number Service -- the default wait time is 5 seconds.
6.1 - Generate a Spring Boot Template from https://start.spring.io
Stick to the default settings, however update:
- artifact name to config-server
- for dependencies add Config Server, Actuator
We need to add the @EnableConfigServer annoation to the ConfigServerApplication class file
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
Update the application.properties file to use the conventioal config port of 8888 instead of the Spring Boot default of 8888. Enable the native profile to load configuration from local files / class path instead of the the GIT default. Add an config server search location for /configs/application path.
server.port=8888
spring.profiles.active=native
spring.cloud.config.server.native.search-locations=classpath:/configs/{application}
Add some random test settings to a Slot Machine Service application.properties file.
In the /src/main/resources folder of the Config Server .. create a configs and a slot-machine-service directory and add an application.properties file. i.e. /src/main/resources/configs/slot-machine-service/application.properties
Add some settings:
test.setting=some_value
$ mvn spring-boot:run
6.6 - Attempt to load default configuration settings for the Slot Machine Service via localhost:8888/slot-machine-service/default
You should see our created slot-machine-service test settings.
<dependencies>
<!-- exisitng dependencies are here -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
In the configs/slot-machine-service/application.properties of the Config Server , add an existing Slot Machine Setting with a differnt value:
spring.application.name=different-slot-machine-service
$ mvn spring-boot:run
At the beginiing of the Slot Machine Service console initlization, you should see that the congifuration was loaded: Fetching config from server at: http://localhost:8888
In the case of the spring.application.name update, this should be reflected both the the Eureka service listings, and in the initial console initailzaition for Slot Machine Service.
8.1 - Generate a Spring Boot Template from https://start.spring.io
Stick to the default settings, however update:
- artifact name to hystrix-dashboard
- for dependencies add Hystrix Dashboard, Actuator
We need to add the @EnableHystrixDashboard annoation to the HystrixDashboardApplication class file
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
Update the application.properties file to use a differnt port instead of the Spring Boot default of 8888.
server.port=8083
$ mvn spring-boot:run
8.6 - Configure the Hystrix Dashboard at http://localhost:8083/hystrix
Add the Slot Machine Service Hystrix stream for monitoring : http://localhost:8081/hystrix.stream
What we want is to have the slot machine symbol values i.e. bell cherry plum etc defined in properties files instead of hardcored in the source file.
9.1 - Replace the inline setting of the slotMachineSymbols array in the SlotMachineController with loading from the application.properties file.
@Value("${slot.machine.symbols}")
private String[] slotMachineSymbols;
9.2 - Define the new possible slot machine symbol values in the Slot Machine Controller application.properties file:
slot.machine.symbols=Cherry,Bar,Orange,Plum,Apple
9.4 - Define a different set of possible slot machine symbol values in the Slot Machine Controller application.properties file in the Config Server:
slot.machine.symbols=Cherry,Bar,Orange,Plum,Apple,7,Bell
You will need to restart both the Slot Machine Service and the Config Server
9.6 - Allow for updates of these symbol values in the Slot Machine Service without restarting the Slot Machine Service
You will need to add @RefreshScope to the SlotMachineController :
@RestController
@RefreshScope
public class SlotMachineController {
Note that this code change will not automatically refresh the context. We still need to tell the application to refresh it's variables. This is accomplished via the /refresh endpoint. Make a Post call to this endpoint (via curl), to force the application to redownload it's variables. Note that a browser call won't work by default as it's a Get call (in which case you will see a Method Not Allowed error).
$ curl -X POST http://localhost:8081/refresh
You will get a 401 unauthorized error .. the /refresh endpoint is by default locked-down .. To disable it, at the following to the Slot Machine Controller application.properties:
management.security.enabled=false
Note that for testing purposes this is fine .. but for production you will not want to disable security for critical actuator endpoints.
9.5 - Verify the Slot Machine Controller is corretly getting updated symbol values without a restart
Update the values in the Config Server.
Restart the Config Server.
Call the /refresh endpoint on the Slot Machine Service.
Call the /spin endpoint and verify that the new values are called.
10 - Update the Config Server to use the Registry Service , and for the Slot Machine Service to identify the Config Server via Eureka
By default, Spring Boot will look for the Configuration Server at localhost:8888 .. we want to change this so that our Service Registry determines tracks the URL, and correctly provides this information to the Slot Machine Service.
10.1 - Change the Config Server port to a non-standard port address (in the application.properties file)
server.port=8889
Add the Eureka dependency:
<dependencies>
<!-- exisitng dependencies are here -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
Add the @EnableDiscoveryClient annoation in the Application class file i.e. :
@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
public class ConfigServerApplication {
Explicity set the spring.application.name property in the application.properties file:
server.port=8889
spring.application.name=config-server
spring.profiles.active=native
spring.cloud.config.server.native.search-locations=classpath:/configs/{application}
After a restart, you should know see the config-service show up in the Service Registry at http://localhost:8761.
Configure the Service to load configuration from config-server. In the application.properties file, add the spring.cloud.config.discovery.service-id property.
server.port=8081
spring.application.name=slot-machine-service
management.security.enabled=false
slot.machine.symbols=Cherry,Bar,Orange,Plum,Apple
spring.cloud.config.discovery.service-id=config-server
You will need to restart affected Services.
11 - Simplify the Development process of the Slot Machine Service .. by automatically restarting the Service when a build happens.
Currently everytime we make a code change, we require a restart of the Slot Machine Service for the changes to take effect. Using spring-boot-devtools we can automated this process.
<dependencies>
<!-- exisitng dependencies are here -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
Restart the Slot Machine Service.
Make some Random Slot Machine Service code changes .. Can be something as simple as a System.out.println on the spin endpoint.
Build the update code .. in Intellij this is "Build Project"
Verifiy that the Slot Machine Service was automatically restarted (in console), and verify that your random changes have taken effect.
12 - BONUS - Add Integration Tests with Mocking and actual invokation of endpoints using a random port.
14 - BONUS - Secure the communication between the Random Number Service and the Slot Machine Service using Spring Security.
Demo DB Migrations .. and DB Testing (Spock can be a good candidate here)