Skip to content

Commit 3856a53

Browse files
authored
Merge pull request #124 from ryanjbaxter/master
Spring Cloud CircuitBreaker Guide
2 parents ff21a8d + 6f5e358 commit 3856a53

25 files changed

+886
-21
lines changed

README.adoc

Lines changed: 168 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
:toc:
88
:icons: font
99
:source-highlighter: prettify
10-
:project_id: draft-gs-template
11-
This guide walks you through the process of creating a Spring application.
10+
:project_id: gs-spring-cloud-circuitbreaker
11+
This guide walks you through the process of applying circuit breakers to potentially-failing method calls using Spring Cloud Circuit Breaker.
1212

1313
== What you'll build
1414

15-
You'll build a Spring application.
15+
You'll build a microservice application that uses the http://martinfowler.com/bliki/CircuitBreaker.html[Circuit Breaker pattern] to gracefully degrade functionality when a method call fails. Use of the Circuit Breaker pattern can allow a microservice to continue operating when a related service fails, preventing the failure from cascading and giving the failing service time to recover.
1616

1717

1818
== What you'll need
@@ -23,57 +23,204 @@ include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/
2323
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/how_to_complete_this_guide.adoc[]
2424

2525

26-
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/hide-show-gradle.adoc[]
26+
[[reveal-gradle]]
27+
[.reveal-gradle]
28+
== Build with Gradle
2729

28-
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/hide-show-maven.adoc[]
30+
[[scratch]]
31+
[.use-gradle]
32+
== Build with Gradle
33+
34+
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/build_system_intro.adoc[]
35+
36+
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/create_directory_structure_hello.adoc[]
37+
38+
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/create_both_builds.adoc[]
39+
40+
`bookstore/build.gradle`
41+
// AsciiDoc source formatting doesn't support groovy, so using java instead
42+
[source,java]
43+
----
44+
include::https://raw.githubusercontent.com/spring-guides/{project_id}/master/initial/bookstore/build.gradle[]
45+
----
46+
47+
`reading/build.gradle`
48+
// AsciiDoc source formatting doesn't support groovy, so using java instead
49+
[source,java]
50+
----
51+
include::https://raw.githubusercontent.com/spring-guides/{project_id}/master/initial/reading/build.gradle[]
52+
----
53+
54+
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/spring-boot-gradle-plugin.adoc[]
55+
56+
[[reveal-maven]]
57+
[.reveal-maven]
58+
== Build with Maven
59+
60+
[[use-maven]]
61+
[.use-maven]
62+
== Build with Maven
63+
64+
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/build_system_intro_maven.adoc[]
65+
66+
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/create_directory_structure_hello.adoc[]
67+
68+
`bookstore/pom.xml`
69+
[source,xml]
70+
----
71+
include::https://raw.githubusercontent.com/spring-guides/{project_id}/master/initial/bookstore/pom.xml[]
72+
----
73+
74+
`reading/pom.xml`
75+
[source,xml]
76+
----
77+
include::https://raw.githubusercontent.com/spring-guides/{project_id}/master/initial/reading/pom.xml[]
78+
----
79+
80+
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/spring-boot-maven-plugin.adoc[]
2981

3082
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/hide-show-sts.adoc[]
3183

3284

3385

3486
[[initial]]
35-
== Create a resource controller
87+
== Set up a server microservice application
88+
89+
The Bookstore service will have a single endpoint. It will be accessible at `/recommended`, and will (for simplicity) return a `Mono` of `String` recommended reading list.
3690

37-
Create a new controller for your Spring application:
91+
Edit our main class, in `BookstoreApplication.java`. It should look like this:
3892

39-
`src/main/java/hello/GreetingController.java`
93+
`bookstore/src/main/java/hello/BookstoreApplication.java`
4094
[source,java,tabsize=2]
4195
----
42-
include::complete/src/main/java/hello/GreetingController.java[]
96+
include::complete/bookstore/src/main/java/hello/BookstoreApplication.java[]
4397
----
4498

45-
NOTE: The above example does not specify `GET` vs. `PUT`, `POST`, and so forth, because `@RequestMapping` maps all HTTP operations by default. Use `@RequestMapping(method=GET)` to narrow this mapping.
99+
The `@RestController` annotation marks `BookstoreApplication` as a controller class, like `@Controller` does, and also ensures that `@RequestMapping` methods in this class will behave as though annotated with `@ResponseBody`. That is, the return values of `@RequestMapping` methods in this class will be automatically converted appropriately from their original types and will be written directly to the response body.
46100

101+
We're going to run this application locally alongside a client service application, so in `src/main/resources/application.properties`, set `server.port` so that the Bookstore service won't conflict with the client when we get that running.
47102

48-
== Make the application executable
103+
`bookstore/src/main/resources/application.properties`
104+
[source,properties]
105+
----
106+
include::complete/bookstore/src/main/resources/application.properties[]
107+
----
49108

50-
Although it is possible to package this service as a traditional link:/understanding/WAR[WAR] file for deployment to an external application server, the simpler approach demonstrated below creates a standalone application. You package everything in a single, executable JAR file, driven by a good old Java `main()` method. Along the way, you use Spring's support for embedding the link:/understanding/Tomcat[Tomcat] servlet container as the HTTP runtime, instead of deploying to an external instance.
109+
== Set up a client microservice application
51110

111+
The Reading application will be our front-end (as it were) to the Bookstore application. We'll be able to view our reading list there at `/to-read`, and that reading list will be retrieved from the Bookstore service application.
52112

53-
`src/main/java/hello/Application.java`
113+
`reading/src/main/java/hello/ReadingApplication.java`
54114
[source,java,tabsize=2]
55115
----
56-
include::complete/src/main/java/hello/Application.java[]
116+
package hello;
117+
118+
import reactor.core.publisher.Mono;
119+
120+
import org.springframework.boot.SpringApplication;
121+
import org.springframework.boot.autoconfigure.SpringBootApplication;
122+
import org.springframework.web.bind.annotation.RestController;
123+
import org.springframework.web.bind.annotation.RequestMapping;
124+
import org.springframework.web.reactive.function.client.WebClient;
125+
126+
@RestController
127+
@SpringBootApplication
128+
public class ReadingApplication {
129+
130+
@RequestMapping("/to-read")
131+
public Mono<String> toRead() {
132+
return WebClient.builder().build()
133+
.get().uri("http://localhost:8090/recommended").retrieve()
134+
.bodyToMono(String.class);
135+
}
136+
137+
public static void main(String[] args) {
138+
SpringApplication.run(ReadingApplication.class, args);
139+
}
140+
}
57141
----
58142

59-
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/spring-boot-application.adoc[]
143+
To get the list from Bookstore, we're using Spring's `WebClient` class. `WebClient` makes an HTTP GET request to the Bookstore service's URL as we provide it and then returns the result as a `Mono` of `String`. (For more information on using Spring to consume a RESTful service using `WebClient`, see the https://spring.io/guides/gs/reactive-rest-service/[Building a Reactive RESTful Web Service] guide.)
60144

61-
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/build_an_executable_jar_subhead.adoc[]
145+
Add the `server.port` property to `src/main/resources/application.properties`:
62146

63-
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/master/build_an_executable_jar_with_both.adoc[]
147+
`reading/src/main/resources/application.properties`
148+
[source,properties]
149+
----
150+
include::complete/reading/src/main/resources/application.properties[]
151+
----
152+
153+
We now can access, in a browser, the `/to-read` endpoint on our Reading application, and see our reading list. Yet since we rely on the Bookstore application, if anything happens to it, or if Reading is simply unable to access Bookstore, we'll have no list and our users will get a nasty HTTP `500` error message.
154+
155+
== Apply The Circuit Breaker Pattern
156+
Spring Cloud's Circuit Breaker library provides an implementation of the Circuit Breaker pattern:
157+
when we wrap a method call in a circuit breaker, Spring Cloud Circuit Breaker watches for failing
158+
calls to that method, and if failures build up to a threshold, Spring Cloud Circuit Breaker opens
159+
the circuit so that subsequent calls automatically fail. While the circuit is open, Spring Cloud
160+
Circuit Breaker redirects calls to the method, and they’re passed on to our specified fallback
161+
method.
162+
163+
Spring Cloud Circuit Breaker supports many different circuit breaker implementations including,
164+
Resilience4J, Hystrix, Sentinal, and Spring Retry. In this guide we will use the Resilience4J
165+
implementation. To use this implementation we just need to add `spring-cloud-starter-circuitbreaker-reactor-resilience4j`
166+
to our application's classpath.
167+
168+
`reading/pom.xml`
169+
[source,xml]
170+
----
171+
include::complete/reading/pom.xml[]
172+
----
173+
174+
`reading/build.gradle`
175+
[source,groovy]
176+
----
177+
include::complete/reading/build.gradle[]
178+
----
179+
180+
Spring Cloud Circuit Breaker provides an interface called `ReactiveCircuitBreakerFactory` which
181+
we can use to create new circuit breakers for our application. An implementation of this interface
182+
will be auto-configured based on the starter that is on your application's classpath. Lets create
183+
a new service that uses this interface to make API calls to the Bookstore application
184+
185+
`reading/src/main/java/hello/BookService.java`
186+
[source,java]
187+
----
188+
include::complete/reading/src/main/java/hello/BookService.java[]
189+
----
190+
191+
The `ReactiveCircuitBreakerFactory` has a single method called `create` we can use to create new circuit
192+
breakers. Once we have our circuit breaker all we have
193+
to do is call `run`. Run takes a `Mono` or `Flux` and an optional
194+
`Function`. The optional `Function` parameter acts as our fallback if anything goes wrong. In our
195+
sample here the fallback will just return a `Mono` containing the `String` `Cloud Native Java (O'Reilly)`.
196+
197+
With our new service in place, we can update the code in `ReadingApplication` to use this new service.
198+
199+
`reading/src/main/java/hello/ReadingApplication.java`
200+
[source,java]
201+
----
202+
include::complete/reading/src/main/java/hello/ReadingApplication.java[]
203+
----
64204

65205

66-
Logging output is displayed. The service should be up and running within a few seconds.
206+
== Try it out
67207

208+
Run both the Bookstore service and the Reading service, and then open a browser to the Reading service, at `localhost:8080/to-read`. You should see the complete recommended reading list:
68209

69-
== Test the application
210+
----
211+
Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)
212+
----
70213

71-
Now that the application is running, you can test it.
214+
Now shut down the Bookstore application. Our list source is gone, but thanks to Hystrix and Spring Cloud Netflix, we have a reliable abbreviated list to stand in the gap; you should see:
72215

216+
----
217+
Cloud Native Java (O'Reilly)
218+
----
73219

74220
== Summary
75221

76-
Congratulations! You've just developed a Spring application!
222+
Congratulations! You've just developed a Spring application that uses the Circuit Breaker pattern to protect against cascading failures and to provide fallback behavior for potentially failing calls.
223+
77224

78225

79226

Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip

complete/bookstore/build.gradle

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
buildscript {
2+
ext {
3+
springBootVersion = '2.2.2.RELEASET'
4+
}
5+
repositories {
6+
mavenCentral()
7+
maven { url 'https://repo.spring.io/milestone' }
8+
maven { url 'https://repo.spring.io/snapshot' }
9+
}
10+
dependencies {
11+
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
12+
}
13+
}
14+
15+
apply plugin: 'java'
16+
apply plugin: 'eclipse'
17+
apply plugin: 'idea'
18+
apply plugin: 'org.springframework.boot'
19+
apply plugin: 'io.spring.dependency-management'
20+
21+
bootJar {
22+
baseName = 'bookstore'
23+
version = '0.0.1-SNAPSHOT'
24+
}
25+
sourceCompatibility = 1.8
26+
targetCompatibility = 1.8
27+
28+
repositories {
29+
mavenCentral()
30+
}
31+
32+
33+
dependencies {
34+
compile('org.springframework.boot:spring-boot-starter-webflux')
35+
testCompile('org.springframework.boot:spring-boot-starter-test')
36+
}
37+
38+
39+
eclipse {
40+
classpath {
41+
containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
42+
containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
43+
}
44+
}
45+

complete/bookstore/pom.xml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>hello</groupId>
7+
<artifactId>bookstore</artifactId>
8+
<version>0.0.1-SNAPSHOT</version>
9+
<packaging>jar</packaging>
10+
11+
<parent>
12+
<groupId>org.springframework.boot</groupId>
13+
<artifactId>spring-boot-starter-parent</artifactId>
14+
<version>2.2.2.RELEASE</version>
15+
<relativePath/> <!-- lookup parent from repository -->
16+
</parent>
17+
18+
<properties>
19+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
20+
<java.version>1.8</java.version>
21+
</properties>
22+
23+
<dependencies>
24+
<dependency>
25+
<groupId>org.springframework.boot</groupId>
26+
<artifactId>spring-boot-starter-webflux</artifactId>
27+
</dependency>
28+
29+
<dependency>
30+
<groupId>org.springframework.boot</groupId>
31+
<artifactId>spring-boot-starter-test</artifactId>
32+
<scope>test</scope>
33+
</dependency>
34+
</dependencies>
35+
36+
<build>
37+
<plugins>
38+
<plugin>
39+
<groupId>org.springframework.boot</groupId>
40+
<artifactId>spring-boot-maven-plugin</artifactId>
41+
</plugin>
42+
</plugins>
43+
</build>
44+
45+
<repositories>
46+
<repository>
47+
<id>spring-snapshots</id>
48+
<name>Spring Snapshots</name>
49+
<url>https://repo.spring.io/libs-snapshot-local</url>
50+
<snapshots>
51+
<enabled>true</enabled>
52+
</snapshots>
53+
</repository>
54+
<repository>
55+
<id>spring-milestones</id>
56+
<name>Spring Milestones</name>
57+
<url>https://repo.spring.io/libs-milestone-local</url>
58+
<snapshots>
59+
<enabled>false</enabled>
60+
</snapshots>
61+
</repository>
62+
<repository>
63+
<id>spring-releases</id>
64+
<name>Spring Releases</name>
65+
<url>https://repo.spring.io/release</url>
66+
<snapshots>
67+
<enabled>false</enabled>
68+
</snapshots>
69+
</repository>
70+
</repositories>
71+
72+
</project>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package hello;
2+
3+
import reactor.core.publisher.Mono;
4+
5+
import org.springframework.boot.SpringApplication;
6+
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.web.bind.annotation.RestController;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
10+
@RestController
11+
@SpringBootApplication
12+
public class BookstoreApplication {
13+
14+
@RequestMapping(value = "/recommended")
15+
public Mono<String> readingList(){
16+
return Mono.just("Spring in Action (Manning), Cloud Native Java (O'Reilly), Learning Spring Boot (Packt)");
17+
}
18+
19+
public static void main(String[] args) {
20+
SpringApplication.run(BookstoreApplication.class, args);
21+
}
22+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
server.port=8090

0 commit comments

Comments
 (0)