Imagine you have several microservices exposing REST APIs. Imagine now that these microservices are spread all over and need to talk to each other. Imagine that you have a nice user interface interacting with these APIs where you can authenticate. And now, imagine that all this runs smoothly.
In this Deep Dive session, Roberto and Antonio will build, step by step, a full microservice architecture (using Java and different frameworks). This session will answer these questions:
How to build, document and deploy several microservices spread on different nodes (we use a Raspberry PI cluster because the Cloud is too expensive)
How to make those microservices talk to each other (Consul for registry and discovery)
How to scale up, down, and deal with network failures (Ribbon and Zuul to the rescue)
How to deal with high traffic (Hystrix, here you come)
How to monitor this distributed system (Dropwizard metrics with the ELK stack)
How to centralize configuration
How to authenticate and manage authorization with JWT (Tribestream Access Gateway)
How to have a centralized nice looking entry point (with Angular)
7. LISTEN TO OUR STORYLISTEN TO OUR STORY
This talk is about our journey…
… through a Microservices architecture
We’ll progressively build a MS architecture
Going from problem, to solu on…
… to problem, to solu on…
… to problem
And end‐up building a (basic) distributed applica on
2 . 4
18. WHAT DO PEOPLE MEAN BY MICROWHAT DO PEOPLE MEAN BY MICRO
SERVICES ?SERVICES ?
3 . 2
19. PROMISE, WE WON’T TALK ABOUT PIZZAS !PROMISE, WE WON’T TALK ABOUT PIZZAS !
3 . 3
20. SO, WHY MICROSERVICES ?SO, WHY MICROSERVICES ?
Business focused (bounded context)
Deliver new features quicker
Smaller, more agile teams
Scale services independently
Address unpredictable loads
Cloud
3 . 4
21. LET’S START SIMPLELET’S START SIMPLE
We have one Number API Microservice
We have one Angular client
We use 2 PIs
How do we deploy and make this run?
4 . 1
23. SEVERAL WAYS TO DEVELOPSEVERAL WAYS TO DEVELOP
MICROSERVICESMICROSERVICES
Dropwizard
Lagom
Vert.x
Spring Boot
MicroProfile
4 . 3
24. WHAT IS MICROPROFILE?WHAT IS MICROPROFILE?
Comes from Java EE (Jakarta EE)
JAX‐RS + CDI + JSON‐P
Extensions
Configura on
Security
Health Check
Fault tolerance
Metrics
4 . 4
27. RUNNING ON WILDFLY SWARMRUNNING ON WILDFLY SWARM
Comes from Wildfly (RedHat)
Open source
Modular
Small frac ons
Executable Jar
Now known as Thorntail
4 . 7
30. OPEN APIOPEN API
Open API Specifica on
API documenta on
What do you call?
What are the parameters?
What are the status code?
Contract wri en in JSon (or Yaml)
Swagger is one implementa on
4 . 10
31. SWAGGER ANNOTATIONSSWAGGER ANNOTATIONS
@Path("numbers")
@Produces(MediaType.TEXT_PLAIN)
@Api(value = "numbers", description = "Generating all sorts of num
public class NumberResource {
@GET
@Path("book")
@ApiOperation(value = "Generates a book number.", response =
public Response generateBookNumber() {
log.info("Generating a book number");
return Response.ok("BK-" + Math.random()).build();
}
}
4 . 11
34. SWAGGER CODE GENSWAGGER CODE GEN
Generates code from a Swagger contract
Client stubs
Several languages
Including TypeScript for Angular
$ swagger-codegen generate -i
swagger.json -l typescript-angular2 -o
src/app/shared
4 . 14
35. THE GENERATED TYPESCRIPT SERVICETHE GENERATED TYPESCRIPT SERVICE
@Injectable()
export class NumbersApi {
protected basePath = 'http://localhost:8084/number-api/api';
public generateBookNumber(extraHttpRequestParams?: any): Obse
return this.generateBookNumberWithHttpInfo(extraHttpReque
.map((response: Response) => {
if (response.status === 204) {
return undefined;
} else {
return response.text() || "";
}
});
}
4 . 15
56. SEVERAL TO CHOOSE FROMSEVERAL TO CHOOSE FROM
By hand
Apache HTTP Client
JAX‐RS Client API
Ne lix Feign
5 . 4
57. WHAT IS FEIGN?WHAT IS FEIGN?
Feign is a Java to HTTP client
Inspired by Retrofit, JAXRS‐2.0, and WebSocket
Reducing the complexity of HTTP APIs calls
Jersey or CXF
Clients for REST or SOAP services
5 . 5
59. BOOK APIBOOK API
@Path("books")
@Api(value = "books", description = "Operations for Books.")
public class BookResource {
@Inject
private NumbersApi numbersApi;
@Inject
private BookRepository bookRepository;
@GET
@Path("/{id : d+}")
public Response findById(@PathParam("id") final Long id) {
log.info("Getting the book " + id);
return ofNullable(bookRepository.findById(id))
map(Response::ok)
5 . 7
60. RUNNING ON TOMEERUNNING ON TOMEE
TomEE = Tomcat + Java EE
Comes from Apache
Open source
Supported by Tomitribe
Executable Fat‐Jar
5 . 8
61. NUMBER API SWAGGER CONTRACTNUMBER API SWAGGER CONTRACT
{
"swagger" : "2.0",
"info" : {
"description" : "Generates all sorts of numbers",
"version" : "02",
"title" : "Numbers API"
},
"host" : "localhost:8084",
"basePath" : "/number-api/api",
"tags" : [ {
"name" : "numbers",
"description" : "Generating all sorts of numbers."
} ],
"schemes" : [ "http", "https" ],
"paths" : {
"/numbers/book" : {
5 . 9
64. THE GENERATED JAVA FEIGN CLIENTTHE GENERATED JAVA FEIGN CLIENT
public class ApiClient {
private String basePath = "http://localhost:8084/number-api/ap
public NumbersApi buildNumberApiClient() {
return Feign.builder()
.logger(new Slf4jLogger(NumbersApi.class))
.logLevel(Logger.Level.FULL)
.target(NumbersApi.class, basePath);
}
}
5 . 12
65. PRODUCING THE FEIGN CLIENTPRODUCING THE FEIGN CLIENT
@ApplicationScoped
public class NumbersApiProducer {
@Produces
@RequestScoped
public NumbersApi createNumbersApi() {
return new ApiClient().buildNumberApiClient();
}
}
5 . 13
66. INVOKING THE NUMBER API WITH FEIGNINVOKING THE NUMBER API WITH FEIGN
CLIENTCLIENT
@Path("books")
@Api(value = "books", description = "Operations for Books.")
public class BookResource {
@Inject
private NumbersApi numbersApi;
public Response create(@ApiParam(value = "Book to be created"
String isbn = numbersApi.generateBookNumber();
book.setIsbn(isbn);
5 . 14
88. SEVERAL TO CHOOSE FROMSEVERAL TO CHOOSE FROM
Environment variable
Property files
XML, JSon, Yaml
Database
JNDI in Java EE
Spring Config
MicroProfile Configura on
7 . 4
90. BOOK API INVOKING NUMBER APIBOOK API INVOKING NUMBER API
public class ApiClient {
public interface Api {
}
private String baseHost = "http://localhost:8084";
private String basePath = "/number-api/api";
public NumbersApi buildNumberApiClient() {
final Config config = ConfigProvider.getConfig();
config.getOptionalValue("NUMBER_API_HOST", String.class)
.ifPresent(host -> baseHost = host);
return Feign.builder()
.logger(new Slf4jLogger(NumbersApi.class))
.logLevel(Logger.Level.FULL)
target(NumbersApi class baseHost + basePath);
7 . 6
98. SERVICE REGISTRATION AND DISCOVERYSERVICE REGISTRATION AND DISCOVERY
Think of it as DNS resolu on
A service registers with a name
Another service looks for it by name
Several instances of the same service
8 . 3
99. SEVERAL TO CHOOSE FROMSEVERAL TO CHOOSE FROM
JNDI
Apache Zookeeper
Ne lix Eureka
HashiCorp Consul
8 . 4
100. WHAT IS CONSUL?WHAT IS CONSUL?
Solu on to connect and configure applica ons
Distributed, highly available, and data center aware
Dynamic Service Discovery
Run me Configura on
8 . 5
101. REGISTER WITH CONSULREGISTER WITH CONSUL
Consul consul = Consul.builder().withUrl(consulHost + ":" + consu
agentClient = consul.agentClient();
final ImmutableRegistration registration = ImmutableRegistration.b
.id("number-api")
.name(NUMBER_API_NAME)
.address(numberApiHost)
.port(numberApiPort)
.check(http(numberApiHost + ":" + numberApiPort + "/numb
.build();
agentClient.register(registration);
8 . 6
103. DISCOVER WITH CONSULDISCOVER WITH CONSUL
final Consul consul = Consul.builder().withUrl(consulHost + ":" +
final HealthClient healthClient = consul.healthClient();
final List<ServiceHealth> nodes = healthClient.getHealthyServiceI
final Service service = nodes.iterator().next().getService();
final String baseHost = service.getAddress() + ":" + service.getP
return Feign.builder()
.logger(new Slf4jLogger(NumbersApi.class))
.logLevel(Logger.Level.FULL)
.target(NumbersApi.class, baseHost + basePath);
8 . 8
114. CIRCUIT BREAKERCIRCUIT BREAKER
Services some mes collaborate when handling
requests
When invoked synchronously, there is always the
possibility of being unavailable
(or high latency)
The failure of one service can poten ally cascade to
others
Client should invoke a remote service via a proxy
If consecu ve failures, the circuit breaks
10 . 3
115. SEVERAL TO CHOOSE FROMSEVERAL TO CHOOSE FROM
Build your own (CDI)
JRugged
MicroProfile Fault‐Tolerance
Ne lix Hystrix
10 . 4
118. NUMBER API FAILINGNUMBER API FAILING
@GET
@Path("book")
@ApiOperation(value = "Generates a book number.", response = Stri
public Response generateBookNumber() throws InterruptedException
final Config config = ConfigProvider.getConfig();
config.getOptionalValue("NUMBER_API_FAKE_TIMEOUT", Integer.cl
log.info("Waiting for " + numberApiFakeTimeout + " seconds");
TimeUnit.SECONDS.sleep(numberApiFakeTimeout);
return Response.ok("BK-" + Math.random()).build();
}
10 . 6
119. BOOK API FALLING BACKBOOK API FALLING BACK
public NumbersApi buildNumberApiClient() {
// Get host from Consul
// This instance will be invoked if there are errors of any k
NumbersApi fallback = () -> {
return "FALLBACK ISBN";
};
return HystrixFeign.builder()
.logger(new Slf4jLogger(NumbersApi.class))
.logLevel(Logger.Level.FULL)
.target(NumbersApi.class, baseHost + basePath, fallba
}
10 . 7
125. CLIENT-SIDE LOAD BALANCINGCLIENT-SIDE LOAD BALANCING
Several instances of same service registered
The client gets all the registered instances
Then choose from among these instances
Following certain criterias
Capacity, round‐robin, cloud‐provider availability‐
zone awareness, mul ‐tenancy
11 . 3
126. SEVERAL TO CHOOSE FROMSEVERAL TO CHOOSE FROM
Amazon Elas c Load Balancing (ELB)
Apache H pd
Nginx
Ne lix Ribbon
11 . 4
127. WHAT IS RIBBON?WHAT IS RIBBON?
Ne lix implementa on of Client‐Side Load
Balancing
Fault tolerant
Mul ple protocols (HTTP, TCP, UDP) support
Synchronous, asynchronous and reac ve model
Caching and batching
Integrates with Feign using Feing Ribbon
11 . 5
128. REGISTER SEVERAL NUMBER APIS INREGISTER SEVERAL NUMBER APIS IN
CONSULCONSUL
final ImmutableRegistration registration =
ImmutableRegistration.builder()
.id(UUID.randomUUID().toString())
.name(NUMBER_API_NAME)
.address(numberApiHost)
.port(numberApiPort)
.check(http(numberApiHost + ":" + numberA
.build();
agentClient.register(registration);
11 . 6
129. BOOK API REGISTERS THE RIBBON LOAD-BOOK API REGISTERS THE RIBBON LOAD-
BALANCERBALANCER
private void registerLoadBalancer(final Consul consul) {
try {
final DefaultClientConfigImpl clientConfig = getClientCon
clientConfig.set(NFLoadBalancerClassName, "com.netflix.lo
clientConfig.set(NFLoadBalancerRuleClassName, "com.netflix
clientConfig.set(ServerListRefreshInterval, 10000);
final DynamicServerListLoadBalancer dynamicServerListLoad
(DynamicServerListLoadBalancer) registerNamedLoad
dynamicServerListLoadBalancer.setServerListImpl(new Numbe
dynamicServerListLoadBalancer.enableAndInitLearnNewServer
} catch (final ClientException e) {
e.printStackTrace();
}
}
11 . 7
130. BOOK API GETS THE REGISTEREDBOOK API GETS THE REGISTERED
INSTANCESINSTANCES
final HealthClient healthClient = consul.healthClient();
final List<ServiceHealth> nodes =
healthClient.getHealthyServiceInstances("CONSUL_NUMBER_API").
final List<Server> servers = nodes.stream()
.map(serviceHealth -> new Serve
URI.create(serviceHealth.ge
serviceHealth.getService().
.collect(toList());
11 . 8
131. BOOK API LOAD-BALANCES WITH FEIGNBOOK API LOAD-BALANCES WITH FEIGN
return HystrixFeign.builder()
.logger(new Logger.JavaLogger())
.logLevel(Logger.Level.FULL)
.target(LoadBalancingTarget.create(NumbersApi.class, "http://
11 . 9
138. SEVERAL TO CHOOSE FROMSEVERAL TO CHOOSE FROM
Amazon API Gateway
Apigee Gateway
Tribestream Access Gateway
12 . 4
139. WHAT IS TRIBESTREAM ACCESS GATEWAY?WHAT IS TRIBESTREAM ACCESS GATEWAY?
API Security For Microservices
OAuth 2
JWT
H p Signatures
Route Microservices
Load Balancer
12 . 5
140. JSON WEB TOKENJSON WEB TOKEN
Lightweight token
Contains « some » data (claims)
Base64
Can be Encrypted
Passed in the HTTP Header
Sent at each request
12 . 6
144. ASKING FOR A TOKENASKING FOR A TOKEN
@Injectable()
export class AuthService {
private _jwt: string;
login(login: string, password: string): Observable<any> {
var headers: Headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlenc
var body = `username=${login}&password=${password}`;
return this.http
.post(this.basePath, body, requestOptions)
.map((response: Response) => {
if (response.status !== 200) {
return undefined;
12 . 10
145. PASSING THE TOKEN AROUNDPASSING THE TOKEN AROUND
public _deleteWithHttpInfo(id: number, extraHttpRequestParams?: a
const path = this.basePath + '/books/${id}'.replace('${' + 'id
let queryParameters = new URLSearchParams();
var jwt = this.authService.jwt;
if (jwt != null) {
this.defaultHeaders.set('Authorization', jwt);
}
let requestOptions: RequestOptionsArgs = new RequestOptions({
method: RequestMethod.Delete,
headers: headers,
search: queryParameters,
withCredentials:this.configuration.withCredentials
});
return this http request(path requestOptions);
12 . 11