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.

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. 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]

    View full-size slide

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

    View full-size slide

  8. 2.
    Modèle de programmation
    9

    View full-size slide

  9. 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();
    }

    View full-size slide

  10. 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();
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. 3.
    Persistence des données
    15

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. 4.
    Monitoring & Observabilité
    18

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  20. 5.
    Tolérance aux pannes
    21

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide