Skip to content

Commit

Permalink
chore(docs): updating Pokeshop documentation (#1892)
Browse files Browse the repository at this point in the history
* Updating Pokeshop documentation

* Changing code specs in Pokeshop docs to json

* standardize sidebar

* Updating Add Pokemon tutorial

* Adding text for list pokemon use case

* Adding documentation to import pokemon

* Adding case of get pokemon by id

* Updating scenarios

* Updating images

* Apply suggestions from code review

Co-authored-by: Julianne Fermi <julianne@kubeshop.io>

---------

Co-authored-by: Julianne Fermi <julianne@kubeshop.io>
  • Loading branch information
danielbdias and jfermi committed Jan 30, 2023
1 parent 6a9ba3e commit 7b9df9e
Show file tree
Hide file tree
Showing 34 changed files with 571 additions and 351 deletions.
Binary file removed docs/docs/img/516849692/518193163.png
Binary file not shown.
File renamed without changes.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions docs/docs/live-examples/pokeshop/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Pokeshop API

As a testing ground, the team at Tracetest has implemented a sample instrumented API around the [PokeAPI](https://pokeapi.co/).

The idea is to have a microservice-divided system that behaves like a typical scenario by having async processes ([RabbitMQ](https://www.rabbitmq.com/)), cache layers ([Redis](https://redis.io/)), database storage ([Postgres](https://www.postgresql.org/)), and simple CRUD interfaces for Pokemons.

With this, users can get familiar with the Tracetest tool by focusing on creating assertions, visualizing the trace, and identifying the different data that comes from the Collector ([Jaeger](https://www.jaegertracing.io/)). Users will learn about basic instrumentation practices like what tools to use, what data to send, when, and what suggested standards need to be followed.

- **Source Code**: https://github.com/kubeshop/pokeshop
- **Running it locally**: [instructions](https://github.com/kubeshop/pokeshop/blob/master/docs/installing.md#run-it-locally)
- **Running on Kubernetes**: [instructions](https://github.com/kubeshop/pokeshop/blob/master/docs/installing.md#run-on-a-kubernetes-cluster)

## Use Cases

We have three use cases that use each component of this structure and that can be observed via Open Telemetry and tested with Tracetest. Each one is triggered by an API call to their respective endpoint:

- [Add Pokemon](./use-cases/add-pokemon.md): Add a new Pokemon only relying on user input into the database.
- [List Pokemon](./use-cases/list-pokemon.md): Lists all Pokemons registered into Pokeshop.
- [Import Pokemon](./use-cases/import-pokemon.md): Given a Pokemon ID, this endpoint does an async process, going to PokeAPI to get Pokemon data and adding it to the database.

## System Architecture

The system is divided into two components:
- an **API** that serves client requests,
- a **Worker** who deals with background processes.

The communication between the API and Worker is made using a `RabbitMQ` queue, and both services emit telemetry data to Jaeger and communicate with a Postgres database.

A diagram of the system structure can be seen here:

```mermaid
flowchart TD
A[(Redis)]
B[(Postgres)]
C(Node.js API)
D(RabbitMQ)
E(Worker)
F(Jaeger)
C --> |IORedis| A
C --> |Sequelize| B
C --> |ampqlib| D
D --> |ampqlib| E
E --> |Sequelize| B
C --> |OpenTelemetry Node.js SDK| F
E --> |OpenTelemetry Node.js SDK| F
```

In our live tests, we are deploying into a single Kubernetes namespace, deployed via a [Helm chart](https://github.com/kubeshop/pokeshop/blob/master/docs/installing.md#run-on-a-kubernetes-cluster).

The Pokeshop API is only accessible from within the Kubernetes cluster network as Tracetest needs to be able to reach it.
97 changes: 97 additions & 0 deletions docs/docs/live-examples/pokeshop/use-cases/add-pokemon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Pokeshop API - Add Pokemon

This use case showcases a simple example of an API call where call data is validated and persisted into a database.

```mermaid
sequenceDiagram
participant Endpoint as POST /pokemon
participant API as API
participant Database as Postgres
Endpoint->>API: request
alt request is invalid
API-->>Endpoint: 400 Bad Request <br> <List of errors>
end
API->>Database: save pokemon
Database-->>API: saved pokemon
API-->>Endpoint: 201 Created <br> <Pokemon object>
```

You can trigger this use case by calling the endpoint `POST /pokemon`, with the following request body:
```json
{
"name": "meowth",
"type": "normal",
"imageUrl": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/052.png",
"isFeatured": true
}
```

It should return the following payload:
```json
{
"id": 1000,
"name": "meowth",
"type": "normal",
"imageUrl": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/052.png",
"isFeatured": true
}
```

## Building a Test for This Scenario

Using Tracetest, we can [create a test](../../../web-ui/creating-tests.md) that will execute an API call on `POST /pokemon` and validate two properties:
- The API should return a proper result with **HTTP 201 Created**.
- The database should return with **low latency (< 200ms)**.

### Traces

Running these tests for the first time will create an Observability trace like the image above, where you can see spans for the API call, validation (an API internal operation), and database calls:
![](../images/add-pokemon-trace.png)

### Assertions

With this trace, now we can build [assertions](../../../concepts/assertions.md) on Tracetest and validate the API response and the database latency:

- **The API should return a proper result with HTTP 201 Created:**
![](../images/add-pokemon-api-test-spec.png)

- **The database should return with low latency (< 200ms):**
![](../images/add-pokemon-database-test-spec.png)

Now you can validate this entire use case.

### Test Definition

If you want to replicate this entire test on Tracetest see by yourself, you can replicate these steps on our Web UI or using our CLI, saving the following test definition as the file `test-definition.yml` and later running:

```sh
tracetest test -d test-definition.yml --wait-for-results
```

```yaml
type: Test
spec:
name: Pokeshop - Add
description: Add a Pokemon
trigger:
type: http
httpRequest:
url: http://demo-pokemon-api.demo/pokemon
method: POST
headers:
- key: Content-Type
value: application/json
body: '{"name":"meowth","type":"normal","imageUrl":"https://assets.pokemon.com/assets/cms2/img/pokedex/full/052.png","isFeatured":true}'
specs:
- selector: span[tracetest.span.type="http" name="POST /pokemon" http.method="POST"]
assertions:
- attr:http.response.body = '{"id":6152,"imageUrl":"https://assets.pokemon.com/assets/cms2/img/pokedex/full/052.png","isFeatured":true,"type":"normal","name":"meowth"}'
- attr:http.status_code = 201
- selector: span[tracetest.span.type="database"]
assertions:
- attr:tracetest.span.duration <= 200ms
```
153 changes: 153 additions & 0 deletions docs/docs/live-examples/pokeshop/use-cases/get-pokemon-by-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Pokeshop API - Get Pokemon by ID

This use case retrieves a specific Pokemon from the cache if it is cached, or from the database (Postgres) also populating the cache. The idea of this query is to showcase a straightforward scenario, where the API layer receives a request from the outside and needs to evaluate a different behavior depending of its dependencies.

```mermaid
sequenceDiagram
participant Endpoint as GET /pokemon/:id
participant API as API
participant Database as Postgres
participant Cache as Redis
Endpoint->>API: request
API->>Cache: query cache
Cache-->>API: cache response
alt cache hit
API-->>Endpoint: 200 OK <br> <Pokemon object>
else cache miss
API->>Database: get specific of pokemon
Database-->>API: pokemon
API->>Cache: populate cache
Cache-->>API: ok
API-->>Endpoint: 200 OK <br> <Pokemon object>
end
```

You can trigger this use case by calling the endpoint `GET /pokemon/25` without payload and should receive a payload similar to this:
```json
{
"id": 25,
"name": "pikachu",
"type": "electric",
"imageUrl": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/025.png",
"isFeatured": true
}
```

## Building a Test for the Described Scenarios

Using Tracetest, we can [create two tests](../../../web-ui/creating-tests.md) that will execute an API call on `GET /pokemon/25` and validate the following scenarios:
1. **An API call with a cache hit.**
- The API should return a valid result with HTTP 200 OK.
- The cache should be queried.
- The database should not be queried.
2. **An API call with a cache miss.**
- The API should return a valid result with HTTP 200 OK.
- The cache should be queried.
- The cache should be populated.
- The database should be queried.

### Traces

Running these tests for the first time will create an Observability trace with two different shapes, depending on the cache situation.

1. **Cache Miss** where we can see spans from the API, database, and cache:
![](../images/get-pokemon-by-id-trace-cachemiss.png)

2. **Cache Hit** where we can see spans from the API and cache:
![](../images/get-pokemon-by-id-trace-cachehit.png)

### Assertions

With this trace, we can build [assertions](../../../concepts/assertions.md) on Tracetest and validate API, cache, and database responses:

- [Both Cases] The API should return a valid result with HTTP 200 OK.
![](../images/get-pokemon-by-id-api-test-spec.png)

- [Both Cases] The cache should be queried.
![](../images/get-pokemon-by-id-redis-query-test-spec.png)

- [Cache Hit] The database should not be queried.
![](../images/get-pokemon-by-id-db-no-query-test-spec.png)

- [Cache Miss] The cache should be populated.
![](../images/get-pokemon-by-id-redis-set-test-spec.png)

- [Cache Miss] The database should be queried.
![](../images/get-pokemon-by-id-db-query-test-spec.png)

Now you can validate this entire use case.

### Test Definition

If you want to replicate those tests on Tracetest, you can replicate these steps on our Web UI or using our CLI, saving one of the test definitions as the file `test-definition.yml` and running:

```sh
tracetest test -d test-definition.yml --wait-for-results
```

#### Cache Miss Scenario

```yaml
type: Test
spec:
name: Pokeshop - Get Pokemon by ID [cache miss scenario]
trigger:
type: http
httpRequest:
url: http://demo-pokemon-api.demo/pokemon/${env:POKEMON_ID}
method: GET
headers:
- key: Content-Type
value: application/json
specs:
- selector: span[tracetest.span.type="http" http.method="GET"]
assertions:
- attr:http.status_code = 200
- attr:http.response.body | json_path '$.id' = '${env:POKEMON_ID}'
- selector: span[tracetest.span.type="database" db.system="redis" db.operation="get"]
assertions:
- attr:name = "get pokemon-${env:POKEMON_ID}"
- selector: span[tracetest.span.type="database" db.system="redis" db.operation="set"]
assertions:
- attr:name = "set pokemon-${env:POKEMON_ID}"
- selector: |-
span[tracetest.span.type="database" name="findOne pokeshop.pokemon"
db.system="postgres" db.name="pokeshop" db.operation="findOne" db.sql.table="pokemon"]
assertions:
- attr:tracetest.selected_spans.count > 0
```

#### Cache Miss Scenario

```yml
type: Test
spec:
name: Pokeshop - Get Pokemon by ID [cache hit scenario]
trigger:
type: http
httpRequest:
url: http://demo-pokemon-api.demo/pokemon/${env:POKEMON_ID}
method: GET
headers:
- key: Content-Type
value: application/json
specs:
- selector: span[tracetest.span.type="http" http.method="GET"]
assertions:
- attr:http.status_code = 200
- attr:http.response.body | json_path '$.id' = "${env:POKEMON_ID}"
- selector: span[tracetest.span.type="database" db.system="redis" db.operation="get"]
assertions:
- attr:name = "get pokemon-${env:POKEMON_ID}"
- attr:db.result | json_path '$.id' = "${env:POKEMON_ID}"
- selector: |-
span[tracetest.span.type="database" name="findOne pokeshop.pokemon"
db.system="postgres" db.name="pokeshop" db.operation="findOne" db.sql.table="pokemon"]
assertions:
- attr:tracetest.selected_spans.count = 0
```

0 comments on commit 7b9df9e

Please sign in to comment.