$30 off During Our Annual Pro Sale. View Details »

[TNT22] Java dans le cloud: avec Spring ou Quarkus?

[TNT22] Java dans le cloud: avec Spring ou Quarkus?

Né au début des années 2000, le framework Spring a mis en avant la facilité de développement. Plus récemment, il a su s'adapter aux contraintes techniques liées aux applications cloud-natives.

De son coté, Quarkus est plus récent, il est né en 2019 sur la base de MicroProfile avec un objectif clair : tirer le meilleur parti des plateformes Kubernetes en se concentrant sur un démarrage rapide et une faible empreinte mémoire.

Nous avons donc un champion et un outsider !
Maintenant lequel choisir en fonction de vos besoins et votre contexte ? Spring ou Quarkus ?

Au travers d'une démonstration "live" nous présenterons un cas contret basé sur notre expérience.
Ce dernier sera implémenté avec chacune des deux stacks.

Enfin, nous vous mettrons à contribution au travers de sondages pour dynamiser ensemble notre réflexion.

Alexandre Touret

January 22, 2022
Tweet

More Decks by Alexandre Touret

Other Decks in Technology

Transcript

  1. Java dans le Cloud: Avec Spring ou Quarkus? Jean-François JAMES

    @jefrajames Alexandre TOURET @touret_alex
  2. Un rapide sondage 2 https://bit.ly/javacloud-tnt-1

  3. Deux frameworks ▷ Spring est né dans les 2000’s ▷

    Spring Boot est né en 2014 ▷ Spring Boot a pour but de créer des applications Spring de manière simple ▷ Il a évolué au fil des ans pour répondre aux contraintes du Cloud ▷ Créé par Red Hat en 2019 ▷ Supersonic et Subatomic ! ▷ Basé sur MicroProfile ▷ Réutilisation & innovation ▷ Moteur d’exécution Vert.X ▷ Focus sur la facilité de développement ▷ Supporte le mode JVM et natif 3
  4. 4

  5. Cinq rounds pour les challenger ▷ Round 1: Modèle de

    programmation ▷ Round 2: Persistence en base de données ▷ Round 3: Monitoring & Observabilité ▷ Round 4: Tolérance aux pannes ▷ Round 5: Tests 5
  6. 1. Notre cas d’utilisation Ou comment challenger Spring et Quarkus

    sur un cas concret … 6
  7. Application Bookstore 7 User BookStoreService API Provides an API to

    manage books BookNumberService API Provides ISBN Numbers Get/Create Books [JSON/HTTP] JAEGER Store Exposes distributed tracing logs Get ISBN [JSON/HTTP] JDBC Pull Metrics Pull Metrics Send tracing [UDP] Send Tracing [UDP]
  8. Deux implémentations https://github.com/alexandre- touret/bookstore_spring https://github.com/jefrajames/bookstore 8

  9. 2. Modèle de programmation 9

  10. API REST 10 @RestController() @RequestMapping(value = "/api/books", produces = APPLICATION_JSON_VALUE)

    public class BookController { […] @GetMapping public ResponseEntity<List<Book>> getAllBooks() { return ResponseEntity. ok(bookService.findAllBooks()); } @GetMapping("/count") public ResponseEntity<Map<String, Long>> count() { return ResponseEntity.ok(Map.of("books.count", bookService.count())); } @Path("/books") public class BookResource { @Inject BookService service; @GET public Response getAllBooks() { List<Book> books = service.findAllBooks(); return Response.ok(books).build(); } @GET @Path("/count") public JsonObject count() { return Json.createObjectBuilder() .add("book.count", service.count()) .build(); } @Path("/book") @RegisterRestClient public interface NumberClient { @GET IsbnNumbers generateIsbnNumbers(); }
  11. Documentation d’API REST 11 implementation 'org.springdoc:springdoc-openapi-ui:1.5.8’ springdoc: api-docs: enabled: false

    @Operation(summary = "Gets all books") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Found books", content = {@Content(mediaType = APPLICATION_JSON_VALUE, schema = @Schema(implementation = List.class))}), @ApiResponse(responseCode = "204", description = "No books found")}) @GetMapping public ResponseEntity<List<Book>> getAllBooks() { return ResponseEntity.ok(bookService.findAllBooks()); } # Enable Swagger UI for the test & prod modes quarkus.swagger-ui.always-include=true @Operation(summary = "Returns all the books from the database") @APIResponse(responseCode = "200", content = @Content(mediaType =APPLICATION_JSON, schema = @Schema(implementation = Book.class, type = SchemaType.ARRAY))) @APIResponse(responseCode = "204", description = "No books") @GET public Response getAllBooks() { List<Book> books = service.findAllBooks(); return Response.ok(books).build(); }
  12. Injection de dépendances 12 @Configuration public class BookConfiguration { @Value("${booknumbers.api.timeout_sec}")

    private int timeoutInSec; @Bean public RestTemplate createRestTemplate (RestTemplateBuilder restTemplateBuilder) { return restTemplateBuilder.build(); } @Service public class BookService { […] public BookService(BookRepository bookRepository, RestTemplate restTemplate, @Value("${booknumbers.api.url}") String isbnServiceURL, CircuitBreakerFactory circuitBreakerFactory) { this.bookRepository = bookRepository; this.restTemplate = restTemplate; this.isbnServiceURL = isbnServiceURL; this.circuitBreakerFactory = circuitBreakerFactory; } @ApplicationScoped public class BookService { @ConfigProperty(name="app.is.ope") boolean inMaintenance; @Inject @RestClient NumberClient numberProxy; // Avoid injection on private fields! private OtherBean otherBean; […] }
  13. Configuration 13 server: port: 8082 spring: ## The most important

    feature... ever banner: location=classpath:/banner.txt datasource: url: jdbc:postgresql://localhost:5432/books_database username: book password: book jpa: database-platform: org.hibernate.dialect.PostgreSQL9… hibernate: ddl-auto: create-drop cloud: circuitbreaker: resilience4j: enabled: true application: name: rest-book # HTTP quarkus.http.port=8082 quarkus.http.cors=true # No startup banner quarkus.banner.enabled=false # Hibernate quarkus.hibernate-orm.database.generation=drop-and- create quarkus.hibernate-orm.log.sql=false quarkus.hibernate-orm.sql-load-script=import.sql # Datasource in production %prod.quarkus.datasource.db-kind=postgresql %prod.quarkus.datasource.username=book %prod.quarkus.datasource.password=zzzz %prod.quarkus.datasource.jdbc.url=… %prod.quarkus.datasource.jdbc.initial-size=2 %prod.quarkus.datasource.jdbc.max-size=8
  14. Natif vs JVM ▷ GraalVM : une JVM polyglotte ▷

    Ahead of Time Compiler ▷ SubstrateVM ▷ Red Hat Mandrel ▷ Démarrage ultra rapide et faible empreinte mémoire ▷ Build long et gourmand, scalabilité horizontale réduite, pas de chargement de classe « à la volée » 14 Le mode natif n’est pas aussi puissant qu’une « vraie » JVM (Hotspot, J9) Il n’y a pas de class loading dymamique, de debugger, d’AOP,…
  15. 3. Persistence des données 15

  16. Spring Data vs Panache ▷ Spring Data supporte plusieurs technos

    ▷ Implémente le pattern Repository ▷ Fournit plusieurs fonctionnalités avancées : pagination, tri, … ▷ Panache supporte JPA/Hibernate et MongoDB ▷ Préconise le pattern ActiveRecord ▷ Fournit plusieurs fonctionnalités avancées : projection, pagination, tri, … ▷ Field access rewrite sur attribut public ▷ Des extensions pour Redis, Cassandra, Neo4J et Elasticsearch 16
  17. Spring Data vs Panache 17 public long count() { return

    bookRepository.count(); } public Optional<Book> findBookById(Long id) { return bookRepository.findById(id); } public Book updateBook(@Valid Book book) { return bookRepository.save(book); } @ApplicationScoped public class BookService { public List<Book> findAllBooks() { return Book.listAll(); } public long count() { return Book.count(); } @Transactional public Book registerBook(@Valid Book book) { Book.persist(book); return book; } […] } @Entity public class Book extends PanacheEntity { public String title; public Integer nbOfPages; […] }
  18. 4. Monitoring & Observabilité 18

  19. Observabilité ▷ Le monitoring « traditionnel » ne suffit pas

    pour répondre aux enjeux du Cloud ▷ On a beaucoup de services qui sont déployé « quelque part » dans le cloud ▷ On a besoin d’indicateurs plus précis 19 Distributed tracing Metrics Health checks
  20. Spring Actuator vs MicroProfile ▷ Spring Actuator ◦ Audit ◦

    Health ◦ Metrics ▷ Micrometer Metrics ▷ OpenTracing ▷ MicroProfile ◦ Health ◦ Metrics ◦ OpenTracing ▷ Supporte également ◦ Micrometer ◦ OpenTelemetry 20
  21. 5. Tolérance aux pannes 21

  22. “Gérer” les pannes 22 Comment gérer les “timeout” ? Comment

    gérer les “retry” Comment faire un “fallback” ? Comment éviter l’effet boule de neige quand tout commence à aller mal ? Les pannes et latences sont inévitables!
  23. Fault Tolerance 23 Basé sur MP FaultTolerance: o @Timeout, @Retry,

    @Fallback, @CircuitBreaker, @Asynchronous, @BulkHead Basé sur Resilience4J: o En remplacement de Hystrix o Configuration programmatique @Bean public Customizer<Resilience4JCircuitBreakerFactory> createSlowNumbersAPI…() { return factory -> factory.configure( builder -> builder.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()) .timeLimiterConfig(TimeLimiterConfig.custom() .timeoutDuration(Duration.ofSeconds(timeoutInSec)) .build()), "slowNumbers"); } public Book registerBook(@Valid Book book) { circuitBreakerFactory.create("slowNumbers").run( () -> persistBook(book), throwable -> fallbackPersistBook(book) ); @Fallback(fallbackMethod = "fallbackPersistBook") public Book registerBook(@Valid Book book) { IsbnNumbers numbers=proxy.generateIsbnNumbers(); […] } // Number service not available! private Book fallbackPersistBook(Book book) { // Store the book creation request locally […] }
  24. 6. Tests 24

  25. Tests unitaires & d’intégration ▷ Spring Testing ◦ Tests unitaires

    et d’integration ◦ Supporte les bases de données embarquées ◦ Permet l’execution de fichiers SQL pendant les tests ◦ Supporte le mocking ◦ Supporte TestContainer ▷ Quarkus Testing ◦ RestAssured pour les API ◦ Supporte le mocking en utilisant QuarkusMock ◦ Supporte TestContainer ◦ Dev Services ◦ Continuous testing ◦ JVM Tests vs Native Tests 25
  26. Testing 26 @SpringBootTest(webEnvironment = RANDOM_PORT) @Sql("classpath:/books-data.sql") class BookControllerIT { @LocalServerPort

    private int port; @Autowired private TestRestTemplate testRestTemplate; @Test void should_get_a_random_book() { var book = testRestTemplate. getForEntity( […]", Book.class) .getBody(); assertNotNull(book.getId()); } @Test void shouldGetInitialItems() { List<Book> books = given() .when() .get("/api/books") .then() .statusCode(OK.getStatusCode()) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .extract() .body() .as(getBookTypeRef()); nbBooks = books.size(); } @QuarkusTest public class BookResourceTest { […] }
  27. Mocking, TestContainer 27 @Autowired private RestTemplate restTemplate; private MockRestServiceServer mockServer;

    @BeforeEach void setUp() { mockServer = MockRestServiceServer .bindTo(restTemplate).build(); IsbnNumbers isbnNumbers = new IsbnNumbers(); isbnNumbers.setIsbn10("0123456789"); mockServer.expect(ExpectedCount.once(), requestTo(new URI(API_BOOKS))) .andExpect(method(HttpMethod.GET)) .andRespond(withStatus(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(isbnNumbers)) ); } @RestClient @Mock public class MockNumberProxy implements NumberClient { IsbnNumbers numbers = new IsbnNumbers(); […] return numbers; }
  28. Un rapide sondage 28 https://bit.ly/javacloud-tnt-2

  29. En résumé 29 Spring Quarkus Rest API ***** ***** Persistence

    ***** **** Injection de dépendance ***** ***** Programmation réactive **** **** Richesse de l’écosystème ***** **** Cohérence de l’écosystème **** ***** Support du mode natif **** ***** Documentation ***** ***** Observabilité **** **** Tolérance aux pannes **** **** Cloud native **** ***** Conformité aux spécifications *** **** Popularité ***** ** Maturité ***** ****
  30. Merci! Des questions? Jean-François JAMES @jefrajames Alexandre TOURET @touret_alex 30