From beb075363b180ba54cb931f2651933c151ca3ef7 Mon Sep 17 00:00:00 2001 From: Alexandre Touret Date: Tue, 2 Jan 2024 11:29:39 +0100 Subject: [PATCH] feat: add documentation --- README.md | 459 ++++++++-------- docs/01-without_versioning.md | 634 +++++++++++----------- docs/02-first_version.md | 652 +++++++++++----------- docs/03-second_version.md | 412 +++++++------- docs/04-scm.md | 570 +++++++++---------- docs/05-conflicts.md | 992 +++++++++++++++++----------------- docs/06-authorization.md | 482 ++++++++--------- 7 files changed, 2102 insertions(+), 2099 deletions(-) diff --git a/README.md b/README.md index 782f1c8..eb757d3 100644 --- a/README.md +++ b/README.md @@ -1,228 +1,231 @@ - -# REST APIs Versioning: Hands-on ! - -This workshop aims to introduce different ways to handle and propose several versions of a same API to your customers. - -## :dart: Big picture - -During this workshop we will strive with API versioning on a (small) microservice application. -Here is a short description of it. - -This platform aims to store and get books of a bookstore. - -### System View - -```mermaid -C4Context - title System Context diagram for Bookstore System - Person(customerA, "Bookstore Customer", "A customer of the bookstore") - Person(adminA, "Bookstore Administrator", "An administrator of
the bookstore") - Enterprise_Boundary(b0, "Bookstore Boundary") { - System(bookstoreSystem, "Bookstore System", "Allows Book
creation, search,...") - System(iamSystem, "Bookstore IAM", "Allows Identification
& authorization...") - } - Rel(customerA, bookstoreSystem, "Uses") - Rel(adminA, bookstoreSystem, "Uses & manage users") - Rel(customerA, iamSystem, "identifies & authorizes") - Rel(adminA, iamSystem, "identifies & authorizes") -``` - -#### Explanations - -Here we have two main kind of users: -* Customer : He can browse and create books -* Administrator: He can create books and activate/deactivate the maintenance mode - -Within our platform, we have two main systems: - -* Bookstore system which operate all the book related operations -* Bookstore IAM which is responsible for identifying and authorizing users - -### Container view - -```mermaid -C4Container - title Container Context diagram for Bookstore System - - - Person(customerA, "Bookstore Customer", "A customer of the bookstore") - Person(adminA, "Bookstore Administrator", "An administrator
of the bookstore") - - Enterprise_Boundary(b0, "Bookstore Boundary") { - Container_Boundary(b2,"Bookstore IAM"){ - Container(iam,"IAM","Provides a JWT token with roles in claims") - - } - Container_Boundary(b1,"Bookstore System"){ - Container(bookstoreApi,"Bookstore API","Spring Boot, Cloud","Exposes the Bookstore APIs") - Container(gateway,"API Gateway","Spring Cloud Gateway","Exposes the APIs") - ContainerDb(database, "Database", "PostgreSQL Database", "Stores bookstore") - Container(isbnApi,"ISBN","Spring Boot, Cloud","Exposes the ISBN APIs") - Container(configuration,"Configuration Server","Spring Cloud Config","Exposes the configuration") - Container(zipkin,"Zipkin","Zipkin","Gathers and
provides distributed tracing") - } - } - - Rel(customerA,gateway, "Uses") - Rel(adminA, gateway, "Uses & manage users") - Rel(customerA, iam, "identifies & authorizes") - Rel(adminA, iam, "identifies & authorizes") - Rel(gateway, iam, "verify token") - Rel(gateway, bookstoreApi, "exposes") - Rel(gateway, isbnApi, "exposes") - Rel(bookstoreApi, isbnApi, "uses") - Rel(bookstoreApi, database, "stores data") - UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1") -``` - -#### Explanations - -This diagram digs into the systems exposed above in the system view. - -The Bookstore system is composed of: -* The API Gateway which exposes our APIs -* The Bookstore API which exposes all the related book APIs and stores data to a PostgreSQL database -* The ISBN API which provides random ISBN numbers -* A Configuration server which centralizes all the configuration files - -The Bookstore IAM is composed of: -* A mock server which provides JWT token with appropriate roles and information. - -### :straight_ruler: Stack -Here is a summary of the stack used in this workshop for this architecture: - -| Container | Tools | Comments | -|---|--------------------------------------------------------------|---| -| API Gateway | Spring Cloud Gateway 2023.0.0 | | -| Bookstore API | JAVA 21,Spring Boot 3.2.X | | -| ISBN API | JAVA 21,Spring Boot 3.2.X | | -| Configuration Server | Spring Cloud Config 2023.0.0 | | -| Database | PostgreSQL | | -| Authorization Server | JAVA 21,Spring Boot 3.2.X, Spring Authorization Server 1.1.0 | | - - -### Customers - -## Our API Roadmap - -```mermaid -%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%% -gitGraph: - commit id:"Init" - commit id: "new features" tag:"Adding excerpt attribute & operation" - branch V1 - checkout V1 - commit id:"add URI PATH versions" - commit id: "add HTTP Header versions" - commit id: "add accept HTTP Header versions" - checkout main - branch V2 - commit id: "revamping" - commit id: "Add author list feature" - checkout V1 - commit id: "Add fallback behaviour in V1" - checkout V2 - commit id: "Authorization management" - merge V1 - commit id: "Deprecating V1" -``` - -## :traffic_light: Prerequisites - -### :mortar_board: Skills - -| Skill | Level | -|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---| -| [REST API](https://google.aip.dev/general) | proficient | -| [Java](https://www.oracle.com/java/) | novice | -| [Gradle](https://gradle.org/) | novice | -| [Spring Framework](https://spring.io/projects/spring-framework), [Boot](https://spring.io/projects/spring-boot), [Cloud Config](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_quick_start), [Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) [Spring Authorization Server](https://docs.spring.io/spring-authorization-server/docs/current/reference/html/index.html)| novice | -| [OpenID Connect](https://openid.net/connect) | novice |] -| [Docker](https://docs.docker.com/) | novice | - -### :wrench: Tools -#### If you want to execute this workshop locally -You **MUST** have set up these tools first: -* [Java 17+](https://adoptium.net/temurin/releases/?version=17) -* [Gradle 7.5+](https://gradle.org/) -* [Docker](https://docs.docker.com/) & [Docker compose](https://docs.docker.com/compose/) -* Any IDE ([IntelliJ IDEA](https://www.jetbrains.com/idea), [VSCode](https://code.visualstudio.com/), [Netbeans](https://netbeans.apache.org/),...) you want -* [cURL](https://curl.se/), [jq](https://stedolan.github.io/jq/), [HTTPie](https://httpie.io/) or any tool to call your REST APIs - - -Here are commands to validate your environment: - -**Java** - -```jshelllanguage -java -version -openjdk version "21.0.1" 2023-10-17 LTS -OpenJDK Runtime Environment Temurin-21.0.1+12 (build 21.0.1+12-LTS) -OpenJDK 64-Bit Server VM Temurin-21.0.1+12 (build 21.0.1+12-LTS, mixed mode, sharing) -``` - -**Gradle** - -If you use the wrapper, you won't have troubles. Otherwise...: - -```jshelllanguage -gradle -version - ------------------------------------------------------------- -Gradle 8.5 ------------------------------------------------------------- - -Build time: 2023-11-29 14:08:57 UTC -Revision: 28aca86a7180baa17117e0e5ba01d8ea9feca598 - -Kotlin: 1.9.20 -Groovy: 3.0.17 -Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 -JVM: 21.0.1 (Eclipse Adoptium 21.0.1+12-LTS) -OS: Linux 5.15.133.1-microsoft-standard-WSL2 amd64 -``` - -**Docker Compose** - -```jshelllanguage -docker compose version - -Docker Compose version v2.22.2 -``` - -#### :rocket: If you don't want to bother with a local setup - -##### With Gitpod (recommended) -You can use [Gitpod](https://gitpod.io). -You must create an account first. -You then can open this project in either your local VS Code or directly in your browser: - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/alexandre-touret/rest-apis-versioning-workshop.git) - -##### With Github Codespaces -You can also [use Github Codespaces](https://docs.github.com/en/codespaces/). -You can create a new one by [running "Code > Create codespace on main"](https://docs.github.com/en/codespaces/developing-in-codespaces/creating-a-codespace-for-a-repository#creating-a-codespace-for-a-repository). - -You have then to run the command in the shell: - -```jshelllanguage -pip install httpie -sdk install java 21.0.1-tem -sdk default java 21.0.1-tem -``` - -## :boom: Ready ? -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/alexandre-touret/rest-apis-versioning-workshop.git) - -> **If you fork this repo** -> -> Don't forget to change the "Open in GitPod" button URL: -> ```markdown -> [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/%%MY_NAMESPACE%%/rest-apis-versioning-workshop.git) -> ``` - or you can directly browse this URL (think to change the ``%%MY_NAMESPACE%%`` prefix): - -> ``https://gitpod.io/#github.com/%%MY_NAMESPACE%%/rest-apis-versioning-workshop.git`` -_________________ - -**Now, you can start [the workshop](docs/index.md) :tada:.** + +# REST APIs Versioning: Hands-on ! + +This workshop aims to +- Introduce REST API Versioning +- Highlight API breaking and non breaking changes +- Dive into all the impacts: configuration, code, security,... + +## :dart: Big picture + +During this workshop we will strive with API versioning on a (small) microservice application. +Here is a short description of it. + +This platform aims to store and get books of a bookstore. + +### System View + +```mermaid +C4Context + title System Context diagram for Bookstore System + Person(customerA, "Bookstore Customer", "A customer of the bookstore") + Person(adminA, "Bookstore Administrator", "An administrator of
the bookstore") + Enterprise_Boundary(b0, "Bookstore Boundary") { + System(bookstoreSystem, "Bookstore System", "Allows Book
creation, search,...") + System(iamSystem, "Bookstore IAM", "Allows Identification
& authorization...") + } + Rel(customerA, bookstoreSystem, "Uses") + Rel(adminA, bookstoreSystem, "Uses & manage users") + Rel(customerA, iamSystem, "identifies & authorizes") + Rel(adminA, iamSystem, "identifies & authorizes") +``` + +#### Explanations + +Here we have two main kind of users: +* Customer : He can browse and create books +* Administrator: He can create books and activate/deactivate the maintenance mode + +Within our platform, we have two main systems: + +* Bookstore system which operate all the book related operations +* Bookstore IAM which is responsible for identifying and authorizing users + +### Container view + +```mermaid +C4Container + title Container Context diagram for Bookstore System + + + Person(customerA, "Bookstore Customer", "A customer of the bookstore") + Person(adminA, "Bookstore Administrator", "An administrator
of the bookstore") + + Enterprise_Boundary(b0, "Bookstore Boundary") { + Container_Boundary(b2,"Bookstore IAM"){ + Container(iam,"IAM","Provides a JWT token with roles in claims") + + } + Container_Boundary(b1,"Bookstore System"){ + Container(bookstoreApi,"Bookstore API","Spring Boot, Cloud","Exposes the Bookstore APIs") + Container(gateway,"API Gateway","Spring Cloud Gateway","Exposes the APIs") + ContainerDb(database, "Database", "PostgreSQL Database", "Stores bookstore") + Container(isbnApi,"ISBN","Spring Boot, Cloud","Exposes the ISBN APIs") + Container(configuration,"Configuration Server","Spring Cloud Config","Exposes the configuration") + Container(zipkin,"Zipkin","Zipkin","Gathers and
provides distributed tracing") + } + } + + Rel(customerA,gateway, "Uses") + Rel(adminA, gateway, "Uses & manage users") + Rel(customerA, iam, "identifies & authorizes") + Rel(adminA, iam, "identifies & authorizes") + Rel(gateway, iam, "verify token") + Rel(gateway, bookstoreApi, "exposes") + Rel(gateway, isbnApi, "exposes") + Rel(bookstoreApi, isbnApi, "uses") + Rel(bookstoreApi, database, "stores data") + UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1") +``` + +#### Explanations + +This diagram digs into the systems exposed above in the system view. + +The Bookstore system is composed of: +* The API Gateway which exposes our APIs +* The Bookstore API which exposes all the related book APIs and stores data to a PostgreSQL database +* The ISBN API which provides random ISBN numbers +* A Configuration server which centralizes all the configuration files + +The Bookstore IAM is composed of: +* A mock server which provides JWT token with appropriate roles and information. + +### :straight_ruler: Stack +Here is a summary of the stack used in this workshop for this architecture: + +| Container | Tools | Comments | +|---|--------------------------------------------------------------|---| +| API Gateway | Spring Cloud Gateway 2023.0.0 | | +| Bookstore API | JAVA 21,Spring Boot 3.2.X | | +| ISBN API | JAVA 21,Spring Boot 3.2.X | | +| Configuration Server | Spring Cloud Config 2023.0.0 | | +| Database | PostgreSQL | | +| Authorization Server | JAVA 21,Spring Boot 3.2.X, Spring Authorization Server 1.1.0 | | + + +### Customers + +## Our API Roadmap + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%% +gitGraph: + commit id:"Init" + commit id: "new features" tag:"Adding excerpt attribute & operation" + branch V1 + checkout V1 + commit id:"add URI PATH versions" + commit id: "add HTTP Header versions" + commit id: "add accept HTTP Header versions" + checkout main + branch V2 + commit id: "revamping" + commit id: "Add author list feature" + checkout V1 + commit id: "Add fallback behaviour in V1" + checkout V2 + commit id: "Authorization management" + merge V1 + commit id: "Deprecating V1" +``` + +## :traffic_light: Prerequisites + +### :mortar_board: Skills + +| Skill | Level | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---| +| [REST API](https://google.aip.dev/general) | proficient | +| [Java](https://www.oracle.com/java/) | novice | +| [Gradle](https://gradle.org/) | novice | +| [Spring Framework](https://spring.io/projects/spring-framework), [Boot](https://spring.io/projects/spring-boot), [Cloud Config](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_quick_start), [Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) [Spring Authorization Server](https://docs.spring.io/spring-authorization-server/docs/current/reference/html/index.html)| novice | +| [OpenID Connect](https://openid.net/connect) | novice |] +| [Docker](https://docs.docker.com/) | novice | + +### :wrench: Tools +#### If you want to execute this workshop locally +You **MUST** have set up these tools first: +* [Java 17+](https://adoptium.net/temurin/releases/?version=17) +* [Gradle 7.5+](https://gradle.org/) +* [Docker](https://docs.docker.com/) & [Docker compose](https://docs.docker.com/compose/) +* Any IDE ([IntelliJ IDEA](https://www.jetbrains.com/idea), [VSCode](https://code.visualstudio.com/), [Netbeans](https://netbeans.apache.org/),...) you want +* [cURL](https://curl.se/), [jq](https://stedolan.github.io/jq/), [HTTPie](https://httpie.io/) or any tool to call your REST APIs + + +Here are commands to validate your environment: + +**Java** + +```jshelllanguage +java -version +openjdk version "21.0.1" 2023-10-17 LTS +OpenJDK Runtime Environment Temurin-21.0.1+12 (build 21.0.1+12-LTS) +OpenJDK 64-Bit Server VM Temurin-21.0.1+12 (build 21.0.1+12-LTS, mixed mode, sharing) +``` + +**Gradle** + +If you use the wrapper, you won't have troubles. Otherwise...: + +```jshelllanguage +gradle -version + +------------------------------------------------------------ +Gradle 8.5 +------------------------------------------------------------ + +Build time: 2023-11-29 14:08:57 UTC +Revision: 28aca86a7180baa17117e0e5ba01d8ea9feca598 + +Kotlin: 1.9.20 +Groovy: 3.0.17 +Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023 +JVM: 21.0.1 (Eclipse Adoptium 21.0.1+12-LTS) +OS: Linux 5.15.133.1-microsoft-standard-WSL2 amd64 +``` + +**Docker Compose** + +```jshelllanguage +docker compose version + +Docker Compose version v2.22.2 +``` + +#### :rocket: If you don't want to bother with a local setup + +##### With Gitpod (recommended) +You can use [Gitpod](https://gitpod.io). +You must create an account first. +You then can open this project in either your local VS Code or directly in your browser: + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/alexandre-touret/rest-apis-versioning-workshop.git) + +##### With Github Codespaces +You can also [use Github Codespaces](https://docs.github.com/en/codespaces/). +You can create a new one by [running "Code > Create codespace on main"](https://docs.github.com/en/codespaces/developing-in-codespaces/creating-a-codespace-for-a-repository#creating-a-codespace-for-a-repository). + +You have then to run the command in the shell: + +```jshelllanguage +pip install httpie +sdk install java 21.0.1-tem +sdk default java 21.0.1-tem +``` + +## :boom: Ready ? +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/alexandre-touret/rest-apis-versioning-workshop.git) + +> **If you fork this repo** +> +> Don't forget to change the "Open in GitPod" button URL: +> ```markdown +> [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#github.com/%%MY_NAMESPACE%%/rest-apis-versioning-workshop.git) +> ``` + or you can directly browse this URL (think to change the ``%%MY_NAMESPACE%%`` prefix): + +> ``https://gitpod.io/#github.com/%%MY_NAMESPACE%%/rest-apis-versioning-workshop.git`` +_________________ + +**Now, you can start [the workshop](docs/index.md) :tada:.** diff --git a/docs/01-without_versioning.md b/docs/01-without_versioning.md index cac5687..d22273e 100644 --- a/docs/01-without_versioning.md +++ b/docs/01-without_versioning.md @@ -1,317 +1,317 @@ -# How to upgrade your API without versioning? - -At this point we have our first customer : **John Doe** who uses our API with the current specification. - -## TL;DR: What will you learn in this chapter? - -This chapter covers the following topics: - -1. How to start the platform -2. Adding a non-breaking change and see how it doesn't impact the API Contract - -## Prerequisites - -You must start three new shells and run [rest-book](../rest-book), [rest-number](../rest-number) and [the gateway](../gateway) modules. -As mentioned earlier, you must be at the root of the project (i.e., ``rest-apis-versioning-workshop``). - -In the first shell, run: - -```jshelllanguage -./gradlew bootRun -p rest-book -``` - -In the second one: - -```jshelllanguage -./gradlew bootRun -p rest-number -``` - - -And in the last one: - -```jshelllanguage -./gradlew bootRun -p gateway -``` - -_You can disable unit and integration tests by adding the option ``-x test`` at the end of the command ;-)._ - - -## The current status - -### Getting the OpenAPI Documentation - -You can now reach the current API documentation by running these commands: - -For the books API: - -```jshelllanguage -http :8082/v1/v3/api-docs -``` -For the numbers API: - -```jshelllanguage -http :8081/v1/v3/api-docs -``` - -You can also check the documentation by browsing these endpoints: - -* http://localhost:8082/v1/swagger-ui/index.html -* http://localhost:8081/v1/swagger-ui/index.html - -You can also use the scripts located in the [bin](../bin) folder. - -Here are some examples of the functionalities provided: - -* Get a Random Book - -You can get a random book by running this command: - -```jshelllanguage -. ./bin/randomBook.sh -``` -* Create a book - -```jshelllanguage -. ./bin/createBook.sh -``` - -Now you can stop this service (i.e., [rest-book](../rest-book)) now by typing CTRL+C on the shell you started the rest-book module. - -## Adding new data - -In this chapter, we will update the [Book schema in the OpenAPI spec file](../rest-book/src/main/resources/openapi.yml) adding the attribute ``excerpt``. - -This attribute is (only for the workshop) the beginning of the [description attribute](../rest-book/src/main/resources/openapi.yml). -We will extract the first 100 characters. - -1. Update the [OpenAPI spec file](../rest-book/src/main/resources/openapi.yml) - of [the rest-book module]((../rest-book/src/main/resources/openapi.yml)) , add the ``excerpt`` attribute: - -```yaml - Book: - required: - - title - type: object - properties: - excerpt: - readOnly: true - type: string -``` -2. Build the application again - -```jshelllanguage -./gradlew build -p rest-book -``` - -The build and tests should success. In the meantime, you would get this warning message: - -```jshelllanguage -...mapper/BookMapper.java:13: warning: Unmapped target property: "excerpt". - BookDto toBookDto(Book book); - -``` - -It is *"normal"* because the POJO (*Plain Old Java Object*) used to persist data has not been modified yet. - -3. Normally you can see now this new attribute in - the [BookDto class](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) - . -4. In the [Book entity class](../rest-book/src/main/java/info/touret/bookstore/spring/book/entity/Book.java), add a - transient attribute as below by uncommenting the following code. - -```java - -@Transient -private transient String excerpt; - - -// getter - -public String getExcerpt(){ - return this.excerpt; - } - - -@PostLoad -public void initFields(){ - if(description!=null) { - this.excerpt = description.substring(0, 100); - } -} -``` -You can now rebuild the application. - -Before creating unit and integration tests, we can run them to see if this modification is blocking. - -```jshelllanguage -./gradlew build -p rest-book -``` - -:question: See what happens: Is it blocking or not? - - -5. Now, let's get a random book with an excerpt - -Restart your rest-book service - -```jshelllanguage -./gradlew bootRun -p rest-book -``` - -Check it manually by running the following command: - -```jshelllanguage -http :8082/v1/books/1098 --print b | jq .excerpt -``` - -You can also do that through the API Gateway: - -```jshelllanguage -http :8080/v1/books/1098 --print b | jq .excerpt -``` -## Adding a new operation - -You can then add a new operation ``getBookExcerpt``. - -In the [OpenAPI spec file](../rest-book/src/main/resources/openapi.yml), add a new operation: - -For instance: - -```yaml - /books/{id}/excerpt: - get: - tags: - - book-controller - summary: Gets a book's excerpt from its ID - operationId: getBookExcerpt - parameters: - - name: id - in: path - required: true - schema: - type: integer - format: int64 - responses: - '200': - description: Found book excerpt - content: - application/json: - schema: - type: string - '408': - description: Request Timeout - content: - "*/*": - schema: - "$ref": "#/components/schemas/APIError" - '418': - description: I'm a teapot - content: - "*/*": - schema: - "$ref": "#/components/schemas/APIError" - '500': - description: Internal Server Error - content: - "*/*": - schema: - "$ref": "#/components/schemas/APIError" - -``` - - -You can now generate the corresponding Java code. - -```jshelllanguage -./gradlew openApiGenerate -p rest-book -``` - -Now, let us create the corresponding method in [BookController](../rest-book/src/main/java/info/touret/bookstore/spring/book/controller/BookController.java): - -Uncomment the following method: - -```java - @Override -public ResponseEntity getBookExcerpt(Long id) { - var optionalBook = bookService.findBookById(id); - if (optionalBook.isPresent()) { - return ResponseEntity.ok(optionalBook.get().getExcerpt()); - } else { - return ResponseEntity.notFound().build(); - } -} -``` - -Build it again: - -```jshelllanguage -./gradlew build -p rest-book -``` - -You have now added new data and functionality to your API without any version :exclamation: - -## What about backward compatibility? - -Let's check -the [OldBookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java) -integration test. -It uses -the [good old BookDto definition](../rest-book/src/test/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) -which represents the previous definition -of [BookDto](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) ( -i.e., without the ``excerpt`` functionality. -This class is based on the -first [BookDto definition](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) ( -i.e., without the ``exceprt`` attribute). - -Run it, check the log output provided by [LogBook](https://github.com/zalando/logbook/). - -```jshelllanguage -./gradlew -p rest-book test -``` -Check the [test log file](../rest-book/build/test-results/test/TEST-info.touret.bookstore.spring.book.controller.OldBookControllerIT.xml) and search the HTTP logs - -For instance: - -```json - { - "origin" : "local", - "type" : "response", - "correlation" : "acc9e76fa90e42ed", - "duration" : 36, - "protocol" : "HTTP/1.1", - "status" : 200, - "headers" : { - "Connection" : [ "keep-alive" ], - "Content-Type" : [ "application/json" ], - "Date" : [ "Mon, 12 Jun 2023 15:54:16 GMT" ], - "Keep-Alive" : [ "timeout=60" ], - "Transfer-Encoding" : [ "chunked" ] - }, - "body" : { - "excerpt" : "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l", - "title" : "la case de l oncle tom", - "isbn13" : "1234567899123", - "isbn10" : "1234567890", - "author" : "Harriet Beecher Stowe", - "yearOfPublication" : 1852, - "nbOfPages" : 613, - "rank" : 4, - "price" : null, - "smallImageUrl" : null, - "mediumImageUrl" : null, - "description" : "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.", - "id" : 100 -} -} - -``` - -> **Note** -> -> See what happens, compare the data with -> the [good old BookDto definition](../rest-book/src/test/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) -> and **explain it** :exclamation: -> -> [Go then to chapter 2](./02-first_version.md) - +# How to upgrade your API without versioning? + +At this point we have our first customer : **John Doe** who uses our API with the current specification. + +## TL;DR: What will you learn in this chapter? + +This chapter covers the following topics: + +1. How to start the platform +2. Adding a non-breaking change and see how it doesn't impact the API Contract + +## Prerequisites + +You must start three new shells and run [rest-book](../rest-book), [rest-number](../rest-number) and [the gateway](../gateway) modules. +As mentioned earlier, you must be at the root of the project (i.e., ``rest-apis-versioning-workshop``). + +In the first shell, run: + +```jshelllanguage +./gradlew bootRun -p rest-book +``` + +In the second one: + +```jshelllanguage +./gradlew bootRun -p rest-number +``` + + +And in the last one: + +```jshelllanguage +./gradlew bootRun -p gateway +``` + +_You can disable unit and integration tests by adding the option ``-x test`` at the end of the command ;-)._ + + +## The current status + +### Getting the OpenAPI Documentation + +You can now reach the current API documentation by running these commands: + +For the books API: + +```jshelllanguage +http :8082/v1/v3/api-docs +``` +For the numbers API: + +```jshelllanguage +http :8081/v1/v3/api-docs +``` + +You can also check the documentation by browsing these endpoints: + +* http://localhost:8082/v1/swagger-ui/index.html +* http://localhost:8081/v1/swagger-ui/index.html + +You can also use the scripts located in the [bin](../bin) folder. + +Here are some examples of the functionalities provided: + +* Get a Random Book + +You can get a random book by running this command: + +```jshelllanguage +. ./bin/randomBook.sh +``` +* Create a book + +```jshelllanguage +. ./bin/createBook.sh +``` + +Now you can stop this service (i.e., [rest-book](../rest-book)) now by typing CTRL+C on the shell you started the rest-book module. + +## Adding new data + +In this chapter, we will update the [Book schema in the OpenAPI spec file](../rest-book/src/main/resources/openapi.yml) adding the attribute ``excerpt``. + +This attribute is (only for the workshop) the beginning of the [description attribute](../rest-book/src/main/resources/openapi.yml). +We will extract the first 100 characters. + +1. Update the [OpenAPI spec file](../rest-book/src/main/resources/openapi.yml) + of [the rest-book module]((../rest-book/src/main/resources/openapi.yml)) , add the ``excerpt`` attribute: + +```yaml + Book: + required: + - title + type: object + properties: + excerpt: + readOnly: true + type: string +``` +2. Build the application again + +```jshelllanguage +./gradlew build -p rest-book +``` + +The build and tests should success. In the meantime, you would get this warning message: + +```jshelllanguage +...mapper/BookMapper.java:13: warning: Unmapped target property: "excerpt". + BookDto toBookDto(Book book); + +``` + +It is *"normal"* because the POJO (*Plain Old Java Object*) used to persist data has not been modified yet. + +3. Normally you can see now this new attribute in + the [BookDto class](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) + . +4. In the [Book entity class](../rest-book/src/main/java/info/touret/bookstore/spring/book/entity/Book.java), add a + transient attribute as below by uncommenting the following code. + +```java + +@Transient +private transient String excerpt; + + +// getter + +public String getExcerpt(){ + return this.excerpt; + } + + +@PostLoad +public void initFields(){ + if(description!=null) { + this.excerpt = description.substring(0, 100); + } +} +``` +You can now rebuild the application. + +Before creating unit and integration tests, we can run them to see if this modification is blocking. + +```jshelllanguage +./gradlew build -p rest-book +``` + +:question: See what happens: Is it blocking or not? + + +5. Now, let's get a random book with an excerpt + +Restart your rest-book service + +```jshelllanguage +./gradlew bootRun -p rest-book +``` + +Check it manually by running the following command: + +```jshelllanguage +http :8082/v1/books/1098 --print b | jq .excerpt +``` + +You can also do that through the API Gateway: + +```jshelllanguage +http :8080/v1/books/1098 --print b | jq .excerpt +``` +## Adding a new operation + +You can then add a new operation ``getBookExcerpt``. + +In the [OpenAPI spec file](../rest-book/src/main/resources/openapi.yml), add a new operation: + +For instance: + +```yaml + /books/{id}/excerpt: + get: + tags: + - book-controller + summary: Gets a book's excerpt from its ID + operationId: getBookExcerpt + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: Found book excerpt + content: + application/json: + schema: + type: string + '408': + description: Request Timeout + content: + "*/*": + schema: + "$ref": "#/components/schemas/APIError" + '418': + description: I'm a teapot + content: + "*/*": + schema: + "$ref": "#/components/schemas/APIError" + '500': + description: Internal Server Error + content: + "*/*": + schema: + "$ref": "#/components/schemas/APIError" + +``` + + +You can now generate the corresponding Java code. + +```jshelllanguage +./gradlew openApiGenerate -p rest-book +``` + +Now, let us create the corresponding method in [BookController](../rest-book/src/main/java/info/touret/bookstore/spring/book/controller/BookController.java): + +Uncomment the following method: + +```java + @Override +public ResponseEntity getBookExcerpt(Long id) { + var optionalBook = bookService.findBookById(id); + if (optionalBook.isPresent()) { + return ResponseEntity.ok(optionalBook.get().getExcerpt()); + } else { + return ResponseEntity.notFound().build(); + } +} +``` + +Build it again: + +```jshelllanguage +./gradlew build -p rest-book +``` + +You have now added new data and functionality to your API without any version :exclamation: + +## What about backward compatibility? + +Let's check +the [OldBookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java) +integration test. +It uses +the [good old BookDto definition](../rest-book/src/test/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) +which represents the previous definition +of [BookDto](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) ( +i.e., without the ``excerpt`` functionality. +This class is based on the +first [BookDto definition](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) ( +i.e., without the ``exceprt`` attribute). + +Run it, check the log output provided by [LogBook](https://github.com/zalando/logbook/). + +```jshelllanguage +./gradlew -p rest-book test +``` +Check the [test log file](../rest-book/build/test-results/test/TEST-info.touret.bookstore.spring.book.controller.OldBookControllerIT.xml) and search the HTTP logs + +For instance: + +```json + { + "origin" : "local", + "type" : "response", + "correlation" : "acc9e76fa90e42ed", + "duration" : 36, + "protocol" : "HTTP/1.1", + "status" : 200, + "headers" : { + "Connection" : [ "keep-alive" ], + "Content-Type" : [ "application/json" ], + "Date" : [ "Mon, 12 Jun 2023 15:54:16 GMT" ], + "Keep-Alive" : [ "timeout=60" ], + "Transfer-Encoding" : [ "chunked" ] + }, + "body" : { + "excerpt" : "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l", + "title" : "la case de l oncle tom", + "isbn13" : "1234567899123", + "isbn10" : "1234567890", + "author" : "Harriet Beecher Stowe", + "yearOfPublication" : 1852, + "nbOfPages" : 613, + "rank" : 4, + "price" : null, + "smallImageUrl" : null, + "mediumImageUrl" : null, + "description" : "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.", + "id" : 100 +} +} + +``` + +> **Note** +> +> See what happens, compare the data with +> the [good old BookDto definition](../rest-book/src/test/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java) +> and **explain it** :exclamation: +> +> [Go then to chapter 2](./02-first_version.md) + diff --git a/docs/02-first_version.md b/docs/02-first_version.md index 08c2581..705a1d3 100644 --- a/docs/02-first_version.md +++ b/docs/02-first_version.md @@ -1,326 +1,326 @@ -# Your first version - -## TL;DR: What will you learn in this chapter? - -This chapter covers the following topics: - -1. Pinpoint the impacts of the versioning in the OPENAPI Description file -2. Implement a URL Based versioning -3. Implement a header based versioning -4. Deploy and configure a default version for your API - -## Prerequisites -We will define in this chapter our first version in the URI and in a header mixing in the gateway & the apps. - -> **Warning** -> -> Before starting this chapter, please shut down all the apps already started: -> * [config server](../config-server) -> * [gateway](../gateway) -> * [authorization server](../authorization-server) -> * [rest-book](../rest-book) -> * [rest-number](../rest-number) - -## URI based version - -In the current rest-book version,we have already defined the version in the BookController's URI. - -The pattern is ``/api/%VERSION%/books``. -For instance, we could have ``/api/v1/books``. - -We will shortly review the configuration already done and update the OpenAPI documentation. - -### Configuration - -#### Rest-Book - -You can check the default context path in -the [rest-book configuration file](../config-server/src/main/resources/config/rest-book.yml) - -```yaml -server: - servlet: - context-path: /v1 -``` - -Update the [rest-book's openAPI descriptor file](../rest-book/src/main/resources/openapi.yml) adding the version in the -URL: - -```yaml -openapi: 3.0.0 -info: - title: OpenAPI definition - version: "v1" -servers: - - url: http://localhost:8082/v1 -``` - -Now, build the project: - -```bash -./gradlew build -p rest-book -``` - -##### Looking forward to rest-number api versioning updates - -The same version is applied in both rest-book and rest-number modules. - -To reach it, the same version has been applied to reach the rest-number module. -You can check the corresponding configuration in -the [rest-book configuration file](../config-server/src/main/resources/config/rest-book.yml): - -```yaml -booknumbers: - api: - url: http://127.0.0.1:8081/v1/isbns -``` - -#### Rest-Number - -Check and modify the [rest-number's openAPI descriptor file](../rest-number/src/main/resources/openapi.yml) to indicate -the version: - -```yaml -openapi: 3.0.1 -info: - title: OpenAPI definition - version: v1 -servers: - - url: http://localhost:8081/v1 -``` - -Check the [rest-number configuration file](../config-server/src/main/resources/config/rest-number.yml) and the contex -path: - -```yaml -server: - servlet: - context-path: /v1 -``` - -Now, build the project: - -```bash -./gradlew build -p rest-number -``` - -### In the gateway -Check the routes already defined in the [gateway application.yml configuration file](../gateway/src/main/resources/application.yml). - -```yaml -spring: - application: - name: gateway - zipkin: - base-url: http://localhost:9411 - sender: - type: web - cloud: - gateway: - routes: - - id: path_route - uri: http://127.0.0.1:8082 - predicates: - - Path=/v1/books - - id: path_route - uri: http://127.0.0.1:8082 - predicates: - - Path=/v1/books/{segment} - - id: path_route - uri: http://127.0.0.1:8081 - predicates: - - Path=/v1/isbns -``` - -### Tests - -#### Startup - -Normally, you Docker infrastructure should be up. If not, start it: - - -```jshelllanguage -cd infrastructure -docker compose up -``` - - -Start then the different applications: - -In the first shell: - -```jshelllanguage -./gradlew bootRun -p config-server -``` -In the second shell: - -```jshelllanguage -./gradlew bootRun -p authorization-server -``` - -In the third shell: - -```jshelllanguage -./gradlew bootRun -p rest-book -``` -In the fourth shell: - -```jshelllanguage -./gradlew bootRun -p rest-number -``` - -Last but not least, in the last one: - -```jshelllanguage - ./gradlew bootRun -p gateway -``` - -You can now reach the API. - -For instance, you can reach the gateway: - -```jshelllanguage -http :8080/v1/books/count -``` - -You can also access directly to the rest-book backend: - -```jshelllanguage -http :8082/v1/books/count -``` - -By the way, you can also verify if the Swagger and OpenAPI is up-to-date by browsing these endpoints: - -* http://localhost:8082/v1/swagger-ui/index.html -* http://localhost:8081/v1/swagger-ui/index.html - -### Create a HTTP Header based version - -In this chapter, we will put in place a rewrite/redirection mechanism in the gateway to route incoming requests -regarding a header. - -For this workshop we will extract the ``X-API-VERSION`` HTTP header and route to the appropriate backend. -For instance if we reach the API as following : - -```jshelllanguage -http :8080/... "X-API-VERSION: v1" -``` -Our gateway will rewrite the URL and reach the good version (i.e., the version specified by the header). - -You could find below a flowchart explaining the mechanism: - -```mermaid -flowchart TD - A(Incoming request /books/count with header ``X-API-VERSION: v1``) --> B{Check the presence of the HEADER and the URI base path} - B -->|OK| C(URL Rewriting : books/count > /v1/books/count ) - B -->|KO| D[Error] - C -->E(Send request to rest-book) - -``` - -We will illustrate this behaviour by adding another route in the [gateway's configuration](../gateway/src/main/resources/application.yml): - -Here is an example: - -```yaml - cloud: - gateway: - routes: - [ ... ] - - id: rewrite_v1 - uri: http://127.0.0.1:8082 - predicates: - - Path=/books/{segment} - - Header=X-API-VERSION, v1 - filters: - - RewritePath=/books/(?.*),/v1/books/$\{segment} - - id: rewrite_v1 - uri: http://127.0.0.1:8082 - predicates: - - Path=/books - - Header=X-API-VERSION, v1 - filters: - - RewritePath=/books,/v1/books - - id: rewrite_v1 - uri: http://127.0.0.1:8081 - predicates: - - Path=/isbns - - Header=X-API-VERSION, v1 - filters: - - RewritePath=/isbns,/v1/isbns -``` - -Restart the gateway: - -* Type CTRL+C first in the gateway console -* Run it again: -```jshelllanguage -./gradlew bootRun -p gateway -``` - -Now you can reach your versioned API in two ways: -1. By adding the version in the URI (e.g., ``/v1/books``) -2. By putting an HTTP header in the HTTP request - -You can now test your API using this new way: - -```jshelllanguage -http :8080/books/count "X-API-VERSION: v1" -``` - -You can use now some dedicated scripts for this new approach: - -* ``bin/countBooks-header.sh`` -* ``bin/createBook-header.sh`` -* ``bin/randomBook-header.sh`` -* ``bin/secureCountBooks-header.sh`` -* ``bin/secureISBN-header.sh`` -* ``bin/secureCreateBook-header.sh`` -* ``bin/secureRandomBook-header.sh`` - -Now you can test your API using either these two ways. - -### Create a default version -Now let's deep dive into the gateway configuration. -We will configure it to apply automatically a version if no one is applied. - -Stop the gateway by typing CTRL+C. - -Add the following route at **THE END** of the routes definition: - - -```yaml - - id: default_version_v1 - uri: http://127.0.0.1:8081 - predicates: - - Path=/isbns - filters: - - RewritePath=/isbns,/v1/isbns -``` - -Restart the gateway - -```jshelllanguage - ./gradlew bootRun -p gateway -``` - -and run the following command: - -```jshelllanguage -http :8080/isbns -``` - -The default version is automatically applied and the gateway should throw the request to the isbns v1 API endpoint. - -## Conclusion - -In this chapter we have seen how to specify and deal with API version numbers in a gateway and the backends. -The [gateway configuration](../gateway/src/main/resources/application.yml) is intentionally simple and minimalistic. -In _the real life_ we would code a dynamic routing and filtering mechanism. - - -> **Note** -> -> In your opinion, which way is the best: URI, HTTP header, Accept HTTP header? And where: in the gateway or in the backend? or both? -> -> [Go then to chapter 3](./03-second_version.md) +# Your first version + +## TL;DR: What will you learn in this chapter? + +This chapter covers the following topics: + +1. Pinpoint the impacts of the versioning in the OPENAPI Description file +2. Implement a URL Based versioning +3. Implement a header based versioning +4. Deploy and configure a default version for your API + +## Prerequisites +We will define in this chapter our first version in the URI and in a header mixing in the gateway & the apps. + +> **Warning** +> +> Before starting this chapter, please shut down all the apps already started: +> * [config server](../config-server) +> * [gateway](../gateway) +> * [authorization server](../authorization-server) +> * [rest-book](../rest-book) +> * [rest-number](../rest-number) + +## URI based version + +In the current rest-book version,we have already defined the version in the BookController's URI. + +The pattern is ``/api/%VERSION%/books``. +For instance, we could have ``/api/v1/books``. + +We will shortly review the configuration already done and update the OpenAPI documentation. + +### Configuration + +#### Rest-Book + +You can check the default context path in +the [rest-book configuration file](../config-server/src/main/resources/config/rest-book.yml) + +```yaml +server: + servlet: + context-path: /v1 +``` + +Update the [rest-book's openAPI descriptor file](../rest-book/src/main/resources/openapi.yml) adding the version in the +URL: + +```yaml +openapi: 3.0.0 +info: + title: OpenAPI definition + version: "v1" +servers: + - url: http://localhost:8082/v1 +``` + +Now, build the project: + +```bash +./gradlew build -p rest-book +``` + +##### Looking forward to rest-number api versioning updates + +The same version is applied in both rest-book and rest-number modules. + +To reach it, the same version has been applied to reach the rest-number module. +You can check the corresponding configuration in +the [rest-book configuration file](../config-server/src/main/resources/config/rest-book.yml): + +```yaml +booknumbers: + api: + url: http://127.0.0.1:8081/v1/isbns +``` + +#### Rest-Number + +Check and modify the [rest-number's openAPI descriptor file](../rest-number/src/main/resources/openapi.yml) to indicate +the version: + +```yaml +openapi: 3.0.1 +info: + title: OpenAPI definition + version: v1 +servers: + - url: http://localhost:8081/v1 +``` + +Check the [rest-number configuration file](../config-server/src/main/resources/config/rest-number.yml) and the contex +path: + +```yaml +server: + servlet: + context-path: /v1 +``` + +Now, build the project: + +```bash +./gradlew build -p rest-number +``` + +### In the gateway +Check the routes already defined in the [gateway application.yml configuration file](../gateway/src/main/resources/application.yml). + +```yaml +spring: + application: + name: gateway + zipkin: + base-url: http://localhost:9411 + sender: + type: web + cloud: + gateway: + routes: + - id: path_route + uri: http://127.0.0.1:8082 + predicates: + - Path=/v1/books + - id: path_route + uri: http://127.0.0.1:8082 + predicates: + - Path=/v1/books/{segment} + - id: path_route + uri: http://127.0.0.1:8081 + predicates: + - Path=/v1/isbns +``` + +### Tests + +#### Startup + +Normally, you Docker infrastructure should be up. If not, start it: + + +```jshelllanguage +cd infrastructure +docker compose up +``` + + +Start then the different applications: + +In the first shell: + +```jshelllanguage +./gradlew bootRun -p config-server +``` +In the second shell: + +```jshelllanguage +./gradlew bootRun -p authorization-server +``` + +In the third shell: + +```jshelllanguage +./gradlew bootRun -p rest-book +``` +In the fourth shell: + +```jshelllanguage +./gradlew bootRun -p rest-number +``` + +Last but not least, in the last one: + +```jshelllanguage + ./gradlew bootRun -p gateway +``` + +You can now reach the API. + +For instance, you can reach the gateway: + +```jshelllanguage +http :8080/v1/books/count +``` + +You can also access directly to the rest-book backend: + +```jshelllanguage +http :8082/v1/books/count +``` + +By the way, you can also verify if the Swagger and OpenAPI is up-to-date by browsing these endpoints: + +* http://localhost:8082/v1/swagger-ui/index.html +* http://localhost:8081/v1/swagger-ui/index.html + +### Create a HTTP Header based version + +In this chapter, we will put in place a rewrite/redirection mechanism in the gateway to route incoming requests +regarding a header. + +For this workshop we will extract the ``X-API-VERSION`` HTTP header and route to the appropriate backend. +For instance if we reach the API as following : + +```jshelllanguage +http :8080/... "X-API-VERSION: v1" +``` +Our gateway will rewrite the URL and reach the good version (i.e., the version specified by the header). + +You could find below a flowchart explaining the mechanism: + +```mermaid +flowchart TD + A(Incoming request /books/count with header ``X-API-VERSION: v1``) --> B{Check the presence of the HEADER and the URI base path} + B -->|OK| C(URL Rewriting : books/count > /v1/books/count ) + B -->|KO| D[Error] + C -->E(Send request to rest-book) + +``` + +We will illustrate this behaviour by adding another route in the [gateway's configuration](../gateway/src/main/resources/application.yml): + +Here is an example: + +```yaml + cloud: + gateway: + routes: + [ ... ] + - id: rewrite_v1 + uri: http://127.0.0.1:8082 + predicates: + - Path=/books/{segment} + - Header=X-API-VERSION, v1 + filters: + - RewritePath=/books/(?.*),/v1/books/$\{segment} + - id: rewrite_v1 + uri: http://127.0.0.1:8082 + predicates: + - Path=/books + - Header=X-API-VERSION, v1 + filters: + - RewritePath=/books,/v1/books + - id: rewrite_v1 + uri: http://127.0.0.1:8081 + predicates: + - Path=/isbns + - Header=X-API-VERSION, v1 + filters: + - RewritePath=/isbns,/v1/isbns +``` + +Restart the gateway: + +* Type CTRL+C first in the gateway console +* Run it again: +```jshelllanguage +./gradlew bootRun -p gateway +``` + +Now you can reach your versioned API in two ways: +1. By adding the version in the URI (e.g., ``/v1/books``) +2. By putting an HTTP header in the HTTP request + +You can now test your API using this new way: + +```jshelllanguage +http :8080/books/count "X-API-VERSION: v1" +``` + +You can use now some dedicated scripts for this new approach: + +* ``bin/countBooks-header.sh`` +* ``bin/createBook-header.sh`` +* ``bin/randomBook-header.sh`` +* ``bin/secureCountBooks-header.sh`` +* ``bin/secureISBN-header.sh`` +* ``bin/secureCreateBook-header.sh`` +* ``bin/secureRandomBook-header.sh`` + +Now you can test your API using either these two ways. + +### Create a default version +Now let's deep dive into the gateway configuration. +We will configure it to apply automatically a version if no one is applied. + +Stop the gateway by typing CTRL+C. + +Add the following route at **THE END** of the routes definition: + + +```yaml + - id: default_version_v1 + uri: http://127.0.0.1:8081 + predicates: + - Path=/isbns + filters: + - RewritePath=/isbns,/v1/isbns +``` + +Restart the gateway + +```jshelllanguage + ./gradlew bootRun -p gateway +``` + +and run the following command: + +```jshelllanguage +http :8080/isbns +``` + +The default version is automatically applied and the gateway should throw the request to the isbns v1 API endpoint. + +## Conclusion + +In this chapter we have seen how to specify and deal with API version numbers in a gateway and the backends. +The [gateway configuration](../gateway/src/main/resources/application.yml) is intentionally simple and minimalistic. +In _the real life_ we would code a dynamic routing and filtering mechanism. + + +> **Note** +> +> In your opinion, which way is the best: URI, HTTP header, Accept HTTP header? And where: in the gateway or in the backend? or both? +> +> [Go then to chapter 3](./03-second_version.md) diff --git a/docs/03-second_version.md b/docs/03-second_version.md index b179169..7add018 100644 --- a/docs/03-second_version.md +++ b/docs/03-second_version.md @@ -1,206 +1,206 @@ -# And now something completely different : a second version - -## TL;DR: What are you going to learn in this chapter? - -This chapter covers the following topics: - -1. Creating a new version (it will be a copy of the rest-book module) -2. Add a new breaking change functionality to the last version - -## A new functionality for a new customer - -We have now a new customer. - -Good news/bad news! - -The good one is our API is now famous, the bad one is we must change our API contract without impacting our existing customers. - -The very bad point, is our existing customers cannot update their API clients by one year (at least). -We then decided to create a new version! - -In this case, it is strongly recommended to deal with GIT long time versions. -For instance, using [Gitflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). - -You can also use and ship Docker images built on top of this workflow to facilitate the deployment of module's versions. -To simplify the development loop of this workshop, we will only duplicate the [rest-book](../rest-book) module. - -> **Note** -> -> In this chapter, we won't be working on having two separate versions running in the same time. -> We will be doing that on the next chapter after configuring the config server. - -### Pre requisites - -You **MUST** stop the running [rest-book module](../rest-book) before! - -### Duplicating the rest-book module - -* Update the [build.gradle](../build.gradle) file uncommenting the following configuration: - -```groovy -project(':rest-book-2') { - apply plugin: 'org.openapi.generator' - dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'org.postgresql:postgresql' - testImplementation 'com.h2database:h2' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j' - implementation 'org.springframework.cloud:spring-cloud-starter-config' - implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${springdocVersion}" - implementation 'com.fasterxml.jackson.core:jackson-annotations' - implementation "org.mapstruct:mapstruct:${mapstructVersion}" - implementation 'org.zalando:logbook-spring-boot-starter:3.0.0' - annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" - } - openApiValidate { - inputSpec = "$projectDir/src/main/resources/openapi.yml".toString() - recommend = true - } - openApiGenerate { - generatorName = "spring" - library = "spring-boot" - modelNameSuffix = "Dto" - inputSpec = "$projectDir/src/main/resources/openapi.yml".toString() - outputDir = "$buildDir/generated".toString() - apiPackage = "info.touret.bookstore.spring.book.generated.controller" - invokerPackage = "info.touret.bookstore.spring.book.generated.invoker" - modelPackage = "info.touret.bookstore.spring.book.generated.dto" - configOptions = [ - dateLibrary : "java8", - java8 : "true", - openApiNullable : "false", - documentationProvider: "springdoc", - useBeanValidation : "true", - interfaceOnly : "true", - useSpringBoot3 : "true" - ] - } - tasks.withType(JavaCompile) { - options.compilerArgs = [ - '-Amapstruct.suppressGeneratorTimestamp=true', - '-Amapstruct.suppressGeneratorVersionInfoComment=true', - '-Amapstruct.defaultComponentModel=spring' - ] - } - - springBoot { - mainClass = "info.touret.bookstore.spring.RestBookstoreApplication" - } - sourceSets.main.java.srcDirs += "$buildDir/generated/src/main/java".toString() - compileJava.dependsOn 'openApiGenerate' -} - -``` - -In the [settings.gradle](../settings.gradle) file you have to define this new module. -Uncomment this line: - -```properties -include 'rest-book-2' -``` - -Validate your configuration by building this project: - -```jshelllanguage -./gradlew build -p rest-book-2 -``` - -You will then have to re-import the new configuration in your IDE by refreshing it (You can also only build the new module by running this command): - -```jshelllanguage -./gradlew build -p rest-book-2 -``` - -## Adding a new functionality to rest-book2 - -In this new service, we are to deploy new features for our new customer. -He/She has a huge library of books, we therefore want to limit the numbers of results provided by our [``/books`` API](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/controller/BookController.java) to only 10 results. - -We could imagine that a search engine functionality would be more realistic. -However, for this workshop, we will only work to a books list limiter. - -This limit will be applied by creating a new query which uses a [``Pageable`` parameter](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Pageable.html). - -This object is really useful to paginate results. We will only use it for limiting the data queried on the database and returned by our API. - -In the rest-book-2 [``BookRepository`` class](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/repository/BookRepository.java), -add the following method: - -```java -List findAll(Pageable pageable); -``` - -Add also these import declarations: - -```java -import org.springframework.data.domain.Pageable; -import java.util.List; -``` - -In the [BookService](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/service/BookService.java) class, update the [``findAllBooks`` method](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/service/BookService.java): - -```java -public List findAllBooks(){ - return bookRepository.findAll(PageRequest.of(0,findLimit)); -} -``` - -Add also the corresponding import declaration: - -```java -import org.springframework.data.domain.PageRequest; -``` - -The field ``findLimit`` is set in the constructor: - -```java - private final Integer findLimit; - -public BookService(BookRepository bookRepository, - RestTemplate restTemplate, -@Value("${booknumbers.api.url}") String isbnServiceURL, -@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") CircuitBreakerFactory circuitBreakerFactory, -@Value("${book.find.limit:10}") Integer findLimit){ - this.bookRepository=bookRepository; - this.restTemplate=restTemplate; - this.isbnServiceURL=isbnServiceURL; - - this.circuitBreakerFactory=circuitBreakerFactory; - this.findLimit=findLimit; - } - - -``` - -You have then to add some config lines and the [rest-book.yml](../config-server/src/main/resources/config/rest-book.yml) file: - -```yaml -book: - find: - limit: 10 -``` - -## Running it - -Now, you can start your new module: - -```jshelllanguage -./gradlew bootRun -p rest-book-2 -``` - -You can test it and especially the new feature: - -```jshelllanguage -http :8082/v1/books --print b | jq '. | length' -``` - -You must only get ten elements. - -> **Note** -> -> In this chapter, we added a new feature creating a new version. -> At this time, we can't run both of the two versions simultaneously. It will be done in the next chapter. -> -> [Go to chapter 4](04-scm.md) +# And now something completely different : a second version + +## TL;DR: What are you going to learn in this chapter? + +This chapter covers the following topics: + +1. Creating a new version (it will be a copy of the rest-book module) +2. Add a new breaking change functionality to the last version + +## A new functionality for a new customer + +We have now a new customer. + +Good news/bad news! + +The good one is our API is now famous, the bad one is we must change our API contract without impacting our existing customers. + +The very bad point, is our existing customers cannot update their API clients by one year (at least). +We then decided to create a new version! + +In this case, it is strongly recommended to deal with GIT long time versions. +For instance, using [Gitflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). + +You can also use and ship Docker images built on top of this workflow to facilitate the deployment of module's versions. +To simplify the development loop of this workshop, we will only duplicate the [rest-book](../rest-book) module. + +> **Note** +> +> In this chapter, we won't be working on having two separate versions running in the same time. +> We will be doing that on the next chapter after configuring the config server. + +### Pre requisites + +You **MUST** stop the running [rest-book module](../rest-book) before! + +### Duplicating the rest-book module + +* Update the [build.gradle](../build.gradle) file uncommenting the following configuration: + +```groovy +project(':rest-book-2') { + apply plugin: 'org.openapi.generator' + dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'org.postgresql:postgresql' + testImplementation 'com.h2database:h2' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j' + implementation 'org.springframework.cloud:spring-cloud-starter-config' + implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${springdocVersion}" + implementation 'com.fasterxml.jackson.core:jackson-annotations' + implementation "org.mapstruct:mapstruct:${mapstructVersion}" + implementation 'org.zalando:logbook-spring-boot-starter:3.0.0' + annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + } + openApiValidate { + inputSpec = "$projectDir/src/main/resources/openapi.yml".toString() + recommend = true + } + openApiGenerate { + generatorName = "spring" + library = "spring-boot" + modelNameSuffix = "Dto" + inputSpec = "$projectDir/src/main/resources/openapi.yml".toString() + outputDir = "$buildDir/generated".toString() + apiPackage = "info.touret.bookstore.spring.book.generated.controller" + invokerPackage = "info.touret.bookstore.spring.book.generated.invoker" + modelPackage = "info.touret.bookstore.spring.book.generated.dto" + configOptions = [ + dateLibrary : "java8", + java8 : "true", + openApiNullable : "false", + documentationProvider: "springdoc", + useBeanValidation : "true", + interfaceOnly : "true", + useSpringBoot3 : "true" + ] + } + tasks.withType(JavaCompile) { + options.compilerArgs = [ + '-Amapstruct.suppressGeneratorTimestamp=true', + '-Amapstruct.suppressGeneratorVersionInfoComment=true', + '-Amapstruct.defaultComponentModel=spring' + ] + } + + springBoot { + mainClass = "info.touret.bookstore.spring.RestBookstoreApplication" + } + sourceSets.main.java.srcDirs += "$buildDir/generated/src/main/java".toString() + compileJava.dependsOn 'openApiGenerate' +} + +``` + +In the [settings.gradle](../settings.gradle) file you have to define this new module. +Uncomment this line: + +```properties +include 'rest-book-2' +``` + +Validate your configuration by building this project: + +```jshelllanguage +./gradlew build -p rest-book-2 +``` + +You will then have to re-import the new configuration in your IDE by refreshing it (You can also only build the new module by running this command): + +```jshelllanguage +./gradlew build -p rest-book-2 +``` + +## Adding a new functionality to rest-book2 + +In this new service, we are to deploy new features for our new customer. +He/She has a huge library of books, we therefore want to limit the numbers of results provided by our [``/books`` API](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/controller/BookController.java) to only 10 results. + +We could imagine that a search engine functionality would be more realistic. +However, for this workshop, we will only work to a books list limiter. + +This limit will be applied by creating a new query which uses a [``Pageable`` parameter](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Pageable.html). + +This object is really useful to paginate results. We will only use it for limiting the data queried on the database and returned by our API. + +In the rest-book-2 [``BookRepository`` class](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/repository/BookRepository.java), +add the following method: + +```java +List findAll(Pageable pageable); +``` + +Add also these import declarations: + +```java +import org.springframework.data.domain.Pageable; +import java.util.List; +``` + +In the [BookService](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/service/BookService.java) class, update the [``findAllBooks`` method](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/service/BookService.java): + +```java +public List findAllBooks(){ + return bookRepository.findAll(PageRequest.of(0,findLimit)); +} +``` + +Add also the corresponding import declaration: + +```java +import org.springframework.data.domain.PageRequest; +``` + +The field ``findLimit`` is set in the constructor: + +```java + private final Integer findLimit; + +public BookService(BookRepository bookRepository, + RestTemplate restTemplate, +@Value("${booknumbers.api.url}") String isbnServiceURL, +@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") CircuitBreakerFactory circuitBreakerFactory, +@Value("${book.find.limit:10}") Integer findLimit){ + this.bookRepository=bookRepository; + this.restTemplate=restTemplate; + this.isbnServiceURL=isbnServiceURL; + + this.circuitBreakerFactory=circuitBreakerFactory; + this.findLimit=findLimit; + } + + +``` + +You have then to add some config lines and the [rest-book.yml](../config-server/src/main/resources/config/rest-book.yml) file: + +```yaml +book: + find: + limit: 10 +``` + +## Running it + +Now, you can start your new module: + +```jshelllanguage +./gradlew bootRun -p rest-book-2 +``` + +You can test it and especially the new feature: + +```jshelllanguage +http :8082/v1/books --print b | jq '. | length' +``` + +You must only get ten elements. + +> **Note** +> +> In this chapter, we added a new feature creating a new version. +> At this time, we can't run both of the two versions simultaneously. It will be done in the next chapter. +> +> [Go to chapter 4](04-scm.md) diff --git a/docs/04-scm.md b/docs/04-scm.md index 49d6bb6..5a8d04a 100644 --- a/docs/04-scm.md +++ b/docs/04-scm.md @@ -1,285 +1,285 @@ -# Configuration management - -## TL;DR: What are you going to learn in this chapter? - -This chapter covers the following topics: - -1. Pinpoint Configuration management issues due to API Versioning -2. Exposing the two versions on the API Gateway - -## Preamble -You can configure your services either during deployment using CI tooling, such as [Gitlab Environments](https://docs.gitlab.com/ee/ci/environments/), or any other [Infra As Code](https://en.wikipedia.org/wiki/Infrastructure_as_code) tool ([Istio](https://istio.io/), [Ansible](https://www.ansible.com/),...) or using a configuration server. -For this workshop, all the configuration items will be provided by [Spring Cloud Config](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_quick_start). - -We will illustrate in this chapter the impacts of versioning in the configuration management. - -Here are the issues to fix in this chapter: -* Specify a different port number for the new rest-book version -* Specify a new version number on all the layers -* Apply different parameters for the number of results of the ``/books`` API, timeout,... - -> **WARNING** -> -> Now you **MUST** stop all your Spring apps. -> - -## Configuration server version management - -For this workshop, we will only carry out a [simple version management based on Spring profiles](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_quick_start). - -Copy / paste the [rest-book.yml](../config-server/src/main/resources/config/rest-book.yml) to [rest-book-v1.yml](../config-server/src/main/resources/config/rest-book-v1.yml) and [rest-book-v2.yml](../config-server/src/main/resources/config/rest-book-v2.yml). - -In the latter, modify the properties ``server.port`` and ``server.servet.context-path``: - -```yaml -server: - port: 8083 - servlet: - context-path: /v2 -``` - -You can also remove the ``book.find.limit`` property in the first version and modify the ``booknumbers.api.timeout_sec`` property in the second one. - -Now, start your config server: - -```jshelllanguage -./gradlew clean bootRun -p config-server -``` - - -You can test it using these HTTP requests: - -```jshelllanguage -http :8888/rest-book/v1 --print b | jq ' .propertySources[0].source' | jq '."server.servlet.context-path"' -``` -You must have this output: - -```jshelllanguage - "/v1" -``` -and - -```jshelllanguage -http :8888/rest-book/v2 --print b | jq ' .propertySources[0].source."server.servlet.context-path"' -``` -You must have this output: - -```jshelllanguage - "/v2" -``` - -## Rest-book configuration management - -First, modify the ``application.properties`` files to specify the current profile: - -In the [V1](../rest-book/src/main/resources/application.properties): - -```properties -spring.profiles.active=v1 -``` - -And in the [V2](../rest-book-2/src/main/resources/application.properties): - -```properties -spring.profiles.active=v2 -``` - -### OpenAPI - -Modify or check [the rest-book v1 OpenAPI description file](../rest-book/src/main/resources/openapi.yml) to specify the new version: - -```yaml -openapi: 3.0.0 -info: - title: OpenAPI definition - version: "v1" -servers: - - url: http://localhost:8082/v1 -``` - -Modify [the rest-book v2 OpenAPI description file](../rest-book-2/src/main/resources/openapi.yml) to specify the new version: - -```yaml -openapi: 3.0.0 -info: - title: OpenAPI definition - version: "v2" -servers: - - url: http://localhost:8083/v2 -``` - -### Test it - -First, stop the config server, and build the whole application: - -```jshelllanguage -./gradlew clean build -``` - -The build must be successful. - -Start your backends (we assume your Docker infrastructure is still up). - -In the first shell: - -```jshelllanguage -./gradlew bootRun -p config-server -``` -In the second shell: - -```jshelllanguage -./gradlew bootRun -p rest-book-2 -``` - -In the third shell: - -```jshelllanguage -./gradlew bootRun -p rest-book -``` -In the fourth shell: - -```jshelllanguage -./gradlew bootRun -p rest-number -``` - -Now, reach your APIs (without using the gateway): - -For the V1: -```jshelllanguage -http :8082/v1/books -``` - -and the V2: -```jshelllanguage -http :8083/v2/books -``` - -## Rest-number - -This service doesn't really need to be versioned now. -To put in place the whole infrastructure and the same behaviour of rest-book module, we will apply the same configuration: - -* Stop the config server. -* Rename the [rest-number.yml](../config-server/src/main/resources/config/rest-number.yml) configuration file to [rest-number-v1.yml](../config-server/src/main/resources/config/rest-number-v1.yml) -* Restart the config server - -```jshelllanguage -./gradlew bootRun -p config-server -``` - -* Define the current profile in the [rest-number application.properties file](../rest-number/src/main/resources/application.properties): - -```properties -spring.profiles.active=v1 -``` - -* Start then the rest-number module: - -```jshelllanguage -./gradlew bootRun -p rest-number -``` - -## Gateway configuration - -Now, we will expose both versions in the gateway. -Add the following content into the [gateway configuration file](../gateway/src/main/resources/application.yml), : - -```yaml - # HTTP HEADER VERSIONING - - id: rewrite_v2 - uri: http://127.0.0.1:8083 - predicates: - - Path=/books/{segment} - - Header=X-API-VERSION, v2 - filters: - - RewritePath=/books/(?.*),/v2/books/$\{segment} - - id: rewrite_v2 - uri: http://127.0.0.1:8083 - predicates: - - Path=/books - - Header=X-API-VERSION, v2 - filters: - - RewritePath=/books,/v2/books - - id: rewrite_v2 - uri: http://127.0.0.1:8081 - predicates: - - Path=/isbns - - Header=X-API-VERSION, v2 - filters: - - RewritePath=/isbns,/v1/isbns - # HTTP ACCEPT MEDIA TYPE HEADER VERSIONING - - id: rewrite_accept_v2 - uri: http://127.0.0.1:8083 - predicates: - - Path=/books - - Header=accept, application/vnd.api\.v2\+json - filters: - - RewritePath=/books,/v2/books - - id: rewrite_accept_v2 - uri: http://127.0.0.1:8083 - predicates: - - Path=/books/{segment} - - Header=accept, application/vnd.api\.v2\+json - filters: - - RewritePath=/books/(?.*),/v2/books/$\{segment} - - id: rewrite_accept_v2 - uri: http://127.0.0.1:8081 - predicates: - - Path=/isbns - - Header=accept, application/vnd.api\.v2\+json - filters: - - RewritePath=/isbns,/v1/isbns - # URI PATH VERSIONING - - id: path_route - uri: http://127.0.0.1:8083 - predicates: - - Path=/v2/books - - id: path_route - uri: http://127.0.0.1:8083 - predicates: - - Path=/v2/books/{segment} - - id: path_route - uri: http://127.0.0.1:8081 - predicates: - - Path=/v2/isbns - filters: - - RewritePath=/v2/isbns,/v1/isbns - - -``` - -To propose a cohesive and coherent API to our customer, we chose to publish all our API endpoints with a v1 and v2 prefix. -Although [rest-number](../rest-number) only provides **ONE** version (i.e., the ``v1``), we will expose both on the gateway and rewrite the URL as following: - -```yaml - - id: path_route - uri: http://127.0.0.1:8081 - predicates: - - Path=/v2/isbns - filters: - - RewritePath=/v2/isbns,/v1/isbns -``` - -### Test it -Restart your gateway and test it: - -```jshelllanguage -./gradlew clean bootRun -p gateway -``` - -You can now use [the scripts files](../bin). -For every script (e.g., [``countBooks.sh``](../bin/countBooks.sh)), you have one which reach the V2 endpoints (e.g., [``countBooks-v2.sh``](../bin/countBooks-v2.sh)) - -Here is an example for the ``countBooks.sh`` script file copied to ``countBooks-v2.sh``. - -```jshelllanguage -http :8080/v2/books/count -``` -This action is voluntary simple. -Feel free to add an argument to the existing script files if you want ;-). - -> **Note** -> -> In this chapter, we have seen one part of the impacts of API versioning in configuration management. The most important part is done before, both in the GIT configuration and the release management. -> -> [Go then to chapter 5](05-conflicts.md) +# Configuration management + +## TL;DR: What are you going to learn in this chapter? + +This chapter covers the following topics: + +1. Pinpoint Configuration management issues due to API Versioning +2. Exposing the two versions on the API Gateway + +## Preamble +You can configure your services either during deployment using CI tooling, such as [Gitlab Environments](https://docs.gitlab.com/ee/ci/environments/), or any other [Infra As Code](https://en.wikipedia.org/wiki/Infrastructure_as_code) tool ([Istio](https://istio.io/), [Ansible](https://www.ansible.com/),...) or using a configuration server. +For this workshop, all the configuration items will be provided by [Spring Cloud Config](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_quick_start). + +We will illustrate in this chapter the impacts of versioning in the configuration management. + +Here are the issues to fix in this chapter: +* Specify a different port number for the new rest-book version +* Specify a new version number on all the layers +* Apply different parameters for the number of results of the ``/books`` API, timeout,... + +> **WARNING** +> +> Now you **MUST** stop all your Spring apps. +> + +## Configuration server version management + +For this workshop, we will only carry out a [simple version management based on Spring profiles](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_quick_start). + +Copy / paste the [rest-book.yml](../config-server/src/main/resources/config/rest-book.yml) to [rest-book-v1.yml](../config-server/src/main/resources/config/rest-book-v1.yml) and [rest-book-v2.yml](../config-server/src/main/resources/config/rest-book-v2.yml). + +In the latter, modify the properties ``server.port`` and ``server.servet.context-path``: + +```yaml +server: + port: 8083 + servlet: + context-path: /v2 +``` + +You can also remove the ``book.find.limit`` property in the first version and modify the ``booknumbers.api.timeout_sec`` property in the second one. + +Now, start your config server: + +```jshelllanguage +./gradlew clean bootRun -p config-server +``` + + +You can test it using these HTTP requests: + +```jshelllanguage +http :8888/rest-book/v1 --print b | jq ' .propertySources[0].source' | jq '."server.servlet.context-path"' +``` +You must have this output: + +```jshelllanguage + "/v1" +``` +and + +```jshelllanguage +http :8888/rest-book/v2 --print b | jq ' .propertySources[0].source."server.servlet.context-path"' +``` +You must have this output: + +```jshelllanguage + "/v2" +``` + +## Rest-book configuration management + +First, modify the ``application.properties`` files to specify the current profile: + +In the [V1](../rest-book/src/main/resources/application.properties): + +```properties +spring.profiles.active=v1 +``` + +And in the [V2](../rest-book-2/src/main/resources/application.properties): + +```properties +spring.profiles.active=v2 +``` + +### OpenAPI + +Modify or check [the rest-book v1 OpenAPI description file](../rest-book/src/main/resources/openapi.yml) to specify the new version: + +```yaml +openapi: 3.0.0 +info: + title: OpenAPI definition + version: "v1" +servers: + - url: http://localhost:8082/v1 +``` + +Modify [the rest-book v2 OpenAPI description file](../rest-book-2/src/main/resources/openapi.yml) to specify the new version: + +```yaml +openapi: 3.0.0 +info: + title: OpenAPI definition + version: "v2" +servers: + - url: http://localhost:8083/v2 +``` + +### Test it + +First, stop the config server, and build the whole application: + +```jshelllanguage +./gradlew clean build +``` + +The build must be successful. + +Start your backends (we assume your Docker infrastructure is still up). + +In the first shell: + +```jshelllanguage +./gradlew bootRun -p config-server +``` +In the second shell: + +```jshelllanguage +./gradlew bootRun -p rest-book-2 +``` + +In the third shell: + +```jshelllanguage +./gradlew bootRun -p rest-book +``` +In the fourth shell: + +```jshelllanguage +./gradlew bootRun -p rest-number +``` + +Now, reach your APIs (without using the gateway): + +For the V1: +```jshelllanguage +http :8082/v1/books +``` + +and the V2: +```jshelllanguage +http :8083/v2/books +``` + +## Rest-number + +This service doesn't really need to be versioned now. +To put in place the whole infrastructure and the same behaviour of rest-book module, we will apply the same configuration: + +* Stop the config server. +* Rename the [rest-number.yml](../config-server/src/main/resources/config/rest-number.yml) configuration file to [rest-number-v1.yml](../config-server/src/main/resources/config/rest-number-v1.yml) +* Restart the config server + +```jshelllanguage +./gradlew bootRun -p config-server +``` + +* Define the current profile in the [rest-number application.properties file](../rest-number/src/main/resources/application.properties): + +```properties +spring.profiles.active=v1 +``` + +* Start then the rest-number module: + +```jshelllanguage +./gradlew bootRun -p rest-number +``` + +## Gateway configuration + +Now, we will expose both versions in the gateway. +Add the following content into the [gateway configuration file](../gateway/src/main/resources/application.yml), : + +```yaml + # HTTP HEADER VERSIONING + - id: rewrite_v2 + uri: http://127.0.0.1:8083 + predicates: + - Path=/books/{segment} + - Header=X-API-VERSION, v2 + filters: + - RewritePath=/books/(?.*),/v2/books/$\{segment} + - id: rewrite_v2 + uri: http://127.0.0.1:8083 + predicates: + - Path=/books + - Header=X-API-VERSION, v2 + filters: + - RewritePath=/books,/v2/books + - id: rewrite_v2 + uri: http://127.0.0.1:8081 + predicates: + - Path=/isbns + - Header=X-API-VERSION, v2 + filters: + - RewritePath=/isbns,/v1/isbns + # HTTP ACCEPT MEDIA TYPE HEADER VERSIONING + - id: rewrite_accept_v2 + uri: http://127.0.0.1:8083 + predicates: + - Path=/books + - Header=accept, application/vnd.api\.v2\+json + filters: + - RewritePath=/books,/v2/books + - id: rewrite_accept_v2 + uri: http://127.0.0.1:8083 + predicates: + - Path=/books/{segment} + - Header=accept, application/vnd.api\.v2\+json + filters: + - RewritePath=/books/(?.*),/v2/books/$\{segment} + - id: rewrite_accept_v2 + uri: http://127.0.0.1:8081 + predicates: + - Path=/isbns + - Header=accept, application/vnd.api\.v2\+json + filters: + - RewritePath=/isbns,/v1/isbns + # URI PATH VERSIONING + - id: path_route + uri: http://127.0.0.1:8083 + predicates: + - Path=/v2/books + - id: path_route + uri: http://127.0.0.1:8083 + predicates: + - Path=/v2/books/{segment} + - id: path_route + uri: http://127.0.0.1:8081 + predicates: + - Path=/v2/isbns + filters: + - RewritePath=/v2/isbns,/v1/isbns + + +``` + +To propose a cohesive and coherent API to our customer, we chose to publish all our API endpoints with a v1 and v2 prefix. +Although [rest-number](../rest-number) only provides **ONE** version (i.e., the ``v1``), we will expose both on the gateway and rewrite the URL as following: + +```yaml + - id: path_route + uri: http://127.0.0.1:8081 + predicates: + - Path=/v2/isbns + filters: + - RewritePath=/v2/isbns,/v1/isbns +``` + +### Test it +Restart your gateway and test it: + +```jshelllanguage +./gradlew clean bootRun -p gateway +``` + +You can now use [the scripts files](../bin). +For every script (e.g., [``countBooks.sh``](../bin/countBooks.sh)), you have one which reach the V2 endpoints (e.g., [``countBooks-v2.sh``](../bin/countBooks-v2.sh)) + +Here is an example for the ``countBooks.sh`` script file copied to ``countBooks-v2.sh``. + +```jshelllanguage +http :8080/v2/books/count +``` +This action is voluntary simple. +Feel free to add an argument to the existing script files if you want ;-). + +> **Note** +> +> In this chapter, we have seen one part of the impacts of API versioning in configuration management. The most important part is done before, both in the GIT configuration and the release management. +> +> [Go then to chapter 5](05-conflicts.md) diff --git a/docs/05-conflicts.md b/docs/05-conflicts.md index 05f1506..0cb3e40 100644 --- a/docs/05-conflicts.md +++ b/docs/05-conflicts.md @@ -1,496 +1,496 @@ -# Dealing with breaking changes - -## TL;DR: What are you going to learn in this chapter? - -This chapter covers the following topics: - -1. Dig into Backward compatibility hassle and implement a solution to make both of the two versions work - -## Preamble - -Now it is time to move on. - -We just deprecated our [first version](../rest-book), and we must add new features for our new customers while bringing them wisely to our existing ones! - -How to migrate your customers who use the V1 to the V2 ? -Good question! - -The first thing to do is to communicate on a regular basis the roadmap and the planned your product End Of Life (EoL) milestones. - -By the way, our customer wants to get several authors for a same book. -Currently, one book could only get one author. -This functionality could be considered as a [breaking change](https://en.wiktionary.org/wiki/breaking_change). - -Beyond the API definition, this new functionality impacts the whole application. From the OpenAPI description to database schema. -How could we do that maintaining two versions of our API for our customers? - - -## V2 API Changes - -Stop both the [rest-book-2](../rest-book-2) and [rest-book](../rest-book) modules. - -In the [rest-books-2 OpenAPI description file](../rest-book-2/src/main/resources/openapi.yml), update the definition of the ``Book``. -Rename the fied ``author`` to ``authors`` and define it like this: - -```yaml - authors: - type: array - items: - $ref: '#/components/schemas/Author' -``` - -Now, create the Author object below the ``Maintenance`` object: - -````yaml - Author: - type: object - properties: - lastname: - type: string - firstname: - type: string - publicId: - type: string -```` - -Regenerate the corresponding classes: - -```jshelllanguage -./gradlew openApiGenerate -p rest-book-2 -``` - -You should see in the [generated sources folder](../rest-book-2/build/generated/src/main) the new ``AuthorDto`` class. - -If you build your application, you will get warnings. - -```jshelllanguage -./gradlew clean build -p rest-book-2 -``` - -```log -/workspace/rest-apis-versioning-workshop/rest-book-2/src/main/java/info/touret/bookstore/spring/book/mapper/BookMapper.java:11: warning: Unmapped target property: "author". - Book toBook(BookDto bookDto); - ^ -/workspace/rest-apis-versioning-workshop/rest-book-2/src/main/java/info/touret/bookstore/spring/book/mapper/BookMapper.java:13: warning: Unmapped target property: "authors". - BookDto toBookDto(Book book); - ^ -Note: /workspace/rest-apis-versioning-workshop/rest-book-2/build/generated/sources/annotationProcessor/java/main/info/touret/bookstore/spring/book/mapper/BookMapperImpl.java uses or overrides a deprecated API. -Note: Recompile with -Xlint:deprecation for details. -2 warnings - -``` - -## What's next? -Regarding the use case, we should apply this new relationship between the ``Book`` and ``Author`` objects into the whole application, from the API to the database. - -This new feature implies a breaking change. -How to add this feature without disturbing the existing customers? -We have few ways: - -* By isolating the different tenants in a dedicated database/schema. It means the database schema could be also versioned. -* By mixing the features in the same schema (adding a field author and an author list) -* By anticipating the depreciation of the v1, upgrading the database schema and updating it to keep return only one author to the author list. - -You got it: there is no free lunch! - -Although the first solution is the smartest, there are several impacts: database data migrations,lack of loose coupling between the API and the database, painful version upgrades and such like. - -In this workshop, we will implement the last option: - -## Create the new functionality in the V2 - -We will implement in this chapter this new functionality. - -### JPA Entities - -Create an ``Author`` entity with the following content in the [rest-book-2](../rest-book-2) project. -Use the ``info.touret.bookstore.spring.book.entity`` package. - -```java -package info.touret.bookstore.spring.book.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.ManyToMany; - -import java.io.Serializable; -import java.util.List; -import java.util.UUID; - -@Entity -public class Author implements Serializable { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private UUID publicId; - - - private String lastname; - - private String firstname; - - @ManyToMany(mappedBy = "authors") - private List books; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getLastname() { - return lastname; - } - - public void setLastname(String lastname) { - this.lastname = lastname; - } - - public String getFirstname() { - return firstname; - } - - public void setFirstname(String firstname) { - this.firstname = firstname; - } - - public UUID getPublicId() { - return publicId; - } - - public void setPublicId(UUID publicId) { - this.publicId = publicId; - } - - public List getBooks() { - return books; - } - - public void setBooks(List books) { - this.books = books; - } -} - -``` - -Modify the [``Book``](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/entity/Book.java) adding a new field ``authors``: - -Remove first the ``author`` field declaration and its getter/setter methods. - -```java -@ManyToMany(fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH,CascadeType.DETACH}) -private List authors; -``` - -You can add these import to add the ``@ManyToMany`` annotation and the ``List`` interface into the classpath: - -```java -import jakarta.persistence.*; - -import java.util.List; -``` - -Add finally the getters and setters: - -```java - public List getAuthors() { - return authors; - } - - public void setAuthors(List authors) { - this.authors = authors; - } -``` - -### Spring Data repository - -Create the repository ``AuthorRepository`` class in the package [``info.touret.bookstore.spring.book.repository``](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/repository): - -```java -package info.touret.bookstore.spring.book.repository; - -import info.touret.bookstore.spring.book.entity.Author; -import org.springframework.data.repository.CrudRepository; - -import java.util.Optional; -import java.util.UUID; - -public interface AuthorRepository extends CrudRepository { - Optional findByPublicId(UUID uuid); - -} - - -``` - -### Service layer implementation - -Here is how I implemented this new feature in the service layer within the [BookService](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/service/BookService.java). -It updates the ``persistBook`` and ``updateBook`` methods: - -```java -public Book updateBook(@Valid Book book) { - return bookRepository.save(updateBookGettingOrCreatingNewAuthors(book)); - } -[. ..] - -private Book updateBookGettingOrCreatingNewAuthors(Book book){ - book.setAuthors(book.getAuthors().stream().map(author -> - authorRepository.findByPublicId(author.getPublicId()).orElseGet(() -> { - author.setBooks(List.of(book)); - return author; - }) - ).toList()); - return book; - } - -private Book persistBook(Book book) { - var isbnNumbers = restTemplate.getForEntity(isbnServiceURL, IsbnNumbers.class).getBody(); - if (isbnNumbers != null) { - book.setIsbn13(isbnNumbers.getIsbn13()); - book.setIsbn13(isbnNumbers.getIsbn10()); - } - return bookRepository.save(updateBookGettingOrCreatingNewAuthors(book)); -} -``` - -You must also inject the ``AuthorRepository`` class in the constructor: - -```java - public BookService(BookRepository bookRepository, - AuthorRepository authorRepository, - RestTemplate restTemplate, - @Value("${booknumbers.api.url}") String isbnServiceURL, - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") CircuitBreakerFactory circuitBreakerFactory, - @Value("${book.find.limit:10}") Integer findLimit){ - this.bookRepository=bookRepository; - this.authorRepository=authorRepository; - this.restTemplate=restTemplate; - this.isbnServiceURL=isbnServiceURL; - - this.circuitBreakerFactory=circuitBreakerFactory; - this.findLimit=findLimit; - } -``` - -and add a field ``authorRepository``: - -```java -private final AuthorRepository authorRepository; -``` - -Finally you must add this import declaration: - -```java -import info.touret.bookstore.spring.book.repository.AuthorRepository; -``` - -### Import data - -You can remove all the data located in [``import.sql.ORI``](../rest-book-2/src/main/resources/import.sql.ORI) and replace it by: - -```sql -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1000,'Antonio','Concalves','7c11e1bf-1c74-4280-812b-cbc6038b7d21'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1001,'Roger','Kitain','b7896f56-3168-4d45-aca7-745e2071bca2'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1002,'Kinman','Chung','0138897a-a867-4f0a-8063-9744a289df15'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1003,'Lincoln','Baxter','6122e45a-4880-4014-96ef-162f380c06f3'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1004,'Antoine','Sabot-Durand','d61c3c68-bb34-4ebe-bce0-76240bf64361'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1005,'Adam','Bien','8c8fd370-a17b-419c-b175-8ca50010ee88'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1006,'Nigel','Deakin','640db75b-15e8-48d5-8ec7-9992d7ca4e05'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1007,'Ed','Burns','00ce3ab4-bce3-42f2-b1dd-2eec75b62b30'); -INSERT INTO author(id,firstname,lastname,public_id) VALUES (1008,'Arun','Gupta','640db75b-15e8-48d5-8ec7-9992d7ca4e05'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 997, '9781980399025', 'Understanding Bean Validation', 9, 'https://images-na.ssl-images-amazon.com/images/I/31fHenHChZL._SL160_.jpg', 'https://images-na.ssl-images-amazon.com/images/I/31fHenHChZL.jpg', 9.99, 129, 2018, 'In this fascicle will you will learn Bean Validation and use its different APIs to apply constraints on a bean, validate all sorts of constraints and write your own constraints'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 998, '9781093918977', 'Understanding JPA', 9, 'https://images-na.ssl-images-amazon.com/images/I/3122s2sjOtL._SL160_.jpg', 'https://images-na.ssl-images-amazon.com/images/I/3122s2sjOtL.jpg', 9.99, 246, 2019, 'In this fascicle, you will learn Java Persistence API, its annotations for mapping entities, as well as the Java Persistence Query Language and entity life cycle'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1002, '1931182311', 'Advanced Java EE Development for Rational Application Developer 7.5: Developers'' Guidebook Author: Robert McChesney Nov-2011', 3, 'http://ecx.images-amazon.com/images/I/51pri75YOPL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51pri75YOPL._SL160_.jpg', 9.99, 543, 2011, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1001, '1931182310', 'Advanced Java EE Development for Rational Application Developer 7.5: Developers'' Guidebook', 2, 'http://ecx.images-amazon.com/images/I/51bjnhlGbeL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51bjnhlGbeL._SL160_.jpg', 79.95, 752, 2011, 'Written by IBM senior field engineers and senior product development experts, this advanced book provides a solid look at the development of a range of core Java EE technologies, as well as an in-depth description of the development facilities provided by IBM Rational Application Developer version 7.5. Since the Java EE developmental platform incorporates a wide range of technologies from disparate and myriad sources, this up-to-date guidebook helps developers triumph over the complexity and depth of knowledge required to build successful architectures. Senior developers, engineers, and architects—especially those who work with Rational Application Developer and those seeking certification at the Sun-certified Java master-tier level or the IBM Rational Application Developer certified professional and certified advanced professional levels—will appreciate this convenient, single reference point.'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1003, '1931182312', 'Advanced Java EE Development for Rational Application Developer 7.5: Developers'' Guidebook 2nd , Seco edition by McChesney, Robert, Cole, Kameron, Raszka, Richard (2011) Paperback', 3, 'http://ecx.images-amazon.com/images/I/511pqkJd3UL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/511pqkJd3UL._SL160_.jpg', 9.99, 987, 2005, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1004, '1514210959', 'Advanced Java EE Development with WildFly', 3, 'http://ecx.images-amazon.com/images/I/51f7V8CEb7L._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51f7V8CEb7L._SL160_.jpg', 44.99, 416, 2015, 'Your one-stop guide to developing Java® EE applications with the Eclipse IDE, Maven, and WildFly® 8.1 About This Book •Develop Java EE 7 applications using the WildFly platform •Discover how to use EJB 3.x, JSF 2.x, Ajax, JAX-RS, JAX-WS, and Spring with WildFly 8.1 •A practical guide filled with easy-to-understand programming examples to help you gain hands-on experience with Java EE development using WildFly Who This Book Is For This book is for professional WildFly developers. If you are already using JBoss or WildFly but don''t use the Eclipse IDE and Maven for development, this book will show you how the Eclipse IDE and Maven facilitate the development of Java EE applications with WildFly 8.1. This book does not provide a beginner-level introduction to Java EE as it is written as an intermediate/advanced course in Java EE development with WildFly 8.1. In Detail This book starts with an introduction to EJB 3 and how to set up the environment, including the configuration of a MySQL database for use with WildFly. We will then develop object-relational mapping with Hibernate 4, build and package the application with Maven, and then deploy it in WildFly 8.1, followed by a demonstration of the use of Facelets in a web application. Moving on from that, we will create an Ajax application in the Eclipse IDE, compile and package it using Maven, and run the web application on WildFly 8.1 with a MySQL database. In the final leg of this book, we will discuss support for generating and parsing JSON with WildFly 8.1.'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1005, '8894038912', 'Advanced Jax-Ws Web Services', 2, 'http://ecx.images-amazon.com/images/I/31lRGN%2BfvDL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/31lRGN%2BfvDL._SL160_.jpg', 22.90, 154, 2014, 'In this book you''ll learn the concepts of Soap based Web Services architecture and get practical advice on building and deploying Web Services in the enterprise. Starting from the basics and the best practices for setting up a development environment, this book enters into the inner details of the Jax-Ws in a clear and concise way. You will also learn about the major toolkits available for creating, compiling and testing Soap Web Services and how to address common issues such as debugging data and securing its content. What you will learn: Move your first steps with Soap Web Services. Developing Web Services using top-down and bottom-up approach. Using Maven archetypes to speed up Web Services creation. Getting into the details of Jax-Ws types: Java to Xml mapping and Xml to Java Developing Soap Web Services on WildFly 8 and Tomcat. Running native Apache Cxf on WildFly Securing Web Services. Applying authentication policies to your services. Encrypting the communication.'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1006, '0071763929', 'Mike Meyers'' Guide to Supporting Windows 7 for CompTIA A+ Certification (Exams 701 & 702) (All-in-One)', 9, 'http://ecx.images-amazon.com/images/I/51Dk-Zq2I7L._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51Dk-Zq2I7L._SL160_.jpg', 30.00, 240, 2011, 'Mike Meyers'' Guide to Supporting Windows 7 for CompTIA A+ Certification, Exams 220-701 & 220-702 Get the latest information on the new Windows 7 topics and questions added to CompTIA A+ exams 220-701 and 220-702. A must-have companion to CompTIA A+ All-in-One Exam Guide, Seventh Edition andMike Meyers'' CompTIA A+ Guide to Managing and Troubleshooting PCs, Third Edition, this book focuses on the new exam objectives. Mike Meyers'' Guide to Supporting Windows 7 for CompTIA A+ Certification provides learning objectives at the beginning of each chapter, exam tips, practice exam questions, and in-depth explanations. Written by the leading authority on CompTIA A+ certification and training, this essential resource provides the up-to-date coverage you need to pass the exams with ease. Mike Meyers, CompTIA A+, CompTIA Network+, CompTIA Security+, MCP, is the industry''s leading authority on CompTIA A+ certification and the bestselling author of seven editions of CompTIA A+ All-in-One Exam Guide. He is the president of PC and network repair seminars for thousands of organizations throughout the world, and a member of CompTIA.'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1007, '1849512442', 'Apache Maven 3 Cookbook (Quick Answers to Common Problems)', 1, 'http://ecx.images-amazon.com/images/I/41uiGX%2BdFRL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/41uiGX%2BdFRL._SL160_.jpg', 39.99, 224, 2011, 'Take Flash to the next dimension by creating detailed, animated, and interactive 3D worlds with Away3D OverviewCreate stunning 3D environments with highly detailed texturesAnimate and transform all types of 3D objects, including 3D TextEliminate the need for expensive hardware with proven Away3D optimization techniques, without compromising on visual appeal Written in a practical and illustrative style, which will appeal to Away3D beginners and Flash developers alike In DetailAway3D is one of the most popular real-time 3D engines for Flash. Besides creating various detailed 3D environments you can also create animated 3D scenes, use various special effects, integrate third-party libraries, and much more. The possibilities of using this engine are endless. But will you be able take full advantage of all these features and make a 3D application that is picture perfect?. This is the best book for guiding you through Away3D, and the possibilities it opens up for the Flash platform. You''ll be able to create basic 3D objects, display lifelike animated characters, construct complex 3D scenes in stunning detail, and much more with this practical hands-on guide. Starting with the very basics, this book will walk you through creating your first Away3D application, and then move on to describe and demonstrate the many features that are available within Away3D such as lighting, shading, animation, 3D text, model loading and more. With the help of this comprehensive guide to all the information you ever needed to use Away3D, you''ll find yourself creating incredibly detailed 3D environments in no time. You begin with an overview of downloading the Away3D source code and configuring various authoring tools like Flex Builder, Flash Builder, FlashDevelop, and Flash CS4. Next you ease your way through creating your first primitive 3D object from scratch, then move on to creating stunning 3D environments with incredibly detailed textures and animations. You will make applications react to mouse events, with the click of a mouse – literally, learn ways to focus your camera and perfect your creation by viewing it from all angles, and take your Away3D application to the next level by overcoming the limitations in default Away3D algorithms. You will also learn optimization techniques to obtain the best performance from Away3D, without compromising on visual appeal. Create stunning real-world 3D Flash applications, right from displaying your first sphere to creating entire 3D cities, with plenty of tips to help you avoid common pitfalls. What you will learn from this bookDraw primitive shapes such as cubes, cones, spheres, and planes without having to manually construct them from their basic elements Add eye-catching special effects to your Away3D applicationWarp, curve, modify, and bend 3D text to your willFocus the Camera and view 3D objects from all anglesImprove mouse interactivity in your 3D applicationIntegrate third-party libraries such as TweenLite and Stardust with Away3D to animate 3D objects and create particle effects Use sprites and sprite classesUtilize the power of Pixel Bender for image processingExport 3D models from 3D modeling applications such as 3ds Max, Blender, MilkShape, and Sketch-Up Get practical tips on achieving maximum performance in your 3D applications'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1008, '2746062399', 'Apache Tomcat 7 - Guide d''administration du serveur Java EE 6 sous Windows et Linux', 3, 'http://ecx.images-amazon.com/images/I/5159Mv-pl3L._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/5159Mv-pl3L._SL160_.jpg', 9.99, 234, 1999, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1009, '2746086336', 'Apache Tomcat 8 - Guide d''administration du serveur Java EE 7 sous Windows et Linux', 3, 'http://ecx.images-amazon.com/images/I/413b2IfJmsL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/413b2IfJmsL._SL160_.jpg', 9.99, 123, 2009, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1010, '1492201448', 'Apache TomEE Cookbook: Apache TomEE Administrator Cookbook', 1, 'http://ecx.images-amazon.com/images/I/61fIAAa6poL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/61fIAAa6poL._SL160_.jpg', 35.00, 196, 2013, 'This cookbook is written for learning TomEE internals by server administrators and Java EE developers. Administrators will learn how to configure TomEE for using it in a production environment. Developers will learn how to create web applications using the Java™ Platform, Enterprise Edition 6 (Java EE 6) technologies provided by TomEE runtime and to deploy these enterprise applications into TomEE. Chapters of the book: Chapter 1 : Introduction Chapter 2 : Getting Started Chapter 3 : TomEE Architecture Chapter 4 : TomEE Web Server, Apache Tomcat Chapter 5 : TomEE EJB Lite Server, Apache OpenEJB Chapter 6 : Deployments in TomEE Chapter 7 : JavaEE Technologies Used in TomEE Chapter 8 : TomEE Security Chapter 9 : JNDI Naming In TomEE Chapter 10 : Transactions in TomEE Chapter 11 : TomEE Clustering Features Chapter 12 : TomEE WebSocket Protocol Support Chapter 13 : TomEE GUI Chapter 14 : Testing Techniques in TomEE Chapter 15 : TomEE Embedded Usage Chapter 16 : Useful References Chapter 17 : ASF License'); -INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1011, '148885162X', 'Application Server 131 Success Secrets: 131 Most Asked Questions On Application Server - What You Need To Know', 3, 'http://ecx.images-amazon.com/images/I/51s-9pleKyL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51s-9pleKyL._SL160_.jpg', 29.95, 60, 2014, 'A fresh Application Server approach. An program server may be whichever a code model that delivers a simplified tactic to generating an application-server effectuation, short of heed to what the program purposes are, either the server part of a concrete effectuation example. In whichever instance, the server''s purpose is committed to the effectual implementation of methods (programs, procedures, scripts) for helping its affected applications. There has never been a Application Server Guide like this. It contains 131 answers, much more than you can imagine; comprehensive answers and extensive details and references, with insights that have never before been offered in print. Get the information you need--fast! This all-embracing guide offers a thorough view of key knowledge and detailed insight. This Guide introduces what you want to know about Application Server. A quick look inside of some of the subjects covered: Web application server - URL mapping, Web application server - Web services, WebSphere Application Server - Version 8.5, Comparison of application servers - Haskell, Application server Advantages of application servers, Oracle Application Server 10g, WebSphere Application Server - Version 6.0, Application server Other platforms, Oracle Application Server 10g - Components, Base4 Application Server - History, Application server - Advantages of application servers, Application server - Java application servers, Comparison of application servers - Ruby, Web application server - Ajax, WebSphere Application Server for z/OS - WAS z/OS Platform Exploitation, Comparison of application servers - Smalltalk, TNAPS Application Server - Hosting, WebLogic Application Server, Sun Java System Application Server - Releases, WebSphere Application Server - Version 7.0, WebLogic Application Server - Components, SAP NetWeaver Application Server - Authentication, and much more…'); - -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (997,1000); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (998,1000); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1001,1001); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1002,1002); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1003,1003); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1004,1004); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1005,1000); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1006,1003); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1007,1005); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1008,1000); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1009,1006); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1010,1007); -INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1011,1008); -``` - - -### Tests - - -Delete the [OldBookControllerIT](../rest-book-2/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java) and the [OldBookDto](../rest-book/src/test/java/info/touret/bookstore/spring/book/dto/OldBookDto.java) class. -We don't need them anymore. - -### Test it - -Run the following command: - -```jshelllanguage -./gradlew clean build -p rest-book-2 -``` - -Check new if both your infrastructure and your config server are up. - -Run the application now: - -```jshelllanguage -./gradlew bootRun -p rest-book-2 -``` - -Try this request and verify you have a list of authors in every book - -For instance: - -```jshelllanguage -http :8083/v2/books -``` - -```json -[...] - "authors": [ - { - "firstname": "Antonio", - "lastname": "Concalves", - "publicId": "7c11e1bf-1c74-4280-812b-cbc6038b7d21" - } - ], - -``` - -## Striving for changes for existing customers in the v1 - -Now, the database is not usable as is for the V1. - -You have to modify it without impacting the service contract (i.e., your OpenAPI definition). -For this workshop, we will do the translation in the [mappers](../rest-book/src/main/java/info/touret/bookstore/spring/book/mapper). - -### JPA entities - -Copy/paste [the entities modified in the v2](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/entity) in -the [v1 module](../rest-book/src/main/java/info/touret/bookstore/spring/book/entity). -Update the Book entity uncommenting the excerpt attributes and the getter/setter. - -### Spring Data repository -Nothing to do here. - -### Service layer -Nothing to do here too - -### Mapper layer -Create a class ``AuthorMapper`` in the package ``info.touret.bookstore.spring.book.mapper`` with the following content: - -```java -package info.touret.bookstore.spring.book.mapper; - -import info.touret.bookstore.spring.book.entity.Author; -import org.mapstruct.Mapper; - -import java.util.List; - -@Mapper -public interface AuthorMapper { - - default String toString(List authors) { - if (authors != null && authors.get(0) != null) { - return authors.get(0).getFirstname() + " " + authors.get(0).getLastname(); - } - return null; - } - - default List toAuthorList(String author) { - if (author != null) { - Author newAuthor = new Author(); - newAuthor.setLastname(author); - return List.of(newAuthor); - } - return null; - } - -} - -``` - -Yes [``Null`` sucks](https://en.wikipedia.org/wiki/Tony_Hoare) but it is the MapStruct _normal_ way . - -In the [``BookMapper`` class](../rest-book/src/main/java/info/touret/bookstore/spring/book/mapper/BookMapper.java), declare the [``AuthorMapper``](../rest-book/src/main/java/info/touret/bookstore/spring/book/mapper/AuthorMapper.java) as a dependency and how to convert a list of authors to one and the other way around: - -```java -package info.touret.bookstore.spring.book.mapper; - -import info.touret.bookstore.spring.book.entity.Book; -import info.touret.bookstore.spring.book.generated.dto.BookDto; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; - -import java.util.List; - -@Mapper(uses = AuthorMapper.class) -public interface BookMapper { - @Mapping(source = "author",target = "authors") - Book toBook(BookDto bookDto); - - @Mapping(source = "authors",target = "author") - BookDto toBookDto(Book book); - - List toBookDtos(List books); -} - -``` - -### Import Data - -Copy paste the [v2 ``import.sql.ORI``](../rest-book-2/src/main/resources/import.sql.ORI) content into [the v1](../rest-book/src/main/resources/import.sql.ORI) (don't forget to remove the existing lines). - -### Tests - -In the same way than for the V1, delete the [OldBookControllerIT](../rest-book-2/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java) and the [OldBookDto](../rest-book/src/test/java/info/touret/bookstore/spring/book/dto/OldBookDto.java) class. -We don't need them anymore. - -### Test it - -Run the following command first: - -```jshelllanguage -./gradlew clean build -p rest-book -``` - -Then, start the rest-book module: - -```jshelllanguage -./gradlew bootRun -p rest-book -``` - -Reach then the API and check the author attribute: - -```jshelllanguage -http :8082/v1/books -``` - -> **Note** -> -> We have seen in this chapter the breaking changes potential issues. -> Adding new features creating new versions usually affect the previous ones. -> That's why it is recommended to only propose **TWO** alive versions to your customers. The V1 is deprecated and the V2 is the target version. -> -> Sometimes the workaround presented here is not possible. You have then to deal with database duplication and versioning. You can use [FlywayDB](https://flywaydb.org/) or [Liquibase](http://www.liquibase.org/) for that purpose. -> -> [Go then to chapter 6](06-authorization.md) +# Dealing with breaking changes + +## TL;DR: What are you going to learn in this chapter? + +This chapter covers the following topics: + +1. Dig into Backward compatibility hassle and implement a solution to make both of the two versions work + +## Preamble + +Now it is time to move on. + +We just deprecated our [first version](../rest-book), and we must add new features for our new customers while bringing them wisely to our existing ones! + +How to migrate your customers who use the V1 to the V2 ? +Good question! + +The first thing to do is to communicate on a regular basis the roadmap and the planned your product End Of Life (EoL) milestones. + +By the way, our customer wants to get several authors for a same book. +Currently, one book could only get one author. +This functionality could be considered as a [breaking change](https://en.wiktionary.org/wiki/breaking_change). + +Beyond the API definition, this new functionality impacts the whole application. From the OpenAPI description to database schema. +How could we do that maintaining two versions of our API for our customers? + + +## V2 API Changes + +Stop both the [rest-book-2](../rest-book-2) and [rest-book](../rest-book) modules. + +In the [rest-books-2 OpenAPI description file](../rest-book-2/src/main/resources/openapi.yml), update the definition of the ``Book``. +Rename the fied ``author`` to ``authors`` and define it like this: + +```yaml + authors: + type: array + items: + $ref: '#/components/schemas/Author' +``` + +Now, create the Author object below the ``Maintenance`` object: + +````yaml + Author: + type: object + properties: + lastname: + type: string + firstname: + type: string + publicId: + type: string +```` + +Regenerate the corresponding classes: + +```jshelllanguage +./gradlew openApiGenerate -p rest-book-2 +``` + +You should see in the [generated sources folder](../rest-book-2/build/generated/src/main) the new ``AuthorDto`` class. + +If you build your application, you will get warnings. + +```jshelllanguage +./gradlew clean build -p rest-book-2 +``` + +```log +/workspace/rest-apis-versioning-workshop/rest-book-2/src/main/java/info/touret/bookstore/spring/book/mapper/BookMapper.java:11: warning: Unmapped target property: "author". + Book toBook(BookDto bookDto); + ^ +/workspace/rest-apis-versioning-workshop/rest-book-2/src/main/java/info/touret/bookstore/spring/book/mapper/BookMapper.java:13: warning: Unmapped target property: "authors". + BookDto toBookDto(Book book); + ^ +Note: /workspace/rest-apis-versioning-workshop/rest-book-2/build/generated/sources/annotationProcessor/java/main/info/touret/bookstore/spring/book/mapper/BookMapperImpl.java uses or overrides a deprecated API. +Note: Recompile with -Xlint:deprecation for details. +2 warnings + +``` + +## What's next? +Regarding the use case, we should apply this new relationship between the ``Book`` and ``Author`` objects into the whole application, from the API to the database. + +This new feature implies a breaking change. +How to add this feature without disturbing the existing customers? +We have few ways: + +* By isolating the different tenants in a dedicated database/schema. It means the database schema could be also versioned. +* By mixing the features in the same schema (adding a field author and an author list) +* By anticipating the depreciation of the v1, upgrading the database schema and updating it to keep return only one author to the author list. + +You got it: there is no free lunch! + +Although the first solution is the smartest, there are several impacts: database data migrations,lack of loose coupling between the API and the database, painful version upgrades and such like. + +In this workshop, we will implement the last option: + +## Create the new functionality in the V2 + +We will implement in this chapter this new functionality. + +### JPA Entities + +Create an ``Author`` entity with the following content in the [rest-book-2](../rest-book-2) project. +Use the ``info.touret.bookstore.spring.book.entity`` package. + +```java +package info.touret.bookstore.spring.book.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; + +import java.io.Serializable; +import java.util.List; +import java.util.UUID; + +@Entity +public class Author implements Serializable { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private UUID publicId; + + + private String lastname; + + private String firstname; + + @ManyToMany(mappedBy = "authors") + private List books; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public UUID getPublicId() { + return publicId; + } + + public void setPublicId(UUID publicId) { + this.publicId = publicId; + } + + public List getBooks() { + return books; + } + + public void setBooks(List books) { + this.books = books; + } +} + +``` + +Modify the [``Book``](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/entity/Book.java) adding a new field ``authors``: + +Remove first the ``author`` field declaration and its getter/setter methods. + +```java +@ManyToMany(fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH,CascadeType.DETACH}) +private List authors; +``` + +You can add these import to add the ``@ManyToMany`` annotation and the ``List`` interface into the classpath: + +```java +import jakarta.persistence.*; + +import java.util.List; +``` + +Add finally the getters and setters: + +```java + public List getAuthors() { + return authors; + } + + public void setAuthors(List authors) { + this.authors = authors; + } +``` + +### Spring Data repository + +Create the repository ``AuthorRepository`` class in the package [``info.touret.bookstore.spring.book.repository``](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/repository): + +```java +package info.touret.bookstore.spring.book.repository; + +import info.touret.bookstore.spring.book.entity.Author; +import org.springframework.data.repository.CrudRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface AuthorRepository extends CrudRepository { + Optional findByPublicId(UUID uuid); + +} + + +``` + +### Service layer implementation + +Here is how I implemented this new feature in the service layer within the [BookService](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/service/BookService.java). +It updates the ``persistBook`` and ``updateBook`` methods: + +```java +public Book updateBook(@Valid Book book) { + return bookRepository.save(updateBookGettingOrCreatingNewAuthors(book)); + } +[. ..] + +private Book updateBookGettingOrCreatingNewAuthors(Book book){ + book.setAuthors(book.getAuthors().stream().map(author -> + authorRepository.findByPublicId(author.getPublicId()).orElseGet(() -> { + author.setBooks(List.of(book)); + return author; + }) + ).toList()); + return book; + } + +private Book persistBook(Book book) { + var isbnNumbers = restTemplate.getForEntity(isbnServiceURL, IsbnNumbers.class).getBody(); + if (isbnNumbers != null) { + book.setIsbn13(isbnNumbers.getIsbn13()); + book.setIsbn13(isbnNumbers.getIsbn10()); + } + return bookRepository.save(updateBookGettingOrCreatingNewAuthors(book)); +} +``` + +You must also inject the ``AuthorRepository`` class in the constructor: + +```java + public BookService(BookRepository bookRepository, + AuthorRepository authorRepository, + RestTemplate restTemplate, + @Value("${booknumbers.api.url}") String isbnServiceURL, + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") CircuitBreakerFactory circuitBreakerFactory, + @Value("${book.find.limit:10}") Integer findLimit){ + this.bookRepository=bookRepository; + this.authorRepository=authorRepository; + this.restTemplate=restTemplate; + this.isbnServiceURL=isbnServiceURL; + + this.circuitBreakerFactory=circuitBreakerFactory; + this.findLimit=findLimit; + } +``` + +and add a field ``authorRepository``: + +```java +private final AuthorRepository authorRepository; +``` + +Finally you must add this import declaration: + +```java +import info.touret.bookstore.spring.book.repository.AuthorRepository; +``` + +### Import data + +You can remove all the data located in [``import.sql.ORI``](../rest-book-2/src/main/resources/import.sql.ORI) and replace it by: + +```sql +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1000,'Antonio','Concalves','7c11e1bf-1c74-4280-812b-cbc6038b7d21'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1001,'Roger','Kitain','b7896f56-3168-4d45-aca7-745e2071bca2'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1002,'Kinman','Chung','0138897a-a867-4f0a-8063-9744a289df15'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1003,'Lincoln','Baxter','6122e45a-4880-4014-96ef-162f380c06f3'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1004,'Antoine','Sabot-Durand','d61c3c68-bb34-4ebe-bce0-76240bf64361'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1005,'Adam','Bien','8c8fd370-a17b-419c-b175-8ca50010ee88'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1006,'Nigel','Deakin','640db75b-15e8-48d5-8ec7-9992d7ca4e05'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1007,'Ed','Burns','00ce3ab4-bce3-42f2-b1dd-2eec75b62b30'); +INSERT INTO author(id,firstname,lastname,public_id) VALUES (1008,'Arun','Gupta','640db75b-15e8-48d5-8ec7-9992d7ca4e05'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 997, '9781980399025', 'Understanding Bean Validation', 9, 'https://images-na.ssl-images-amazon.com/images/I/31fHenHChZL._SL160_.jpg', 'https://images-na.ssl-images-amazon.com/images/I/31fHenHChZL.jpg', 9.99, 129, 2018, 'In this fascicle will you will learn Bean Validation and use its different APIs to apply constraints on a bean, validate all sorts of constraints and write your own constraints'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 998, '9781093918977', 'Understanding JPA', 9, 'https://images-na.ssl-images-amazon.com/images/I/3122s2sjOtL._SL160_.jpg', 'https://images-na.ssl-images-amazon.com/images/I/3122s2sjOtL.jpg', 9.99, 246, 2019, 'In this fascicle, you will learn Java Persistence API, its annotations for mapping entities, as well as the Java Persistence Query Language and entity life cycle'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1002, '1931182311', 'Advanced Java EE Development for Rational Application Developer 7.5: Developers'' Guidebook Author: Robert McChesney Nov-2011', 3, 'http://ecx.images-amazon.com/images/I/51pri75YOPL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51pri75YOPL._SL160_.jpg', 9.99, 543, 2011, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1001, '1931182310', 'Advanced Java EE Development for Rational Application Developer 7.5: Developers'' Guidebook', 2, 'http://ecx.images-amazon.com/images/I/51bjnhlGbeL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51bjnhlGbeL._SL160_.jpg', 79.95, 752, 2011, 'Written by IBM senior field engineers and senior product development experts, this advanced book provides a solid look at the development of a range of core Java EE technologies, as well as an in-depth description of the development facilities provided by IBM Rational Application Developer version 7.5. Since the Java EE developmental platform incorporates a wide range of technologies from disparate and myriad sources, this up-to-date guidebook helps developers triumph over the complexity and depth of knowledge required to build successful architectures. Senior developers, engineers, and architects—especially those who work with Rational Application Developer and those seeking certification at the Sun-certified Java master-tier level or the IBM Rational Application Developer certified professional and certified advanced professional levels—will appreciate this convenient, single reference point.'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1003, '1931182312', 'Advanced Java EE Development for Rational Application Developer 7.5: Developers'' Guidebook 2nd , Seco edition by McChesney, Robert, Cole, Kameron, Raszka, Richard (2011) Paperback', 3, 'http://ecx.images-amazon.com/images/I/511pqkJd3UL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/511pqkJd3UL._SL160_.jpg', 9.99, 987, 2005, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1004, '1514210959', 'Advanced Java EE Development with WildFly', 3, 'http://ecx.images-amazon.com/images/I/51f7V8CEb7L._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51f7V8CEb7L._SL160_.jpg', 44.99, 416, 2015, 'Your one-stop guide to developing Java® EE applications with the Eclipse IDE, Maven, and WildFly® 8.1 About This Book •Develop Java EE 7 applications using the WildFly platform •Discover how to use EJB 3.x, JSF 2.x, Ajax, JAX-RS, JAX-WS, and Spring with WildFly 8.1 •A practical guide filled with easy-to-understand programming examples to help you gain hands-on experience with Java EE development using WildFly Who This Book Is For This book is for professional WildFly developers. If you are already using JBoss or WildFly but don''t use the Eclipse IDE and Maven for development, this book will show you how the Eclipse IDE and Maven facilitate the development of Java EE applications with WildFly 8.1. This book does not provide a beginner-level introduction to Java EE as it is written as an intermediate/advanced course in Java EE development with WildFly 8.1. In Detail This book starts with an introduction to EJB 3 and how to set up the environment, including the configuration of a MySQL database for use with WildFly. We will then develop object-relational mapping with Hibernate 4, build and package the application with Maven, and then deploy it in WildFly 8.1, followed by a demonstration of the use of Facelets in a web application. Moving on from that, we will create an Ajax application in the Eclipse IDE, compile and package it using Maven, and run the web application on WildFly 8.1 with a MySQL database. In the final leg of this book, we will discuss support for generating and parsing JSON with WildFly 8.1.'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1005, '8894038912', 'Advanced Jax-Ws Web Services', 2, 'http://ecx.images-amazon.com/images/I/31lRGN%2BfvDL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/31lRGN%2BfvDL._SL160_.jpg', 22.90, 154, 2014, 'In this book you''ll learn the concepts of Soap based Web Services architecture and get practical advice on building and deploying Web Services in the enterprise. Starting from the basics and the best practices for setting up a development environment, this book enters into the inner details of the Jax-Ws in a clear and concise way. You will also learn about the major toolkits available for creating, compiling and testing Soap Web Services and how to address common issues such as debugging data and securing its content. What you will learn: Move your first steps with Soap Web Services. Developing Web Services using top-down and bottom-up approach. Using Maven archetypes to speed up Web Services creation. Getting into the details of Jax-Ws types: Java to Xml mapping and Xml to Java Developing Soap Web Services on WildFly 8 and Tomcat. Running native Apache Cxf on WildFly Securing Web Services. Applying authentication policies to your services. Encrypting the communication.'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1006, '0071763929', 'Mike Meyers'' Guide to Supporting Windows 7 for CompTIA A+ Certification (Exams 701 & 702) (All-in-One)', 9, 'http://ecx.images-amazon.com/images/I/51Dk-Zq2I7L._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51Dk-Zq2I7L._SL160_.jpg', 30.00, 240, 2011, 'Mike Meyers'' Guide to Supporting Windows 7 for CompTIA A+ Certification, Exams 220-701 & 220-702 Get the latest information on the new Windows 7 topics and questions added to CompTIA A+ exams 220-701 and 220-702. A must-have companion to CompTIA A+ All-in-One Exam Guide, Seventh Edition andMike Meyers'' CompTIA A+ Guide to Managing and Troubleshooting PCs, Third Edition, this book focuses on the new exam objectives. Mike Meyers'' Guide to Supporting Windows 7 for CompTIA A+ Certification provides learning objectives at the beginning of each chapter, exam tips, practice exam questions, and in-depth explanations. Written by the leading authority on CompTIA A+ certification and training, this essential resource provides the up-to-date coverage you need to pass the exams with ease. Mike Meyers, CompTIA A+, CompTIA Network+, CompTIA Security+, MCP, is the industry''s leading authority on CompTIA A+ certification and the bestselling author of seven editions of CompTIA A+ All-in-One Exam Guide. He is the president of PC and network repair seminars for thousands of organizations throughout the world, and a member of CompTIA.'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1007, '1849512442', 'Apache Maven 3 Cookbook (Quick Answers to Common Problems)', 1, 'http://ecx.images-amazon.com/images/I/41uiGX%2BdFRL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/41uiGX%2BdFRL._SL160_.jpg', 39.99, 224, 2011, 'Take Flash to the next dimension by creating detailed, animated, and interactive 3D worlds with Away3D OverviewCreate stunning 3D environments with highly detailed texturesAnimate and transform all types of 3D objects, including 3D TextEliminate the need for expensive hardware with proven Away3D optimization techniques, without compromising on visual appeal Written in a practical and illustrative style, which will appeal to Away3D beginners and Flash developers alike In DetailAway3D is one of the most popular real-time 3D engines for Flash. Besides creating various detailed 3D environments you can also create animated 3D scenes, use various special effects, integrate third-party libraries, and much more. The possibilities of using this engine are endless. But will you be able take full advantage of all these features and make a 3D application that is picture perfect?. This is the best book for guiding you through Away3D, and the possibilities it opens up for the Flash platform. You''ll be able to create basic 3D objects, display lifelike animated characters, construct complex 3D scenes in stunning detail, and much more with this practical hands-on guide. Starting with the very basics, this book will walk you through creating your first Away3D application, and then move on to describe and demonstrate the many features that are available within Away3D such as lighting, shading, animation, 3D text, model loading and more. With the help of this comprehensive guide to all the information you ever needed to use Away3D, you''ll find yourself creating incredibly detailed 3D environments in no time. You begin with an overview of downloading the Away3D source code and configuring various authoring tools like Flex Builder, Flash Builder, FlashDevelop, and Flash CS4. Next you ease your way through creating your first primitive 3D object from scratch, then move on to creating stunning 3D environments with incredibly detailed textures and animations. You will make applications react to mouse events, with the click of a mouse – literally, learn ways to focus your camera and perfect your creation by viewing it from all angles, and take your Away3D application to the next level by overcoming the limitations in default Away3D algorithms. You will also learn optimization techniques to obtain the best performance from Away3D, without compromising on visual appeal. Create stunning real-world 3D Flash applications, right from displaying your first sphere to creating entire 3D cities, with plenty of tips to help you avoid common pitfalls. What you will learn from this bookDraw primitive shapes such as cubes, cones, spheres, and planes without having to manually construct them from their basic elements Add eye-catching special effects to your Away3D applicationWarp, curve, modify, and bend 3D text to your willFocus the Camera and view 3D objects from all anglesImprove mouse interactivity in your 3D applicationIntegrate third-party libraries such as TweenLite and Stardust with Away3D to animate 3D objects and create particle effects Use sprites and sprite classesUtilize the power of Pixel Bender for image processingExport 3D models from 3D modeling applications such as 3ds Max, Blender, MilkShape, and Sketch-Up Get practical tips on achieving maximum performance in your 3D applications'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1008, '2746062399', 'Apache Tomcat 7 - Guide d''administration du serveur Java EE 6 sous Windows et Linux', 3, 'http://ecx.images-amazon.com/images/I/5159Mv-pl3L._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/5159Mv-pl3L._SL160_.jpg', 9.99, 234, 1999, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1009, '2746086336', 'Apache Tomcat 8 - Guide d''administration du serveur Java EE 7 sous Windows et Linux', 3, 'http://ecx.images-amazon.com/images/I/413b2IfJmsL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/413b2IfJmsL._SL160_.jpg', 9.99, 123, 2009, 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1010, '1492201448', 'Apache TomEE Cookbook: Apache TomEE Administrator Cookbook', 1, 'http://ecx.images-amazon.com/images/I/61fIAAa6poL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/61fIAAa6poL._SL160_.jpg', 35.00, 196, 2013, 'This cookbook is written for learning TomEE internals by server administrators and Java EE developers. Administrators will learn how to configure TomEE for using it in a production environment. Developers will learn how to create web applications using the Java™ Platform, Enterprise Edition 6 (Java EE 6) technologies provided by TomEE runtime and to deploy these enterprise applications into TomEE. Chapters of the book: Chapter 1 : Introduction Chapter 2 : Getting Started Chapter 3 : TomEE Architecture Chapter 4 : TomEE Web Server, Apache Tomcat Chapter 5 : TomEE EJB Lite Server, Apache OpenEJB Chapter 6 : Deployments in TomEE Chapter 7 : JavaEE Technologies Used in TomEE Chapter 8 : TomEE Security Chapter 9 : JNDI Naming In TomEE Chapter 10 : Transactions in TomEE Chapter 11 : TomEE Clustering Features Chapter 12 : TomEE WebSocket Protocol Support Chapter 13 : TomEE GUI Chapter 14 : Testing Techniques in TomEE Chapter 15 : TomEE Embedded Usage Chapter 16 : Useful References Chapter 17 : ASF License'); +INSERT INTO Book(id, isbn_13, title, rank, small_image_url, medium_image_url, price, nb_of_pages, year_of_publication, description) VALUES ( 1011, '148885162X', 'Application Server 131 Success Secrets: 131 Most Asked Questions On Application Server - What You Need To Know', 3, 'http://ecx.images-amazon.com/images/I/51s-9pleKyL._SL75_.jpg', 'http://ecx.images-amazon.com/images/I/51s-9pleKyL._SL160_.jpg', 29.95, 60, 2014, 'A fresh Application Server approach. An program server may be whichever a code model that delivers a simplified tactic to generating an application-server effectuation, short of heed to what the program purposes are, either the server part of a concrete effectuation example. In whichever instance, the server''s purpose is committed to the effectual implementation of methods (programs, procedures, scripts) for helping its affected applications. There has never been a Application Server Guide like this. It contains 131 answers, much more than you can imagine; comprehensive answers and extensive details and references, with insights that have never before been offered in print. Get the information you need--fast! This all-embracing guide offers a thorough view of key knowledge and detailed insight. This Guide introduces what you want to know about Application Server. A quick look inside of some of the subjects covered: Web application server - URL mapping, Web application server - Web services, WebSphere Application Server - Version 8.5, Comparison of application servers - Haskell, Application server Advantages of application servers, Oracle Application Server 10g, WebSphere Application Server - Version 6.0, Application server Other platforms, Oracle Application Server 10g - Components, Base4 Application Server - History, Application server - Advantages of application servers, Application server - Java application servers, Comparison of application servers - Ruby, Web application server - Ajax, WebSphere Application Server for z/OS - WAS z/OS Platform Exploitation, Comparison of application servers - Smalltalk, TNAPS Application Server - Hosting, WebLogic Application Server, Sun Java System Application Server - Releases, WebSphere Application Server - Version 7.0, WebLogic Application Server - Components, SAP NetWeaver Application Server - Authentication, and much more…'); + +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (997,1000); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (998,1000); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1001,1001); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1002,1002); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1003,1003); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1004,1004); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1005,1000); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1006,1003); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1007,1005); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1008,1000); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1009,1006); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1010,1007); +INSERT INTO BOOK_AUTHORS(books_id,authors_id) values (1011,1008); +``` + + +### Tests + + +Delete the [OldBookControllerIT](../rest-book-2/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java) and the [OldBookDto](../rest-book/src/test/java/info/touret/bookstore/spring/book/dto/OldBookDto.java) class. +We don't need them anymore. + +### Test it + +Run the following command: + +```jshelllanguage +./gradlew clean build -p rest-book-2 +``` + +Check new if both your infrastructure and your config server are up. + +Run the application now: + +```jshelllanguage +./gradlew bootRun -p rest-book-2 +``` + +Try this request and verify you have a list of authors in every book + +For instance: + +```jshelllanguage +http :8083/v2/books +``` + +```json +[...] + "authors": [ + { + "firstname": "Antonio", + "lastname": "Concalves", + "publicId": "7c11e1bf-1c74-4280-812b-cbc6038b7d21" + } + ], + +``` + +## Striving for changes for existing customers in the v1 + +Now, the database is not usable as is for the V1. + +You have to modify it without impacting the service contract (i.e., your OpenAPI definition). +For this workshop, we will do the translation in the [mappers](../rest-book/src/main/java/info/touret/bookstore/spring/book/mapper). + +### JPA entities + +Copy/paste [the entities modified in the v2](../rest-book-2/src/main/java/info/touret/bookstore/spring/book/entity) in +the [v1 module](../rest-book/src/main/java/info/touret/bookstore/spring/book/entity). +Update the Book entity uncommenting the excerpt attributes and the getter/setter. + +### Spring Data repository +Nothing to do here. + +### Service layer +Nothing to do here too + +### Mapper layer +Create a class ``AuthorMapper`` in the package ``info.touret.bookstore.spring.book.mapper`` with the following content: + +```java +package info.touret.bookstore.spring.book.mapper; + +import info.touret.bookstore.spring.book.entity.Author; +import org.mapstruct.Mapper; + +import java.util.List; + +@Mapper +public interface AuthorMapper { + + default String toString(List authors) { + if (authors != null && authors.get(0) != null) { + return authors.get(0).getFirstname() + " " + authors.get(0).getLastname(); + } + return null; + } + + default List toAuthorList(String author) { + if (author != null) { + Author newAuthor = new Author(); + newAuthor.setLastname(author); + return List.of(newAuthor); + } + return null; + } + +} + +``` + +Yes [``Null`` sucks](https://en.wikipedia.org/wiki/Tony_Hoare) but it is the MapStruct _normal_ way . + +In the [``BookMapper`` class](../rest-book/src/main/java/info/touret/bookstore/spring/book/mapper/BookMapper.java), declare the [``AuthorMapper``](../rest-book/src/main/java/info/touret/bookstore/spring/book/mapper/AuthorMapper.java) as a dependency and how to convert a list of authors to one and the other way around: + +```java +package info.touret.bookstore.spring.book.mapper; + +import info.touret.bookstore.spring.book.entity.Book; +import info.touret.bookstore.spring.book.generated.dto.BookDto; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.List; + +@Mapper(uses = AuthorMapper.class) +public interface BookMapper { + @Mapping(source = "author",target = "authors") + Book toBook(BookDto bookDto); + + @Mapping(source = "authors",target = "author") + BookDto toBookDto(Book book); + + List toBookDtos(List books); +} + +``` + +### Import Data + +Copy paste the [v2 ``import.sql.ORI``](../rest-book-2/src/main/resources/import.sql.ORI) content into [the v1](../rest-book/src/main/resources/import.sql.ORI) (don't forget to remove the existing lines). + +### Tests + +In the same way than for the V1, delete the [OldBookControllerIT](../rest-book-2/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java) and the [OldBookDto](../rest-book/src/test/java/info/touret/bookstore/spring/book/dto/OldBookDto.java) class. +We don't need them anymore. + +### Test it + +Run the following command first: + +```jshelllanguage +./gradlew clean build -p rest-book +``` + +Then, start the rest-book module: + +```jshelllanguage +./gradlew bootRun -p rest-book +``` + +Reach then the API and check the author attribute: + +```jshelllanguage +http :8082/v1/books +``` + +> **Note** +> +> We have seen in this chapter the breaking changes potential issues. +> Adding new features creating new versions usually affect the previous ones. +> That's why it is recommended to only propose **TWO** alive versions to your customers. The V1 is deprecated and the V2 is the target version. +> +> Sometimes the workaround presented here is not possible. You have then to deal with database duplication and versioning. You can use [FlywayDB](https://flywaydb.org/) or [Liquibase](http://www.liquibase.org/) for that purpose. +> +> [Go then to chapter 6](06-authorization.md) diff --git a/docs/06-authorization.md b/docs/06-authorization.md index 3e3cbf5..3c22111 100644 --- a/docs/06-authorization.md +++ b/docs/06-authorization.md @@ -1,241 +1,241 @@ -# Last but not least : what about security and authorization impacts? - -## TL;DR: What are you going to learn in this chapter? - -This chapter covers the following topics: - -1. Pinpointing the impacts on authorization -2. Enforcing API versions restrictions with OAUTHv2 scopes - -While versioning secured APIs, there is usually one impact we miss at the beginning: security, especially authorization. -If you apply authorization policies on your whole platform using for instance, [ABAC](https://en.wikipedia.org/wiki/Attribute-based_access_control) or [RBAC](https://en.wikipedia.org/wiki/Role-based_access_control) approaches, you must take care about it. -They could indeed evolve over your versions. - -If you use [OAUTHv2](https://www.rfc-editor.org/rfc/rfc6749.html) or [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) (_what else?_), you would restrict the usage of a version to specific clients or end users using [scopes](https://auth0.com/docs/get-started/apis/scopes) stored in [claims](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims). - -You can declare scopes stored in claims such as: ``book:v1:write`` or ``number:v2:read`` to specify both the authorised -action and the corresponding version. - -We will see in this chapter how a standard [``credential flow`` authorization mechanism](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) can handle versioning. - -> **Note** -> -> * In the same way as for version handling, we will apply the security only in the gateway using the authorization server. -> * **In this chapter, we will only authorise URI Path service versions.** - -## Enabling security - -Before starting, please stop the [gateway](../gateway) and the [authorization server](../authorization-server). - -### Authorization server - -In the [``application.properties`` file](../authorization-server/src/main/resources/application.properties), update the configuration with the good scopes: - -```properties -server.port=8009 -spring.application.name=authorization-server -authorization.url=http://localhost:${server.port} -authorization.clients.customer1.clientId=customer1 -authorization.clients.customer1.clientSecret=secret1 -authorization.clients.customer1.scopes=book:v1:read,book:v1:write,number:v1:read -authorization.clients.customer2.clientId=customer2 -authorization.clients.customer2.clientSecret=secret2 -authorization.clients.customer2.scopes=book:v2:read,book:v2:write,number:v2:read -authorization.clients.gateway.clientId=gateway -authorization.clients.gateway.clientSecret=secret3 -authorization.clients.gateway.scopes=gateway -logging.level.org.springframework.web:INFO -logging.level.org.springframework.security:INFO -logging.level.org.springframework.security.oauth2:INFO -spring.security.oauth2.resourceserver.jwt.issuer-uri=${authorization.url} -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${authorization.url} -spring.zipkin.base-url=http://localhost:9411 -spring.zipkin.sender.type=web -management.tracing.sampling.probability=1.0 -management.endpoints.web.exposure.include=prometheus -``` - -In this example, we declared the ``customer1`` can use the version 1 and the ``customer2`` the v2. - -You can start it now: - -```jshelllanguage -./gradlew bootRun -p authorization-server -``` - -#### Test it - -You can now try to generate token as either the ``customer1`` or ``customer2``: - -For ``customer1``: - -```jshelllanguage -http --form :8009/oauth2/token grant_type="client_credentials" client_id="customer1" ="secret1" scope="openid book:v1:write book:v1:write number:v1:read" -``` - -```jshelllanguage -http --form :8009/oauth2/token grant_type="client_credentials" client_id="customer2" client_secret="secret2" scope="openid book:v2:write book:v2:read number:v2:read" -``` - -Verify you have the corresponding scopes. -Here is the customer2's token: - -```json -{ - "access_token": "eyJraWQiOiJiMmI5NjFjYi1lM2VlLTQ5OGMtOGIxNi01YmFmZTRjYzZmOWEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJjdXN0b21lcjIiLCJhdWQiOiJjdXN0b21lcjIiLCJuYmYiOjE2OTYyNjA1NjQsInNjb3BlIjpbIm9wZW5pZCIsImJvb2s6djI6cmVhZCIsImJvb2s6djI6d3JpdGUiLCJudW1iZXI6djI6cmVhZCJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDkiLCJleHAiOjE2OTYyNjA4NjQsImlhdCI6MTY5NjI2MDU2NH0.bC-2X4Zfz7TRPZ45zPhhKVPpOg6rZH0FSskL8Z7cIq-iAUiSwoSK60kUKcgEKVgjlfZfBge2B0yvSExCM16Bf_7HhbKppbUjLJ7dO3to_oh1TJVdpdG54l_2hIRI3SGFVxaKk9NpkXbiPq4-nT2HdVbrtd6JlB0R0ticKqhjOJElosA7jGQ-YoCVSJxpdrlcahI-1I0kX_0vqD_iN58XU-saqGG3cG9hG-NjR_NCj5DYG4AEUWu-wFQlRrG8IBwJJmlS3ibM-uVU9jG2mLNrJsCMTJccVnoQ9J17T3L5twEyXg511qlCyqJFvDXSg03pxPFYxex_Yz1GpIcvjnyn_A", - "expires_in": 299, - "scope": "openid book:v2:read book:v2:write number:v2:read", - "token_type": "Bearer" -} - -``` - -You can also try using inappropriate scopes (e.g., using ``bookv1:read`` scope for ``customer2``). - -You MUST have this error: - -```json -{ - "error": "invalid_scope" -} - -``` - -If you want you can also verify the ``access_token`` and the claims on [jwt.io](https://jwt.io/) website. - -After copying/pasting the access token, you can see the following output with the corresponding roles: - -```json -{ - "sub": "customer2", - "aud": "customer2", - "nbf": 1696260564, - "scope": [ - "openid", - "book:v2:read", - "book:v2:write", - "number:v2:read" - ], - "iss": "http://localhost:8009", - "exp": 1696260864, - "iat": 1696260564 -} -``` - - -Finally, if you don't know how to create [OIDC requests](https://openid.net/developers/how-connect-works/) by your own, you can use https://oidcdebugger.com/. - -### Declare routes and corresponding scopes in the gateway - -In [the gateway's configuration](../gateway/src/main/resources/application.yml), enable first the security uncommenting these lines: - -```yaml -# SECURITY CONFIGURATION TO BE APPLIED (remove comments to apply it) - security: - oauth2: - client: - registration: - login-client: - provider: authz - client-id: gateway - client-secret: secret3 - authorization-grant-type: client_credentials - redirect-uri-template: "{baseUrl}/" - scope: gateway - provider: - authz: - authorization-uri: http://localhost:8009/oauth2/authorize - token-uri: http://localhost:8009/oauth2/token - user-info-uri: http://localhost:8009/oauth2/userinfo - user-name-attribute: sub - jwk-set-uri: http://localhost:8009/oauth2/token_keys - resourceserver: - jwt: - jwk-set-uri: http://localhost:8009 -``` - -Uncomment block codes in the [gateway application](../gateway/src/main/java/info/touret/bookstore/spring/gateway/GatewayApplication.java) to get the following content: - -```java - @Bean - SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { - - http.csrf(ServerHttpSecurity.CsrfSpec::disable) - .authorizeExchange(exchanges -> exchanges - .pathMatchers(GET, "/v1/books/count").hasAuthority("SCOPE_bookv1:read") - .pathMatchers(GET, "/v1/books/random").hasAuthority("SCOPE_bookv1:read") - .pathMatchers(POST, "/v1/books").hasAuthority("SCOPE_bookv1:write") - .pathMatchers(GET, "/v1/books").hasAuthority("SCOPE_bookv1:read") - .pathMatchers("/v1/isbns").hasAuthority("SCOPE_numberv1:read") - .pathMatchers(GET, "/v2/books/count").hasAuthority("SCOPE_bookv2:read") - .pathMatchers(GET, "/v2/books/random").hasAuthority("SCOPE_bookv2:read") - .pathMatchers(POST, "/v2/books").hasAuthority("SCOPE_bookv2:write") - .pathMatchers(GET, "/v2/books").hasAuthority("SCOPE_bookv2:read") - .pathMatchers("/v2/isbns").hasAuthority("SCOPE_numberv2:read") - .anyExchange().authenticated() - ) - .oauth2ResourceServer(oAuth2ResourceServerSpec -> oAuth2ResourceServerSpec.jwt(Customizer.withDefaults())); - /* If the previous configuration is applied, you would remove this following line (and the other way around) - http.csrf(ServerHttpSecurity.CsrfSpec::disable) - .authorizeExchange(authorizeExchangeSpec -> authorizeExchangeSpec.anyExchange().permitAll());*/ - return http.build(); - } - - /* If the security is enabled, you MUST uncomment the following factories */ - @Bean - JwtDecoder jwtDecoder(OAuth2ResourceServerProperties properties) { - return NimbusJwtDecoder.withJwkSetUri(properties.getJwt().getJwkSetUri()).build(); - - } - - @Bean - public ReactiveJwtDecoder reactiveJwtDecoder(@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String issuerUrl) { - return ReactiveJwtDecoders.fromIssuerLocation(issuerUrl); - } -``` - -Update then the import statements: - -```java -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import static org.springframework.http.HttpMethod.GET; -import static org.springframework.http.HttpMethod.POST; - -``` - -Now restart the gateway: - -```jshelllanguage -./gradlew bootRun -p gateway -``` - -#### Test it - -Update the scripts with the appropriate version numbers in scopes and the corresponding ``client_id`` and ``client_secret``. - -For instance, in the [``secureCountBooks.sh`` script file](../bin/secureCountBooks.sh), check you have the good scopes: - -```jshelllanguage -#! /bin/bash - - -access_token=`http --form post :8009/oauth2/token grant_type="client_credentials" client_id="customer1" client_secret="secret1" scope="openid bookv1:read" -p b | jq -r '.access_token'` - -http :8080/v1/books/count "Authorization: Bearer ${access_token}" - -``` - -Try them all! +# Last but not least : what about security and authorization impacts? + +## TL;DR: What are you going to learn in this chapter? + +This chapter covers the following topics: + +1. Pinpointing the impacts on authorization +2. Enforcing API versions restrictions with OAUTHv2 scopes + +While versioning secured APIs, there is usually one impact we miss at the beginning: security, especially authorization. +If you apply authorization policies on your whole platform using for instance, [ABAC](https://en.wikipedia.org/wiki/Attribute-based_access_control) or [RBAC](https://en.wikipedia.org/wiki/Role-based_access_control) approaches, you must take care about it. +They could indeed evolve over your versions. + +If you use [OAUTHv2](https://www.rfc-editor.org/rfc/rfc6749.html) or [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) (_what else?_), you would restrict the usage of a version to specific clients or end users using [scopes](https://auth0.com/docs/get-started/apis/scopes) stored in [claims](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims). + +You can declare scopes stored in claims such as: ``book:v1:write`` or ``number:v2:read`` to specify both the authorised +action and the corresponding version. + +We will see in this chapter how a standard [``credential flow`` authorization mechanism](https://www.rfc-editor.org/rfc/rfc6749#section-4.4) can handle versioning. + +> **Note** +> +> * In the same way as for version handling, we will apply the security only in the gateway using the authorization server. +> * **In this chapter, we will only authorise URI Path service versions.** + +## Enabling security + +Before starting, please stop the [gateway](../gateway) and the [authorization server](../authorization-server). + +### Authorization server + +In the [``application.properties`` file](../authorization-server/src/main/resources/application.properties), update the configuration with the good scopes: + +```properties +server.port=8009 +spring.application.name=authorization-server +authorization.url=http://localhost:${server.port} +authorization.clients.customer1.clientId=customer1 +authorization.clients.customer1.clientSecret=secret1 +authorization.clients.customer1.scopes=book:v1:read,book:v1:write,number:v1:read +authorization.clients.customer2.clientId=customer2 +authorization.clients.customer2.clientSecret=secret2 +authorization.clients.customer2.scopes=book:v2:read,book:v2:write,number:v2:read +authorization.clients.gateway.clientId=gateway +authorization.clients.gateway.clientSecret=secret3 +authorization.clients.gateway.scopes=gateway +logging.level.org.springframework.web:INFO +logging.level.org.springframework.security:INFO +logging.level.org.springframework.security.oauth2:INFO +spring.security.oauth2.resourceserver.jwt.issuer-uri=${authorization.url} +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${authorization.url} +spring.zipkin.base-url=http://localhost:9411 +spring.zipkin.sender.type=web +management.tracing.sampling.probability=1.0 +management.endpoints.web.exposure.include=prometheus +``` + +In this example, we declared the ``customer1`` can use the version 1 and the ``customer2`` the v2. + +You can start it now: + +```jshelllanguage +./gradlew bootRun -p authorization-server +``` + +#### Test it + +You can now try to generate token as either the ``customer1`` or ``customer2``: + +For ``customer1``: + +```jshelllanguage +http --form :8009/oauth2/token grant_type="client_credentials" client_id="customer1" ="secret1" scope="openid book:v1:write book:v1:write number:v1:read" +``` + +```jshelllanguage +http --form :8009/oauth2/token grant_type="client_credentials" client_id="customer2" client_secret="secret2" scope="openid book:v2:write book:v2:read number:v2:read" +``` + +Verify you have the corresponding scopes. +Here is the customer2's token: + +```json +{ + "access_token": "eyJraWQiOiJiMmI5NjFjYi1lM2VlLTQ5OGMtOGIxNi01YmFmZTRjYzZmOWEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJjdXN0b21lcjIiLCJhdWQiOiJjdXN0b21lcjIiLCJuYmYiOjE2OTYyNjA1NjQsInNjb3BlIjpbIm9wZW5pZCIsImJvb2s6djI6cmVhZCIsImJvb2s6djI6d3JpdGUiLCJudW1iZXI6djI6cmVhZCJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwMDkiLCJleHAiOjE2OTYyNjA4NjQsImlhdCI6MTY5NjI2MDU2NH0.bC-2X4Zfz7TRPZ45zPhhKVPpOg6rZH0FSskL8Z7cIq-iAUiSwoSK60kUKcgEKVgjlfZfBge2B0yvSExCM16Bf_7HhbKppbUjLJ7dO3to_oh1TJVdpdG54l_2hIRI3SGFVxaKk9NpkXbiPq4-nT2HdVbrtd6JlB0R0ticKqhjOJElosA7jGQ-YoCVSJxpdrlcahI-1I0kX_0vqD_iN58XU-saqGG3cG9hG-NjR_NCj5DYG4AEUWu-wFQlRrG8IBwJJmlS3ibM-uVU9jG2mLNrJsCMTJccVnoQ9J17T3L5twEyXg511qlCyqJFvDXSg03pxPFYxex_Yz1GpIcvjnyn_A", + "expires_in": 299, + "scope": "openid book:v2:read book:v2:write number:v2:read", + "token_type": "Bearer" +} + +``` + +You can also try using inappropriate scopes (e.g., using ``bookv1:read`` scope for ``customer2``). + +You MUST have this error: + +```json +{ + "error": "invalid_scope" +} + +``` + +If you want you can also verify the ``access_token`` and the claims on [jwt.io](https://jwt.io/) website. + +After copying/pasting the access token, you can see the following output with the corresponding roles: + +```json +{ + "sub": "customer2", + "aud": "customer2", + "nbf": 1696260564, + "scope": [ + "openid", + "book:v2:read", + "book:v2:write", + "number:v2:read" + ], + "iss": "http://localhost:8009", + "exp": 1696260864, + "iat": 1696260564 +} +``` + + +Finally, if you don't know how to create [OIDC requests](https://openid.net/developers/how-connect-works/) by your own, you can use https://oidcdebugger.com/. + +### Declare routes and corresponding scopes in the gateway + +In [the gateway's configuration](../gateway/src/main/resources/application.yml), enable first the security uncommenting these lines: + +```yaml +# SECURITY CONFIGURATION TO BE APPLIED (remove comments to apply it) + security: + oauth2: + client: + registration: + login-client: + provider: authz + client-id: gateway + client-secret: secret3 + authorization-grant-type: client_credentials + redirect-uri-template: "{baseUrl}/" + scope: gateway + provider: + authz: + authorization-uri: http://localhost:8009/oauth2/authorize + token-uri: http://localhost:8009/oauth2/token + user-info-uri: http://localhost:8009/oauth2/userinfo + user-name-attribute: sub + jwk-set-uri: http://localhost:8009/oauth2/token_keys + resourceserver: + jwt: + jwk-set-uri: http://localhost:8009 +``` + +Uncomment block codes in the [gateway application](../gateway/src/main/java/info/touret/bookstore/spring/gateway/GatewayApplication.java) to get the following content: + +```java + @Bean + SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { + + http.csrf(ServerHttpSecurity.CsrfSpec::disable) + .authorizeExchange(exchanges -> exchanges + .pathMatchers(GET, "/v1/books/count").hasAuthority("SCOPE_bookv1:read") + .pathMatchers(GET, "/v1/books/random").hasAuthority("SCOPE_bookv1:read") + .pathMatchers(POST, "/v1/books").hasAuthority("SCOPE_bookv1:write") + .pathMatchers(GET, "/v1/books").hasAuthority("SCOPE_bookv1:read") + .pathMatchers("/v1/isbns").hasAuthority("SCOPE_numberv1:read") + .pathMatchers(GET, "/v2/books/count").hasAuthority("SCOPE_bookv2:read") + .pathMatchers(GET, "/v2/books/random").hasAuthority("SCOPE_bookv2:read") + .pathMatchers(POST, "/v2/books").hasAuthority("SCOPE_bookv2:write") + .pathMatchers(GET, "/v2/books").hasAuthority("SCOPE_bookv2:read") + .pathMatchers("/v2/isbns").hasAuthority("SCOPE_numberv2:read") + .anyExchange().authenticated() + ) + .oauth2ResourceServer(oAuth2ResourceServerSpec -> oAuth2ResourceServerSpec.jwt(Customizer.withDefaults())); + /* If the previous configuration is applied, you would remove this following line (and the other way around) + http.csrf(ServerHttpSecurity.CsrfSpec::disable) + .authorizeExchange(authorizeExchangeSpec -> authorizeExchangeSpec.anyExchange().permitAll());*/ + return http.build(); + } + + /* If the security is enabled, you MUST uncomment the following factories */ + @Bean + JwtDecoder jwtDecoder(OAuth2ResourceServerProperties properties) { + return NimbusJwtDecoder.withJwkSetUri(properties.getJwt().getJwkSetUri()).build(); + + } + + @Bean + public ReactiveJwtDecoder reactiveJwtDecoder(@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") String issuerUrl) { + return ReactiveJwtDecoders.fromIssuerLocation(issuerUrl); + } +``` + +Update then the import statements: + +```java +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders; +import org.springframework.security.web.server.SecurityWebFilterChain; + +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; + +``` + +Now restart the gateway: + +```jshelllanguage +./gradlew bootRun -p gateway +``` + +#### Test it + +Update the scripts with the appropriate version numbers in scopes and the corresponding ``client_id`` and ``client_secret``. + +For instance, in the [``secureCountBooks.sh`` script file](../bin/secureCountBooks.sh), check you have the good scopes: + +```jshelllanguage +#! /bin/bash + + +access_token=`http --form post :8009/oauth2/token grant_type="client_credentials" client_id="customer1" client_secret="secret1" scope="openid bookv1:read" -p b | jq -r '.access_token'` + +http :8080/v1/books/count "Authorization: Bearer ${access_token}" + +``` + +Try them all!