Getting back in the ring! Comparing Eclipse Microprofile to Spring Boot

Getting back in the ring! Comparing Eclipse Microprofile to Spring Boot

After many years of arguments debating the differences between Spring and JEE, it seemed that the Spring Framework finally came out on top. With the rise of cloud environments and microservices, application servers had nothing to offer against Spring Boot.
In 2016, the Eclipse foundation presented a new contender: MicroProfile. This approach for Cloud-Native Java, based on the proven Java EE technology, was advanced with an ambitious roadmap to catch up on Spring Boot. Time for a closer look: How do both compare? We are exploring similarities and differences in development, configuration, and operations and take a look at how well the MicroProfile project prepared enterprise Java for the cloud.

A6569b59297bd3e7cbda6de64e1e506b?s=128

Tim Zöller

May 09, 2019
Tweet

Transcript

  1. Getting back in the ring 09.05.2019, Mainz

  2. Tim Zöller what do I do? 2 Mainz Freiburg Hamburg

    IT Consultant / Team Lead Java Developer since 2008 Co-Founder of JUG Mainz javahippie @javahippie
  3. Speed Memory PACKAGE SIZE A WINNER Things I won’t cover

    … although They might be important 3 Feel free to discuss these aspects with me afterwards
  4. The Scenario

  5. Online shop is running on own infrastructure Want to adapt

    new technology Nerd Fashion Inc. Imaginary Online Shop 5
  6. Sarah Senior Software Developer Matthew Senior Software Developer ABOUT THE

    TEAM Employees at Nerd Fashion Inc. 6 Helen Department Lead ?
  7. Application Landscape 7 Customer Service (existing) Complaint Service Order Service

    REST REST REST REST
  8. Communicate via REST, Document Interfaces 04 03 02 01 Requirements

    Apply resilience measurements Provide health status and metrics Configure everything externally 8
  9. Initializing

  10. Maven or Gradle Java, Kotlin, Groovy Java 8 or 11

    Many(!) libraries Spring Initializr Access via Web or API 10
  11. Specification Examples Server implementations Java 8 only No additional libraries

    Microprofile starter Still in beta 11
  12. CONFIGURATION PROPERTIES

  13. Configuration … 13 … Should not require the application to

    be repackaged … should provide Multiple mechanisms … should be changeable at runtime
  14. Spring Boot - Config Type safe Configuration Classes 14 @Value("customerservice.uri")

    private String clientServiceUri; @Autowired private Environment env; String uri = env.getProperty("customerservice.uri"); 1 2
  15. Spring Boot - Config Property Sources 15 1.Devtools global settings

    properties 2.@TestPropertySource annotations on your tests. 3.properties attribute on your tests. 4.Command line arguments. 5.Properties from SPRING_APPLICATION_JSON 6.ServletConfig init parameters. 7.ServletContext init parameters. 8.JNDI attributes from java:comp/env. 9.Java System properties 10.OS environment variables. 11.A RandomValuePropertySource that has properties only in random.*. 12.Profile-specific application properties outside of your packaged jar 13.Profile-specific application properties packaged inside your jar 14.Application properties outside of your packaged jar 15.Application properties packaged inside your jar 16.@PropertySource annotations on your @Configuration classes. 17.Default properties ORDINAL
  16. Spring Boot - Config Write your own property source 16

    public class MyPropertySource extends PropertySource<Map<String, String>> { Map<String, String> myMap = new HashMap<>(); public MyPropertySource() { super("myProperties"); } @Override public Object getProperty(String name) { return myMap.get(name); } } 2 3 1
  17. Spring Boot - Config WIRE your own property source 17

    @Configuration public class PropertySourceConfig { @Autowired private ConfigurableEnvironment env; @Bean @Lazy(false) public MyPropertySource test() { MyPropertySource propertySource = new MyPropertySource(); env.getPropertySources().addBefore("systemProperties", propertySource); return propertySource; } } 1 2
  18. Microprofile Config How to access the Config 18 @ConfigProperty(name =

    "customerservice.uri") // Injection Time private String clientServiceUri; @ConfigProperty(name = "customerservice.uri") // Just in time private Provider<String> clientServiceUriProdiver; clientServiceUriProvider.get(); Config config = ConfigProvider.getConfig(); // Programmatically config.getValue("customerservice.uri", String.class); 1 2 3
  19. Microprofile Config Config Sources 19 Static Properties File Environment Variables

    System Properties Your Own ConfigSource 400 300 100 ??? Ordinal
  20. Microprofile Config Write your own ConfigSource 20 public class MyConfigSource

    implements ConfigSource { @Override public Map<String, String> getProperties() {…} @Override public int getOrdinal() {…} @Override public String getValue(String propertyName) {…} @Override public String getName() {…} } 1 2 3 4 5
  21. CALLING REST SERVICES

  22. REST Clients … 22 … should be type safe …

    should require low configuration
  23. Spring Boot - REST TEMPLATE Simple construction of clients 23

    1 2 @Component public class CustomerClient { @Value("${customerservice.uri}") String resourceUrl; RestTemplate restTemplate; public CustomerClient() { this.restTemplate = new RestTemplate(); } public Customer findCustomerByNumber(String customerNumber) { String path = resourceUrl + "/" + customerNumber; ResponseEntity<Customer> response = restTemplate.getForEntity(path, Customer.class); return response.getBody(); } }
  24. Spring Boot - Feign Declarative Clients with feign 24 @FeignClient(name

    = "customers", url = "${customerservice.uri}") public interface CustomerService { @GetMapping(value = "/{customerNumber}", produces = "application/json") Customer findCustomerByNumber(@PathVariable("customerNumber") String customerNumber); @GetMapping(value = "/search", produces = "application/json") List<Customer> searchCustomerByNumber(@RequestParam("searchString") String searchString); } 1 2 3
  25. Microprofile - JSON-B Define the interface to be called 25

    @Path("/customer") public interface CustomerService { @GET @Path("/{customerNumber}") @Produces("application/json") Customer findCustomerByNumber(@PathParam("customerNumber") String customerNumber); @GET @Path("/search") @Produces("application/json") List<Customer> searchCustomerByNumber(@QueryParam("searchString") String searchString); } 1 2 3
  26. Microprofile - JSON-B Build the REST Client 26 @ApplicationScoped public

    class CustomerClient { @Inject @ConfigProperty(name = "customerservice.uri") private URI clientServiceUri; @Produces public CustomerService build() { return RestClientBuilder.newBuilder() .baseUri(clientServiceUri) .build(CustomerService.class); } } 1 2
  27. DIRECT COMPARISON

  28. DIRECT COMPARISON Pretty similar 28 @FeignClient(name = "customers", url =

    "${customerservice.uri}") public interface CustomerService { @GetMapping(value = "/{customerNumber}", produces = "application/json") Customer findCustomerByNumber(@PathVariable("customerNumber") String customerNumber); @GetMapping(value = "/search", produces = "application/json") List<Customer> searchCustomerByNumber(@RequestParam("searchString") String searchString); } 1 2 3
  29. DIRECT COMPARISON Pretty similar 29 @Path("/customer") public interface CustomerService {

    @GET @Path= "/{customerNumber}") @Produces("application/json") Customer findCustomerByNumber(@PathParam("customerNumber") String customerNumber); @GET @Path("/search") @Produces("application/json") List<Customer> searchCustomerByNumber(@QueryParam("searchString") String searchString); } 1 2 3
  30. PROVIDING REST SERVICES

  31. REST Services … 31 … should be type safe …

    should require low configuration … should provide documentation
  32. Spring Boot - REST CONTROLLER Defining a REST Controller 32

    @RestController @RequestMapping("/api/order") public class OrderController { @Autowired private OrderRepository orderRepository; @Autowired private CustomerService customerService; @PostMapping public ResponseEntity<Order> create(@RequestParam("customerNumber") String customerNumber, @RequestParam("orderText") String orderText) { Customer customer = customerService.findCustomerByNumber(customerNumber); Order order = new Order(customerNumber, orderText); orderRepository.createOrder(order); return ResponseEntity.created(constructCreationUri(order)).build(); } } 1 2 3 4
  33. Spring Boot - REST CONTROLLER Swagger documentation 33 GET /v2/api-docs

    … "/api/order/{orderNumber}": { "get": { "tags": ["order-controller“], "summary": "getOrder", "operationId": "getOrderUsingGET", "consumes": [ "application/json"], "parameters": [ { "name": "orderNumber", "in": "path", "description": "orderNumber", "required": true, "type": "string" } ] …
  34. Spring Boot - REST CONTROLLER Swagger documentation 34 GET /v2/api-docs

    … "/api/order/{orderNumber}": { "get": { "tags": ["order-controller“], "summary": "getOrder", "operationId": "getOrderUsingGET", "consumes": [ "application/json"], "parameters": [ { "name": "orderNumber", "in": "path", "description": "orderNumber", "required": true, "type": "string" } ] … Library needed! <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency>
  35. Microprofile - JAX-RS Defining a REST Controller 35 @Path("/complaint") public

    class ComplaintController { @Inject private ComplaintRepository repository; @Inject private CustomerService customerService; @POST public Response create(@QueryParam("caption") String caption, @QueryParam("content") String content, @QueryParam("customerNumber") String customerNumber) { Customer customer = customerService.findCustomerByNumber(customerNumber); Complaint complaint = repository.createComplaint(caption, content, customer); return Response.created(this.constructCreationUri(complaint)).build(); } … } 1 2 3 4
  36. Microprofile - JAX-RS OPEN API Support 36 GET /openapi {

    "openapi": "3.0.1", "info": { "title": "Generated API", "version": "1.0" }, "paths": { "/api/complaint": { "get": { "responses": { "200": { "description": "OK", "content": { "*/*": {} } } …
  37. DIRECT COMPARISON

  38. DIRECT COMPARISON PRETTY SIMILAR 38 @RestController @RequestMapping("/api/order") public class OrderController

    { @Autowired private OrderRepository orderRepository; @Autowired private CustomerService customerService; @PostMapping public ResponseEntity<Order> create(@RequestParam("customerNumber") String customerNumber, @RequestParam("orderText") String orderText) { Customer customer = customerService.findCustomerByNumber(customerNumber); Order order = new Order(customerNumber, orderText); orderRepository.createOrder(order); return ResponseEntity.created(constructCreationUri(order)).build(); } } 1 2 3 4
  39. DIRECT COMPARISON PRETTY SIMILAR 39 @Path("/api/order") public class OrderController {

    @Inject private OrderRepository orderRepository; @Inject private CustomerService customerService; @POST public Response create(@QueryParam("customerNumber") String customerNumber, @QueryParam("orderText") String orderText) { Customer customer = customerService.findCustomerByNumber(customerNumber); Order order = new Order(customerNumber, orderText); orderRepository.createOrder(order); return Response.created(constructCreationUri(order)).build(); } … } 1 3 4 2
  40. RESILIENCE MEASURES

  41. RESILIENCE … 41 … should react on different error scenarios

    … should enable the developer to provide fallbacks
  42. Hystrix Resilience4j Spring Boot – Fault Tolerance An overview 42

  43. Spring Boot – Hystrix Application 43 @Component public class CustomerClient

    { @Value("${customerservice.uri}") String resourceUrl; RestTemplate restTemplate; public CustomerClient() { this.restTemplate = new RestTemplate(); } @HystrixCommand(raiseHystrixExceptions = HystrixException.RUNTIME_EXCEPTION) public Customer findCustomerByNumber(String customerNumber) { String path = resourceUrl + "/" + customerNumber; ResponseEntity<Customer> response = restTemplate.getForEntity(path, Customer.class); return response.getBody(); } } 1
  44. Retry Try and call the same method again Rate Limiter

    Define the number of calls per timeframe Fallback React to an outage with a fallback Circuitbreaker Let services fail fast and don’t call them again Bulkhead Limit concurrent requests Spring Boot – Resilience4j Mechanisms 44
  45. Spring Boot – Fault Tolerance Resilience4j – Circuit breaker 45

    @RestController @RequestMapping("/api/order") public class OrderController { @Autowired private CustomerClient customerClient; @Autowired private CircuitBreakerConfig cbConfig; private Function<String, Customer> clientCall; @PostConstruct public void init() { CircuitBreaker circuitBreaker = CircuitBreakerRegistry.of(cbConfig).circuitBreaker("customer-client"); clientCall = CircuitBreaker.decorateFunction(circuitBreaker, customerClient::findCustomerByNumber); } private Customer findCustomer(String customerNumber) { return clientCall.apply(customerNumber); } 1 2 3
  46. Spring Boot – Fault Tolerance Resilience4j – Circuit breaker 46

    1 @Component public class CustomerClient { @Value("${customerservice.uri}") String resourceUrl; RestTemplate restTemplate; public CustomerClient() { this.restTemplate = new RestTemplate(); } @CircuitBreaker(name = "customer-cb") @RateLimiter(name = "customer-limiter") public Customer findCustomerByNumber(String customerNumber) { String path = resourceUrl + "/" + customerNumber; ResponseEntity<Customer> response = restTemplate.getForEntity(path, Customer.class); return response.getBody(); } }
  47. Spring Boot – Fault Tolerance Resilience4j – Circuit breaker 47

    1 @Component public class CustomerClient { @Value("${customerservice.uri}") String resourceUrl; RestTemplate restTemplate; public CustomerClient() { this.restTemplate = new RestTemplate(); } @CircuitBreaker(name = "customer-cb") @RateLimiter(name = "customer-limiter") public Customer findCustomerByNumber(String customerNumber) { String path = resourceUrl + "/" + customerNumber; ResponseEntity<Customer> response = restTemplate.getForEntity(path, Customer.class); return response.getBody(); } } Library needed! <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>0.13.2</version> </dependency>
  48. Spring Boot – Fault Tolerance Resilience4j – Circuit breaker 48

    1 @Component public class CustomerClient { @Value("${customerservice.uri}") String resourceUrl; RestTemplate restTemplate; public CustomerClient() { this.restTemplate = new RestTemplate(); } @CircuitBreaker(name = "customer-cb") @RateLimiter(name = "customer-limiter") public Customer findCustomerByNumber(String customerNumber) { String path = resourceUrl + "/" + customerNumber; ResponseEntity<Customer> response = restTemplate.getForEntity(path, Customer.class); return response.getBody(); } } Library needed! <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> <version>0.13.2</version> </dependency> BUT WAIT! https://github.com/spring-cloud- incubator/spring-cloud-circuitbreaker
  49. Retry Try and call the same method again Timeout Define

    a maximum wait time Fallback React to an outage with a fallback Circuitbreaker Let services fail fast and don’t call them again Bulkhead Limit concurrent requests Microprofile - FAULT TOLERANCE Mechanisms 49
  50. Microprofile - FAULT TOLERANCE Example with Timeout, Circuit breaker and

    fallback 50 @Path("/customer") public interface CustomerService { @GET @Path("/{customerNumber}") @Produces("application/json") @Timeout(value = 500, unit = ChronoUnit.MILLIS) @CircuitBreaker(requestVolumeThreshold = 5, failureRatio = 0.25, delay = 5000L, successThreshold = 1) @Fallback(CustomerFallbackHandler.class) Customer findCustomerByNumber(@PathParam("customerNumber") String customerNumber); } 1 2 3 4
  51. HEALTH CHECK

  52. Health Check … 52 … should be accessible via HTTP

    … should indicate health status to K8s … should expose complex health conditions
  53. Spring – Actuator Health Constructing the health endpoint 53 @Component

    public class HealthCheck implements HealthIndicator { @Override public Health health() { Status status = (jdbcConnectionIsUp() && amqpIsConnected()) ? Status.UP : Status.DOWN; return Health .status(status) .withDetail("datapoint", "my-app-data") .build(); } } 2 3 1
  54. Spring – Actuator Health Payload 54 GET /actuator/health { "status":

    "UP", "details": { "healthCheck": { "status": "UP", "details": { "datapoint": "my-app-data" } }, … } }
  55. Microprofile Health Constructing the Health Endpoint 55 @Health @ApplicationScoped public

    class ServiceHealthCheck implements HealthCheck { @Override public HealthCheckResponse call() { return HealthCheckResponse.named(ServiceHealthCheck.class.getSimpleName()) .withData(“datapoint", “my-app-data") .state(jdbcConnectionIsUp() && amqpIsConnected()) .build(); } } 1 2 3 4
  56. Microprofile Health PAYLOAD 56 GET /health { "outcome": "UP", "checks":

    [ { "name": "ServiceHealthCheck", "state": "UP", "data": { “datapoint": “my-app-data" } } ] }
  57. DIRECT COMPARISON

  58. DIRECT COMPARISON QUITE SIMILAR 58 GET /actuator/health { "status": "UP",

    "details": { "healthCheck": { "status": "UP", "details": { "datapoint": "my-app-data" } }, … } }
  59. DIRECT COMPARISON QUITE SIMILAR 59 GET /health { "outcome": "UP",

    "checks": [ { "name": "ServiceHealthCheck", "state": "UP", "data": { “datapoint": “my-app-data" } } ] }
  60. METRICS

  61. METRICS … 61 … should be accessible via HTTP …

    should be integrated with monitoring solutions … should expose complex metrics
  62. Spring – Actuator Metrics query metrics via HTTP 62 GET

    /actuator/metrics/hystrix.circuit.breaker.open { "name": "hystrix.circuit.breaker.open", "description": null, "baseUnit": null, "measurements": [ { "statistic": "VALUE", "value": 1 } ], "availableTags": […] }
  63. Spring – Actuator Metrics Add your own metrics 63 @Component

    public class OrderRepository { private Counter counter; private List<Order> orders = new ArrayList<>(); @Autowired public OrderRepository(MeterRegistry registry) { this.counter = registry.counter("metric.business.order.created"); } public Order createOrder(Order order) { this.orders.add(order); counter.increment(); return order; } } 1 2
  64. Spring – Actuator Metrics query metrics via HTTP 64 GET

    /actuator/metrics/metric.business.order.created { "name": "metric.business.order.created", "description": null, "baseUnit": null, "measurements": [ { "statistic": "COUNT", "value": 14 } ], "availableTags": [] }
  65. Microprofile METRICS BASE 65 GET /metrics/base { "classloader.totalLoadedClass.count": 14849, "cpu.systemLoadAverage":

    2.57568359375, "thread.count": 83, "classloader.currentLoadedClass.count": 14844, "jvm.uptime": 40513, "gc.PS MarkSweep.count": 3, "memory.committedHeap": 1216348160, "thread.max.count": 90, "gc.PS Scavenge.count": 12, "cpu.availableProcessors": 8, "thread.daemon.count": 14, "classloader.totalUnloadedClass.count": 5, "memory.maxHeap": 3817865216, "memory.usedHeap": 892483384, "gc.PS MarkSweep.time": 367, "gc.PS Scavenge.time": 229 }
  66. Microprofile METRICS Application 66 GET /metrics/application { "ft.{…}.CustomerService.findCustomerByNumber.circuitbreaker.open.total": 0, "ft.{…}CustomerService.findCustomerByNumber.circuitbreaker.closed.total":

    16354121928, "ft.{…}.CustomerService.findCustomerByNumber.invocations.total": 13, "ft.{…}.CustomerService.findCustomerByNumber.circuitbreaker.halfOpen.total": 0, "ft.{…}.CustomerService.findCustomerByNumber.fallback.calls.total": 12, "ft.{…}.CustomerService.findCustomerByNumber.circuitbreaker.callsSucceeded.total": 13, "ft.{…}.CustomerService.findCustomerByNumber.timeout.callsNotTimedOut.total": 13, {…} }
  67. Microprofile METRICS Getting the units 67 OPTIONS /metrics/base/jvm.uptime { "jvm.uptime":

    { "unit": "milliseconds", "type": "gauge", "description": "Displays the start time of the Java virtual machine in milliseconds. This attribute displays the approximate time when the Java virtual machine started.", "displayName": "JVM Uptime", "tags": "" } }
  68. Microprofile METRICS Add your own 68 1 2 @ApplicationScoped public

    class ComplaintRepository { @Inject @Metric(name = "metric.business.complaint.created") private Counter counter; private List<Complaint> complaints = new ArrayList<>(); public Complaint createComplaint(String caption, String text, Customer customer) { Complaint complaint = new Complaint(caption, text, customer.getCustomerNumber()); complaints.add(complaint); counter.inc(); return complaint; } }
  69. Something is missing… ?

  70. Security? HARD TO COMPARE 70 MP JWT SPRING SECURITY ALL

    IN ONE JWT ONLY
  71. Summary

  72. Matthews impressions Building a new service with spring boot 72

    „I was able to complete all requirements. I liked the huge ecosystem that I could access, if I needed to. I spent some time searching for spring cloud dependencies. Openapi docs and fault tolerance needed external libraries“
  73. Sarahs impressions Building a new service with microprofile 73 „I

    was able to complete all requirements. I liked that everything could be solved with the microprofile API and the huge variety of fault tolerance methods. The startup time and the ‚Getting started‘ webpage could be better“
  74. Helens Impressions Running both services in production 74 „Both services

    are working as expected and were completed in roughly the same time. Operations is able to configure them in Kubernetes and set up monitoring with prometheus. I am not able to tell a difference.“
  75. Tims Impressions ;) After working with both 75 „Both approaches

    are viable. Spring boot has the bigger ecosystem, It can do more. Microprofile has fresh ideas based on proven methods. Microprofiles API approach with different implementations is a blessing and a curse.“
  76. Questions?