Slide 1

Slide 1 text

Java dans le Cloud: Avec Spring ou Quarkus? Jean-François JAMES @jefrajames Alexandre TOURET @touret_alex

Slide 2

Slide 2 text

Un rapide sondage 2 https://bit.ly/javacloud-tnt-1

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

1. Notre cas d’utilisation Ou comment challenger Spring et Quarkus sur un cas concret … 6

Slide 7

Slide 7 text

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]

Slide 8

Slide 8 text

Deux implémentations https://github.com/alexandre- touret/bookstore_spring https://github.com/jefrajames/bookstore 8

Slide 9

Slide 9 text

2. Modèle de programmation 9

Slide 10

Slide 10 text

API REST 10 @RestController() @RequestMapping(value = "/api/books", produces = APPLICATION_JSON_VALUE) public class BookController { […] @GetMapping public ResponseEntity> getAllBooks() { return ResponseEntity. ok(bookService.findAllBooks()); } @GetMapping("/count") public ResponseEntity> count() { return ResponseEntity.ok(Map.of("books.count", bookService.count())); } @Path("/books") public class BookResource { @Inject BookService service; @GET public Response getAllBooks() { List 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(); }

Slide 11

Slide 11 text

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> 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 books = service.findAllBooks(); return Response.ok(books).build(); }

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

3. Persistence des données 15

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Spring Data vs Panache 17 public long count() { return bookRepository.count(); } public Optional findBookById(Long id) { return bookRepository.findById(id); } public Book updateBook(@Valid Book book) { return bookRepository.save(book); } @ApplicationScoped public class BookService { public List 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; […] }

Slide 18

Slide 18 text

4. Monitoring & Observabilité 18

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Spring Actuator vs MicroProfile ▷ Spring Actuator ○ Audit ○ Health ○ Metrics ▷ Micrometer Metrics ▷ OpenTracing ▷ MicroProfile ○ Health ○ Metrics ○ OpenTracing ▷ Supporte également ○ Micrometer ○ OpenTelemetry 20

Slide 21

Slide 21 text

5. Tolérance aux pannes 21

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

6. Tests 24

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Un rapide sondage 28 https://bit.ly/javacloud-tnt-2

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Merci! Des questions? Jean-François JAMES @jefrajames Alexandre TOURET @touret_alex 30