Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(book): Fix ISBN setters chore(gradle) : update wrapper version to 7.6 doc: update chapter 1 applying code done and validated from solution #10

Merged
merged 7 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ C4Container

#### Explanations

This diagram dig into the systems exposed above in the system view.
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
Expand All @@ -87,7 +87,7 @@ The Bookstore system is composed of:
* 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
* 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:
Expand Down Expand Up @@ -147,6 +147,38 @@ You must have set up these tools first:
* 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 a696618@WL-941Y493
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment Temurin-17.0.5+8 (build 17.0.5+8)
OpenJDK 64-Bit Server VM Temurin-17.0.5+8 (build 17.0.5+8, mixed mode, sharing)

```

**Gradle**

If you use the wrapper, you won't have troubles. Otherwise...:

```jshelllanguage
gradle --version a696618@WL-941Y493

Welcome to Gradle 7.6!
```

**Docker Compose**

```jshelllanguage
docker compose version 16 ↵ a696618@WL-941Y493
Docker Compose version v2.12.2
```



#### :rocket: If you don't want to bother with a local setup

##### With Gitpod (recommended)
Expand Down Expand Up @@ -182,4 +214,4 @@ or you can directly browse this URL (think to change the ``%%MY_NAMESPACE%%`` pr

``https://gitpod.io/#github.com/%%MY_NAMESPACE%%/rest-apis-versioning-workshop.git``

Now, you can start [the workshop](./docs/index.md).
Now, you can start [the workshop](./docs/index.md) :tada:.
3 changes: 3 additions & 0 deletions authorization-server/src/test/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
opentracing:
jaeger:
enabled: false
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ subprojects {
implementation 'io.micrometer:micrometer-registry-prometheus'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
compileOnly 'org.springframework.boot:spring-boot-devtools'
}

bootJar {
Expand Down
3 changes: 3 additions & 0 deletions config-server/src/test/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
opentracing:
jaeger:
enabled: false
219 changes: 175 additions & 44 deletions docs/01-without_versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ At this point we have our first customer : **John Doe** who uses our API with th

## Prerequisites

You have to start three new shells and run [rest-book](../rest-book), [rest-number](../rest-number)
and [the gateway](../gateway) modules.
As earlier, you must be at the root of the project (i.e., ``rest-apis-versioning-workshop``).
You have to 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``).

<details>
<summary>Click to expand</summary>
Expand Down Expand Up @@ -57,6 +56,8 @@ You can also check the documentation by browsing these endpoints:

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:
Expand All @@ -70,7 +71,7 @@ You can get a random book by running this command:
. ./bin/createBook.sh
```

Now you can stop this service.
Now you can stop this service now.

## Adding new data

Expand All @@ -97,6 +98,15 @@ We will extract the first 100 characters.
./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 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)
.
Expand All @@ -105,29 +115,33 @@ We will extract the first 100 characters.

```java

private @Transient excerpt;
@Transient
private transient String excerpt;


// getter

@PostConstruct
private initFields(){
// Extract the first 100 characters of the description
this.excerpt=getDescription().substring(0,100);
public String getExcerpt(){
return this.excerpt;
}
```
You can now rebuild the application

Before creating unit and integration tests, we can run them to see if this modification is blocking.

<details>
<summary>Click to expand</summary>
@PostLoad
public void initFields(){
if(description!=null) {
this.excerpt = description.substring(0, 100);
}
}
```
You can now rebuild the application.

Run the tests with gradle
Before creating unit and integration tests, we can run them to see if this modification is blocking.

```jshelllanguage
./gradlew build -p rest-book
```
</details>

:question: See what happens: Is it blocking or not?

5. You can add a test in the [BookServiceTest](../rest-book/src/test/java/info/touret/bookstore/spring/book/service/BookServiceTest.java)
<details>
Expand All @@ -137,65 +151,182 @@ For instance:


```java
@Test
void should_find_a_random_book_with_excerpt() {
var longList = createBookList().stream().map(Book::getId).collect(Collectors.toList());
when(bookRepository.findAllIds()).thenReturn(longList);
Book book = new Book();
book.setId(1L);
when(bookRepository.findById(any(Long.class))).thenReturn(Optional.of(book));
assertNotNull(bookService.findRandomBook());
var book = bookService.findRandomBook();
assertEquals(book.getDescription().substring(0,100),book.getExcerpt());
}
@Test
void should_find_a_random_book_with_excerpt() {
var book = Mockito.mock(Book.class);
when(book.getId()).thenReturn(100L);
when(book.getDescription()).thenReturn("""
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.
""");
when(book.getExcerpt()).thenReturn("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l");
var longList = createBookList().stream().map(Book::getId).collect(Collectors.toList());
when(bookRepository.findAllIds()).thenReturn(longList);
when(bookRepository.findById(anyLong())).thenReturn(Optional.of(book));
assertNotNull(bookService.findRandomBook());
var bookFounded = bookService.findRandomBook();
assertEquals(book.getDescription().substring(0, 100), bookFounded.getExcerpt());
}
```
</details>

You can also add a similar test in the [BookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/BookControllerIT.java) integration test.

For instance, you can add this assertion in the [``should_get_a_random_book``](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/BookControllerIT.java):

```java
@Test
void should_get_a_random_book() {
var bookDto = testRestTemplate.getForEntity(booksUrl + "/random", BookDto.class).getBody();
assertNotNull(bookDto.getId());
assertEquals(bookDto.getDescription().substring(0,100),bookDto.getExcerpt());
}

```

Now you can re-build your application and validate it by running tests.

```jshelllanguage
./gradlew build -p rest-book
./gradlew clean build -p rest-book
```

6. Now, let's get a random book with an excerpt

You can restart your rest-book service

```jshelllanguage
./gradlew bootRun -p rest-book
```

You can check it manually by running the following command:

```jshelllanguage
http :8082/books/1098 --print b | jq .excerpt
```

You can also do that through the API Gateway:

```jshelllanguage
http :8080/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:
<details>
<summary>Click to expand</summary>

```jshelllanguage
./gradlew bootRun -p rest-book
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"

```
</details>

## Adding new operations
You can now generate the corresponding Java code.

```jshelllanguage
./gradlew openApiGenerate -p rest-book
```

Now, you can add a new integration test assertion:

In the [BookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/BookControllerIT.java) class, add the following method:

```java
@Test
void should_find_an_excerpt() throws Exception {
var responseEntity = testRestTemplate.getForEntity(booksUrl + "/100/excerpt", String.class);
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
var excerpt = responseEntity.getBody();
assertNotNull(excerpt);
assertEquals("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l", excerpt);
}
```

You can also add a new operation getExcerpt
Now, let us create the corresponding method in [BookController](../rest-book/src/main/java/info/touret/bookstore/spring/book/controller/BookController.java):

You just added a new data and functionality without versioning
Add the following method:

## What about backward compatibiliy?
```java
@Override
public ResponseEntity<String> getBookExcerpt(Long id) {
var optionalBook = bookService.findBookById(id);
if (optionalBook.isPresent()) {
return ResponseEntity.ok(optionalBook.get().getExcerpt());
} else {
return ResponseEntity.notFound().build();
}
}
```

Run tests again:

```jshelllanguage
./gradle build
```

You have now added new data and functionality to your API without any version :exclamation:

## What about backward compatibility?

Let's create a additional test with
the [goold old BookDto definition](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java)
the [good old BookDto definition](../rest-book/build/generated/src/main/java/info/touret/bookstore/spring/book/generated/dto/BookDto.java)
.

Copy paste this class in your [test source directory](../rest-book/src/test/java/) and remove the new attribute and
operation created earlier.
You can rename it ``OldBookDto`` for example.
You can rename it ``OldBookDto`` for example and put in the package ``info.touret.bookstore.spring.book.dto``.

In the [BookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/BookControllerIT.java)
, add a new test method:
Copy paste then your [BookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/BookControllerIT.java) integration test to [OldBookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java).

In the [OldBookControllerIT](../rest-book/src/test/java/info/touret/bookstore/spring/book/controller/OldBookControllerIT.java)
, replace the ``BookDto`` class usage with the new one.

You also have to modify the test ``should_get_a_random_book()``.
You can remove this line:

```java
@Test
void should_get_a_random_book_with_old_contract() {
var bookDto = testRestTemplate.getForEntity(booksUrl + "/random", OldBookDto.class).getBody();
assertNotNull(bookDto.getId());
}
assertNotNull(bookDto.getExcerpt());
```

See what happens.
See what happens and **explain it** :exclamation:
Loading