Skip to content

Commit

Permalink
wip - update pokeshop docs
Browse files Browse the repository at this point in the history
  • Loading branch information
danielbdias committed Sep 7, 2023
1 parent 3c5196c commit cc73901
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 15 deletions.
33 changes: 20 additions & 13 deletions docs/docs/live-examples/pokeshop/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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.
The idea is to have a microservice-divided system that behaves like a typical scenario by having async processes ([RabbitMQ](https://www.rabbitmq.com/) and [Kafka](https://kafka.apache.org/)), 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: what tools to use, what data to send, when, and what suggested standards need to be followed.

Expand All @@ -17,16 +17,18 @@ We have three use cases that use each component of this structure and that can b
- [Add Pokemon](./use-cases/add-pokemon.md): Add a new Pokemon only relying on user input into the database.
- [Get Pokemon by ID](./use-cases/get-pokemon-by-id.md): Given a Pokemon ID, this endpoint returns the data of a Pokemon. If the same Pokemon was queried, the API will use its cache to return it.
- [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.
- [Import Pokemon from Queue](./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.
- [Import Pokemon from Stream](./use-cases/import-pokemon-from-stream.md): Listening to a Stream, this usecase also 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.
- a **Queue Worker** who deals with background processes, receiving data from the API
- a **Stream Worker** who handles import events sent from a stream

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.
The communication between the API and Queue Worker is made using a `RabbitMQ` queue, and both services emit telemetry data to Jaeger and communicate with a Postgres database. Additionaly, a Stream Worker listens to a `Kafka` stream to see if there is any import event sent on it to execute.

A diagram of the system structure can be seen here:

Expand All @@ -36,16 +38,21 @@ flowchart TD
B[(Postgres)]
C(Node.js API)
D(RabbitMQ)
E(Worker)
E(Queue 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
G(Kafka)
H(Stream Worker)
C --> A
C --> B
C --> D
D --> E
E --> B
C --> F
E --> F
G --> H
H --> B
H --> 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).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Pokeshop API - Import Pokemon from Stream

This use case showcases a more complex scenario involving an async process. Usually, when working with microservices, there are use cases where some of the processing needs to happen asynchronously, for example, when triggering a user notification, generating reports or processing a payment order. With this endpoint, we provide an example of how users can implement trace-based testing for such scenarios.

Here the process listens to a stream, and whenever an event is read from it it triggers the following process:
```mermaid
sequenceDiagram
participant Stream as Kafka
participant Worker as Stream Worker
participant ExternalAPI as PokeAPI
participant Database as Postgres
Stream->>Worker: read "import" message
Worker->>ExternalAPI: get pokemon info
ExternalAPI-->>Worker: pokemon info
Worker->>Database: save pokemon
Database-->>Worker: pokemon saved
```

You can trigger this use case by sending a message to Kafka on the `pokemon` topic with the following message value:
```json
{
"id": 143
}
```

## Building a Test for This Scenario

Using Tracetest, we can [create a test](../../../web-ui/creating-tests.md) that will produce a message to Kafka on `pokemon` topic and validate the following properties:
- The worker should read the import task.
- PokeAPI should return a valid response.
- The database should respond with low latency (< 200ms).

### Traces

Running these tests for the first time will create an Observability trace like the image below, where you can see spans for the stream messaging, the PokeAPI (external API) call and database calls.

<!-- ![](../images/import-pokemon-trace.png) -->

### Assertions

With this trace, we can build [assertions](../../../concepts/assertions.md) on Tracetest and validate the API and Worker behaviors:

- **A message was received from Kafka stream:**
<!-- ![](../images/import-pokemon-message-dequeue-test-spec.png) -->

- **Import Pokemon use case was triggered**:

- **PokeAPI should return a valid response:**
<!-- ![](../images/import-pokemon-pokeapi-call-test-spec.png) -->

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

Now you can validate this entire use case.

### Test Definition

If you want to replicate this entire test on Tracetest, 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 run test -f test-definition.yml
```

```yaml
type: Test
spec:
id: a97syfdkjad
name: Import a Pokemon reading a Stream
description: Import a Pokemon via Stream
trigger:
type: kafka
kafka:
brokerUrls:
- stream:9092
topic: pokemon
headers: []
messageKey: snorlax-key
messageValue: "{\"id\":143}"
specs:
- selector: span[tracetest.span.type="messaging" name="pokemon process" messaging.system="kafka" messaging.destination="pokemon" messaging.destination_kind="topic" messaging.operation="process"]
name: A message was received from Kafka stream
assertions:
- attr:messaging.system = "kafka"
- selector: span[tracetest.span.type="general" name="import pokemon"]
name: Import Pokemon use case was triggered
assertions:
- attr:name = "import pokemon"
- selector: span[tracetest.span.type="http" name="HTTP GET pokeapi.pokemon" http.method="GET"]
name: PokeAPI should return a valid response
assertions:
- attr:http.response.body = '{"name":"snorlax"}'
- attr:http.status_code = 200
- selector: span[tracetest.span.type="database"]
name: The database should respond with low latency (< 200ms)
assertions:
- attr:tracetest.span.duration <= 200ms

```
4 changes: 2 additions & 2 deletions docs/docs/live-examples/pokeshop/use-cases/import-pokemon.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Pokeshop API - Import Pokemon
# Pokeshop API - Import Pokemon from Queue

This use case showcases a more complex scenario involving an async process. Usually, when working with microservices, there are use cases where some of the processing needs to happen asynchronously, for example, when triggering a user notification, generating reports or processing a payment order. With this endpoint, we provide an example of how users can implement trace-based testing for such scenarios.

Expand Down Expand Up @@ -26,7 +26,7 @@ sequenceDiagram
```mermaid
sequenceDiagram
participant Queue as RabbitMQ
participant Worker as Worker
participant Worker as Queue Worker
participant ExternalAPI as PokeAPI
participant Database as Postgres
Expand Down

0 comments on commit cc73901

Please sign in to comment.