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
$ 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();
}
}
$ ./mvnw 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!!"
$ ./mvnw 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
$ ./mvnw 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:
$ ./mvnw 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 "? ? ?";
}
$ ./mvnw 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
$ ./mvnw spring-boot:run
6.6 - Attempt to load default configuration settings for the Slot Machine Service via localhost:8888/slot-machine-serivce/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
$ ./mvnw 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
$ ./mvnw 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
First in the the Slot Machine Service configuration in application.properties.
Then in the Config Server.
Allow for Slot Machine symbol value updates without Slot Machine Service restarts (hint : RefreshScope).