Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Tim Zöller

May 09, 2019
Tweet

More Decks by Tim Zöller

Other Decks in Technology

Transcript

  1. Getting
    back in the
    ring
    09.05.2019, Mainz

    View Slide

  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

    View Slide

  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

    View Slide

  4. The Scenario

    View Slide

  5. Online shop is running on
    own infrastructure
    Want to adapt new
    technology
    Nerd Fashion Inc.
    Imaginary Online Shop
    5

    View Slide

  6. Sarah
    Senior Software Developer
    Matthew
    Senior Software Developer
    ABOUT THE TEAM
    Employees at Nerd Fashion Inc.
    6
    Helen
    Department Lead
    ?

    View Slide

  7. Application Landscape 7
    Customer Service
    (existing)
    Complaint Service Order Service
    REST
    REST
    REST
    REST

    View Slide

  8. Communicate via REST, Document Interfaces
    04
    03
    02
    01
    Requirements
    Apply resilience measurements
    Provide health status and metrics
    Configure everything externally
    8

    View Slide

  9. Initializing

    View Slide

  10. Maven or Gradle
    Java, Kotlin, Groovy
    Java 8 or 11
    Many(!) libraries
    Spring Initializr
    Access via Web or API
    10

    View Slide

  11. Specification Examples
    Server implementations
    Java 8 only
    No additional libraries
    Microprofile starter
    Still in beta
    11

    View Slide

  12. CONFIGURATION PROPERTIES

    View Slide

  13. Configuration … 13
    … Should not require the application to be
    repackaged
    … should provide Multiple mechanisms
    … should be changeable at runtime

    View Slide

  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

    View Slide

  15. Spring Boot - Config
    Property Sources
    15
    1.Devtools global settings properties
    [email protected] 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
    [email protected] annotations on your @Configuration classes.
    17.Default properties
    ORDINAL

    View Slide

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

    View Slide

  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

    View Slide

  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 clientServiceUriProdiver;
    clientServiceUriProvider.get();
    Config config = ConfigProvider.getConfig(); // Programmatically
    config.getValue("customerservice.uri", String.class);
    1
    2
    3

    View Slide

  19. Microprofile Config
    Config Sources
    19
    Static Properties File
    Environment Variables
    System Properties
    Your Own ConfigSource
    400
    300
    100
    ???
    Ordinal

    View Slide

  20. Microprofile Config
    Write your own ConfigSource
    20
    public class MyConfigSource implements ConfigSource {
    @Override
    public Map getProperties() {…}
    @Override
    public int getOrdinal() {…}
    @Override
    public String getValue(String propertyName) {…}
    @Override
    public String getName() {…}
    }
    1
    2
    3
    4
    5

    View Slide

  21. CALLING REST SERVICES

    View Slide

  22. REST Clients … 22
    … should be type safe
    … should require low configuration

    View Slide

  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 response = restTemplate.getForEntity(path, Customer.class);
    return response.getBody();
    }
    }

    View Slide

  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 searchCustomerByNumber(@RequestParam("searchString") String searchString);
    }
    1
    2
    3

    View Slide

  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 searchCustomerByNumber(@QueryParam("searchString") String searchString);
    }
    1
    2
    3

    View Slide

  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

    View Slide

  27. DIRECT COMPARISON

    View Slide

  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 searchCustomerByNumber(@RequestParam("searchString") String searchString);
    }
    1
    2
    3

    View Slide

  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 searchCustomerByNumber(@QueryParam("searchString") String searchString);
    }
    1
    2
    3

    View Slide

  30. PROVIDING REST SERVICES

    View Slide

  31. REST Services … 31
    … should be type safe
    … should require low configuration
    … should provide documentation

    View Slide

  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 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

    View Slide

  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"
    }
    ]

    View Slide

  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!

    io.springfox
    springfox-swagger2

    View Slide

  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

    View Slide

  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": {
    "*/*": {}
    }
    }

    View Slide

  37. DIRECT COMPARISON

    View Slide

  38. DIRECT COMPARISON
    PRETTY SIMILAR
    38
    @RestController @RequestMapping("/api/order")
    public class OrderController {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private CustomerService customerService;
    @PostMapping
    public ResponseEntity 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

    View Slide

  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

    View Slide

  40. RESILIENCE MEASURES

    View Slide

  41. RESILIENCE … 41
    … should react on different error scenarios
    … should enable the developer to provide
    fallbacks

    View Slide

  42. Hystrix Resilience4j
    Spring Boot – Fault Tolerance
    An overview
    42

    View Slide

  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 response = restTemplate.getForEntity(path, Customer.class);
    return response.getBody();
    }
    }
    1

    View Slide

  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

    View Slide

  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 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

    View Slide

  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 response = restTemplate.getForEntity(path, Customer.class);
    return response.getBody();
    }
    }

    View Slide

  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 response = restTemplate.getForEntity(path, Customer.class);
    return response.getBody();
    }
    }
    Library needed!
    io.github.resilience4j
    resilience4j-spring-boot2
    0.13.2

    View Slide

  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 response = restTemplate.getForEntity(path, Customer.class);
    return response.getBody();
    }
    }
    Library needed!
    io.github.resilience4j
    resilience4j-spring-boot2
    0.13.2

    BUT WAIT!
    https://github.com/spring-cloud-
    incubator/spring-cloud-circuitbreaker

    View Slide

  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

    View Slide

  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

    View Slide

  51. HEALTH CHECK

    View Slide

  52. Health Check … 52
    … should be accessible via HTTP
    … should indicate health status to K8s
    … should expose complex health conditions

    View Slide

  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

    View Slide

  54. Spring – Actuator Health
    Payload
    54
    GET /actuator/health
    {
    "status": "UP",
    "details": {
    "healthCheck": {
    "status": "UP",
    "details": {
    "datapoint": "my-app-data"
    }
    },

    }
    }

    View Slide

  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

    View Slide

  56. Microprofile Health
    PAYLOAD
    56
    GET /health
    {
    "outcome": "UP",
    "checks": [
    {
    "name": "ServiceHealthCheck",
    "state": "UP",
    "data": {
    “datapoint": “my-app-data"
    }
    }
    ]
    }

    View Slide

  57. DIRECT COMPARISON

    View Slide

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

    }
    }

    View Slide

  59. DIRECT COMPARISON
    QUITE SIMILAR
    59
    GET /health
    {
    "outcome": "UP",
    "checks": [
    {
    "name": "ServiceHealthCheck",
    "state": "UP",
    "data": {
    “datapoint": “my-app-data"
    }
    }
    ]
    }

    View Slide

  60. METRICS

    View Slide

  61. METRICS … 61
    … should be accessible via HTTP
    … should be integrated with monitoring solutions
    … should expose complex metrics

    View Slide

  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": […]
    }

    View Slide

  63. Spring – Actuator Metrics
    Add your own metrics
    63
    @Component
    public class OrderRepository {
    private Counter counter;
    private List 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

    View Slide

  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": []
    }

    View Slide

  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
    }

    View Slide

  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,
    {…}
    }

    View Slide

  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": ""
    }
    }

    View Slide

  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 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;
    }
    }

    View Slide

  69. Something is missing… ?

    View Slide

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

    View Slide

  71. Summary

    View Slide

  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“

    View Slide

  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“

    View Slide

  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.“

    View Slide

  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.“

    View Slide

  76. Questions?

    View Slide