Microservice Architecture

with

Spring Cloud

Kod Gemisi, Jun 2017
destan@kodgemisi.com
  • Destan Sarpkaya
  •  
  • Java (Spring Framework, JavaEE)

  • Javascript (React, Angular)

  • Consultant

How many of you used...

  • Java Enterprise Edition (JavaEE)
  • Spring Framework or Spring Boot
  • Service Oriented Architecture (SOA)
  • Microservices

Microservices

x

The Ultimate solution?

  • Hype?
  • Fancy name for good old SOA?
  • Cheaper?
  • Easier to develop?

Microservices

Image source: https://martinfowler.com/articles/microservices.html

Microservices

Microservices don't solve complexity

just move it somewhere else  

Not just does this just move complexity around, it moves it to a place that's less explicit and harder to control.

M. Fowler

Good parts

  • Easy to grasp
  • Easy to rewrite
  • Easy to extend (the whole system)
  • Easy to update
  • Effective scaling
  • Polyglot, no tech-lock-in
  • Easy to adapt

Bad parts

  • Hard to grasp (the async logic)
  • Hard to debug (async and distributed)
  • Latency
  • Operational burden
  • Hard to log
  • Requires organizational changes
  • Redundant data
  • No transactions (Eventual Consistency)
  • High initial investment

The

The

Spring Cloud

  • Bunch of libraries implementing common patterns
  • Built on Spring Boot
  • Convention over configuration
    • Reasonable defaults

Spring Cloud

provides tools for developers to quickly build some of the common patterns in distributed systems

Spring Cloud won't solve your organizational problems!

Talk is Cheap

Show Me the Code

Let's Build Some Microservices

Personal Money Tracker

https://github.com/sqshq/PiggyMetrics

Image source: https://github.com/sqshq/PiggyMetrics

Account Service

Head first implementation!

@Repository
public interface AccountRepository extends CrudRepository<Account, String> {

	Account findByName(String name);

}
@RestController
public class AccountController {

	@Autowired
	private AccountService accountService;

	@RequestMapping(path = "/{name}", method = RequestMethod.GET)
	public Account getAccountByName(@PathVariable String name) {
		return accountService.findByName(name);
	}


	@RequestMapping(path = "/", method = RequestMethod.POST)
	public Account createNewAccount(@Valid @RequestBody User user) {
		return accountService.create(user);
	}

        //...
}

Account service up & running

Notification Service

Notification service up & running

We have 2 microservices

What about scaling?

Having Multiple Instances

via dynamic provisioning

Image source: https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture

Service Discovery

Image source: https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture

Service Discovery

Image source: https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance#high-level-architecture

Netflix's Eureka

@SpringBootApplication
@EnableEurekaServer
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}
server:
  port: ${PORT:8761}

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
    server:
      waitTimeInMsWhenSyncEmpty: 0
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
@EnableEurekaServer

Eureka Client

@SpringBootApplication
@EnableDiscoveryClient
@Configuration
public class AccountApplication {

    //...
}
@EnableDiscoveryClient
eureka:
  instance:
    prefer-ip-address: true
  client:
    serviceUrl:
      defaultZone: http://registry:8761/eureka/
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

Keeping track of dynamic instances is done.

What about selecting one?

 

Netflix's Ribbon

Client Side Load Balancing

Feign Client + Ribbon

package com.piggymetrics.notification.client;

// imports...

@FeignClient(name = "account-service")
public interface AccountServiceClient {

	@RequestMapping(method = RequestMethod.GET, 
                                    value = "/accounts/{accountName}",
                                    consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)

	String getAccount(@PathVariable("accountName") String accountName);
}
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

Using Feign Client

@Service
public class NotificationServiceImpl implements NotificationService {

  @Autowired
  private AccountServiceClient client;

  @Override
  public void sendBackupNotifications() {

        //...

    recipients.forEach(recipient -> CompletableFuture.runAsync(() -> {
      try {
        String attachment = client.getAccount(recipient.getAccountName());
        //...
      } catch (Throwable t) {
        log.error("an error during backup notification for {}", recipient, t);
      }
    }));
  }
}
@RestController
public class SampleController {

  @Autowired
  private RestTemplate restTemplate;

  @Autowired
  @LoadBalanced
  private RestTemplate loadBalanced;

  @RequestMapping("/sample-product")
  public Product hi() {
    Product sampleProduct = 
            loadBalanced.getForObject("http://izmir-store/sample", Product.class);
    return sampleProduct;
  }
}

Feign Alternative: @LoadBalanced Rest Template

What if configuration changes?

  • We need to centralize configuration
  • Remember The Twelve-Factor App?
  • Can't use hard-coded anymore
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {

  public static void main(String[] args) {
    SpringApplication.run(ConfigApplication.class, args);
  }
}

Spring Config Server

spring.cloud.config.server.git.uri=${HOME}/Desktop/config
spring.cloud.config.server.native.search-locations: classpath:/shared

Configuring Our Config Server

OR

spring:
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/shared

Config Client

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

Refresh configs without restart

@RefreshScope
@RestController
class MessageRestController {

    @Value("${message:Hello default}")
    private String message;

    @RequestMapping("/message")
    String getMessage() {
        return this.message;
    }
}

http://localhost:8080/refresh

Shit Happens!

Netflix's Hystrix Circuit Breaker

Image source: https://martinfowler.com/bliki/CircuitBreaker.html

@SpringBootApplication
@EnableCircuitBreaker
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}
@Component
public class StoreIntegration {

    @HystrixCommand(fallbackMethod = "defaultStores")
    public Object getStores(Map<String, Object> parameters) {
        //do stuff that might fail
    }

    public Object defaultStores(Map<String, Object> parameters) {
        return /* something useful */;
    }
}

Circuit Breaker: Hystrix Dashboard

spring-cloud-starter-hystrix-dashboard

+

@EnableHystrixDashboard

Hystrix Dashboard

Data Aggregation

  • Need multiple service call in one request
  • Need concurency
@RestController
public class SampleController {

  @Autowired
  private RestTemplate restTemplate;

  @PostMapping("/users")
  public User createUser(@RequestBody User user) {


    BlackList blackList = 
            loadBalanced.getForObject(blackListUrl, BlackList.class);

    ValidationResult validationResult = 
            loadBalanced.getForObject(tcknValidationUrl, ValidationResult.class);

    // do stuff with both objects

    return user;
  }
}
@RestController
public class SampleController {

  @Autowired
  private AsyncRestTemplate asyncRestTemplate;

  @PostMapping("/users")
  public User createUser(@RequestBody User user) {


    ListenableFuture<ResponseEntity<BlackList>> future1 = asyncRestTemplate
        .getForEntity(blackListUrl, BlackList.class);

    ListenableFuture<ResponseEntity<ValidationResult>> future2 = asyncRestTemplate
        .getForEntity(tcknValidationUrl, ValidationResult.class);

    BlackList blackList = future1.get().getBody();
    ValidationResult validationResult = future2.get().getBody();

    // do stuff with both objects

    return user;
  }
}
@RestController
public class SampleController {

  @Autowired
  private WebClient webClient;

  @PostMapping("/users")
  public Mono<User> createUser(@RequestBody User user) {

    Mono<BlackList> blackList = webClient
        .exchange(blackListRequest)
        .then(response -> response.body(toMono(User.class)));
    
    Mono<ValidationResult> validationResult = webClient
        .exchange(tcknValidationRequest)
        .then(response -> response.body(toMono(Account.class)));
    
    Mono<User> user = 
            Mono.zip(values -> { /*do stuff with values[0] and values[1] */ }), user, account);

    return user;
  }
}

Image source: https://developers.redhat.com/blog/2016/12/09/spring-cloud-for-microservices-compared-to-kubernetes/

Questions?

destan@kodgemisi.com

Microservices Allianz 2017

By KodGemisi

Microservices Allianz 2017

  • 530