Home

Awesome

Cloud Native Java Workshop

The accompanying code for this workshop is on Github

Setup

microservices, for better or for worse, involve a lot of moving parts. Let's make sure we can run all those things in this lab.

1. "Bootcamp"

In this lab we'll take a look at building a basic Spring Boot application that uses JPA and Spring Data REST. We'll look at how to start a new project, how Spring Boot exposes functionality, and how testing works.

Questions:

2. Making a Spring Boot application Production Ready

(Multi-day workshops only)

Code complete != production ready! If you've ever read Michael Nygard's amazing tome, Release It!, then you know that the last mile between being code complete and being to production is much longer than anyone ever anticipates. In this lab, we'll look at how Spring Boot is optimized for the continuous delivery of applications into production.

3. The Config Server

The 12 Factor manifesto talks about externalizing that which changes from one environment to another - hosts, locators, passwords, etc. - from the application itself. Spring Boot readily supports this pattern, but it's not enough. In this lab, we'll look at how to centralize, externalize, and dynamically update application configuration with the Spring Cloud Config Server.

We will need to modify the reservation-service's pom.xml in order to make it a config client. To do this, add the following to your pom.xml of the reservation-service from step #1.

Example:

<dependencyManagement>
    <dependencies>
      ...
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-parent</artifactId>
        <version>Brixton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
</dependencyManagement>

IMPORTANT: Make sure that you make this modification in the </dependencyManagement> block. There are two places where Maven dependencies are added in a pom.xml and some people tend to get confused at this step. If you need help, please raise your hand and flag down an instructor.

Next, add org.springframework.cloud:spring-cloud-starter-config to the reservation-service. We'll add this dependency declaration to the general dependencies tag, unlike the previous modification to </dependencyManagement>.

Example:

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
  </dependency>
..
</dependencies>

Next:

Create a boostrap.properties that lives in the same place as application.properties and discard the application.properties file. Now we need only to tell the Spring application where to find the Config Server, with the property spring.cloud.config.uri=@config.server:http://localhost:8888@, and how to identify itself to the Config Server and other services, later, with spring.application.name.

Now, run the Config Server:

We'll copy and paste bootstrap.properties for each subsequent module, changing only the spring.application.name as appropriate.

In the reservation-service, create a MessageRestController and annotate it with @RefreshScope. Inject the ${message} key and expose it as a REST endpoint, /message.

Trigger a refresh of the message using the /refresh endpoint.

EXTRA CREDIT: Install RabbitMQ server and connect the microservice to the the Spring Cloud Stream-based event bus and then triggering the refresh using the /bus/refresh.

4. Service Registration and Discovery

In the cloud, services are often ephemeral and it's important to be able to talk to these services abstractly, without worrying about the host and ports for these services. At first blush, this seems like a use-case for DNS, but DNS fails in several key situations. How do we know if there's a service waiting on the other end of a DNS-mapped service that can respond? How do we support more sophisticated load-balancing than DNS + a typical loadbalancer can handle (e.g.: round-robin)? How do we avoid the extra hop outside of most cloud environments required to resolve DNS? For all of these and more, we want the effect of DNS - a dispatch table - without being coupled to DNS. We'll use a service registry and Spring Cloud's DiscoveryClient abstraction.

you have a service registry and now you have a single service registered and advertising its presence. Let's take advantage of that in an edge service, which we'll call reservation-client.

5. Edge Services: API gateways (circuit breakers, client-side load balancing)

Edge services sit as intermediaries between the clients (smart phones, HTML5 applications, etc) and the service. An edge service is a logical place to insert any client-specific requirements (security, API translation, protocol translation) and keep the mid-tier services free of this burdensome logic (as well as free from associated redeploys!)

Proxy requests from an edge-service to mid-tier services with a microproxy. For some classes of clients, a microproxy and security (HTTPS, authentication) might be enough.

API gateways are used whenever a client - like a mobile phone or HTML5 client - requires API translation. Perhaps the client requires coarser grained payloads, or transformed views on the data

The code works, but it assumes that the reservation-service will always be up and responding to requests. We need to be a bit more defensive in any code that clients will connect to. We'll use a circuit-breaker to ensure that the reservation-client does something useful as a fallback when it can't connect to the reservation-service.

6. To the Cloud!

Spring Cloud helps you develop services that are resilient to failure - they're fault tolerant. If a service goes down, they'll degrade gracefully, and correctly expand to accommodate the available capacity. But who starts and stops these services? You need a platform for that. In this lab, we'll use a free trial account at Pivotal Web Services to demonstrate how to deploy, scale and heal our services.

You may generate the manifest.yml manually or you may use a tool like Spring Tool Suite's Spring Boot Dashboard which will, on deploy, prompt you to save the deployment configuration as a manifest.yml.

Multi-day workshop:

As you push new instances, you'll get new routes because of the configuration in the manifest.yml which specifies host is "...-${random-word}". When creating the user-provided-services (cf cups ..) be sure to choose only the first route. To delete orphaned routes, use cf delete-orphaned-routes

if you're running the cf cups commands, remember to quote and escape correctly, e.g.: cf cups "{ \"uri":\"..\" }"

if you need to delete an application, you can use cf d _APP_NAME_, where _APP_NAME_ is your application's logical name. If you want to delete a service, use cf ds _SERVICE_NAME_ where _SERVICE_NAME_ is a logical name for the service. Use -f to force the deletion without confirmation.

7. Streams

while REST is an easy, powerful approach to building services, it doesn't provide much in the way of guarantees about state. A failed write needs to be retried, requiring more work of the client. Messaging, on the other hand, guarantees that eventually the intended write will be processed. Eventual consistency works most of the time; even banks don't use distributed transactions! In this lab, we'll look at Spring Cloud Stream which builds atop Spring Integration and the messaging subsystem from Spring XD. Spring Cloud Stream provides the notion of binders that automatically wire up message egress and ingress given a valid connection factory and an agreed upon destination (e.g.: reservations or orders).

This will install a RabbitMQ instance that is available at $DOCKER_IP. You'll also be able to access the console, which is available http://$DOCKER_IP:15672. The username and password to access the console are guest/guest.

Sources - like water from a faucet - describe where messages may come from. In our example, messages come from the reservation-client that wishes to write messages to the reservation-service from the API gateway.

Sinks receive messages that flow to this service (like the kitchen sink into which water from the faucet flows).

8. Distributed Tracing with Zipkin

Distributed tracing lets us trace the path of a request from one service to another. It's very useful in understanding where a failure is occurring in a complex chain of calls.

Now, let's connect our services to the Zipkin service.

9. Consumer Driven Contract Testing

we've built a trivial API with an even more trivial client (thanks to the RestTemplate or Feign). We've done a good job on day one of our journey. What happens on day two or at any point down the line after the API has changed but the client that uses it has updated accordingly? What happens when the producer of the API changes the API? Does this break the client? It's important that we capture such breaking changes as early and often as possible. In a monolithic application the incompatible updates to the producer of an API would be caught on the first compile. Refactoring would help us prevent these problems, as well. In a distributed systems world, these incompatible changes are harder to catch. They get caught in the integration tests. integration tests are among the slowest of the tests you should have in your system. They're towards the top of the testing pyramid because they're expensive - both in terms of time and computational resources. In order to run the tests we'd need to run both client and service and all supporting infrastructure. This is a worst-case scenario; organizations move to microservices to accelerate feedback (which in turn yields learning and improvement), not to reduce it! What we need is some way to capture breaking changes that keeps both producer and consumer in sync and that doesn't constrain velocity of feedback. Spring Cloud Contract, and consumer driven contracts and consumer driven contract testing, make this work easier. The idea is that contract definitions are used to capture the expected behavior of an API for a particular client. This may include all the quirks of particular clients, and it may incluhde older clients using older APIs. A producer may capture as many contract scenarios as needed. These contracts are enforced bilaterally. On the producer side, the Spring Cloud Contract verifier turns the contract into a Spring MVC Test Framework test that fails if the actual API doesn't work as the contract stipulates. On the consumer, clients can run test against actual HTTP (or messaging-based) APIs that are themselves stubs. These stubs are stubs - that is, there's no real business logic behind them. Just preconfigured responses defined by the contracts. As the stub is defined entirely by the contract, it is trivially cheap to run the stub APIs and exercise clients against them. As the stubs are only ever available if the producer passes all its tests, this ensures that the client is building and testing against a reflection of the latest and actual API, not the understanding of the API implied when the client test was originally written.

Spring Cloud Contract supports clients and services written with Spring in mind but can they help us when developing clients in other languages?

10. Security

in a distributed systems world, multiple clients might access multiple services and it becomes very important to have an easy-to-scale answer to the question: which clients may access which resources? The solution for this problem is single signon: all requests to a given resource present a token that may be redeemed with a centralized authentication service. We'll build an OAuth 2-powered authorization service and that secure our edge service to talk to it.

your application will need to talk to an authentication service that understands OAuth. You could use any of a number of valid services, like Github, Facebook, Google, or even an API Gateway product like Apigee. In our case, we'll connect to a custom Spring Security OAuth-powered auth-service.

11. Optimize for Velocity and Consistency

Thus far we've looked at building applications with Spring Boot and Spring Cloud, layering in the various technologies as we've learned about them and needed them. It's been a fun process of discovery (hopefully!), but this shouldn't be required for every developer. Instead, you should package up best-practices as Spring Boot starter dependencies and auto-configurations. Codifying these best practices helps get past the endless list of non-functional requirements required to go to production.

12. Log Aggregation and Analysis with ELK

For all the fancy new technologies we have today, the venerable log file still reigns supreme. Modern logs, are more than strings blurted out by code in the dead of night. They should be structured data. In this exercise we'll write our logs using Logstash and then publish them to an ElasticSearch cluster.

13. Cloud Native Data Processing with Spring Cloud Data Flow