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

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

33003e72c00afccb1b1989e1ec21d21d?s=128

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