Skip to content

Commit

Permalink
docs: document order of execution of middlewares (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberhck authored Oct 8, 2024
1 parent bda3e6c commit 89dd52f
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/docs/middlewares/backoff.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
---
# Backoff
The backoff middleware slows down the processing of messages when errors occur. It uses a backoff policy to determine how long to wait between processing attempts.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/middlewares/consumer.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 3
---
# Consumer
The consumer middleware is responsible for populating the chain with messages from the Kafka topic(s). Consumer middleware retrieves a message from the Kafka topic using the Consumer interface and passes it to the next middleware in the chain.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/middlewares/custom-middleware.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 10
sidebar_position: 11
---

# Write your own
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/middlewares/deadletter.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 8
sidebar_position: 9
---
The deadletter middleware is used to send messages that have failed processing to a deadletter topic after a certain number of retries. This can be useful for identifying and debugging messages that are consistently causing processing errors.

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/middlewares/graceful-shutdown.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 9
sidebar_position: 10
---
# graceful shutdown
The `gracefulshutdown` middleware is designed to handle the `syscall.SIGINT`, `syscall.SIGTERM`, `syscall.SIGHUP`, and `syscall.SIGQUIT` signals gracefully, by allowing you to stop your processor in a controlled manner.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/middlewares/metrics.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 4
sidebar_position: 5
---
# Metrics
The metrics middleware is a middleware for the KP library that tracks various metrics about the message processing, such as the duration of each operation and the number of successes and failures. These metrics are collected using the Prometheus library and can be pushed to a Prometheus gateway to be stored and visualized.
Expand Down
104 changes: 104 additions & 0 deletions docs/docs/middlewares/order-of-middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
sidebar_position: 2
---

### Order of Middleware Execution

The middleware system implemented in the code follows a **stack-based execution model**. Middleware functions are executed in the order they are added, which means that the first middleware added is the first one to execute. However, due to the nature of how the middleware system works, the control flow unwinds in reverse order after processing, allowing each middleware to perform actions both **before** and **after** the next middleware in the stack.

#### Step-by-step Flow of Execution

Let's consider a sample code to show 3 middleware added in order: A, B, C which might look something like below.

```go
package main

import "context"

type Middleware[T any] struct {
Name string
}

func (m Middleware[T]) Process(ctx context.Context, item T, next func(ctx context.Context, item T) error) error {
println("Middleware " + m.Name + " -> before logic")
err := next(ctx, item)
println("Middleware " + m.Name + " -> before logic")

return err
}
func main() {
kp := v2.New[string]()
middlewareA := Middleware[string]{Name: "A"}
middlewareB := Middleware[string]{Name: "B"}
middlewareC := Middleware[string]{Name: "C"}
kp.AddMiddleware(middlewareA)
kp.AddMiddleware(middlewareB)
kp.AddMiddleware(middlewareC)
}
```

1. **Adding Middleware**:
- Middleware is added to the processor using the `AddMiddleware` method. The order in which you add middleware determines the execution order.
- For example, if you add middleware in this order: `MiddlewareA`, `MiddlewareB`, and `MiddlewareC`, they will be executed in the same sequence.

2. **Processing the Middleware Stack**:
- When the `Process` method is called, the middleware functions are executed in the order they were added. The first middleware in the list is executed first.
- During the execution of each middleware, the control is passed to the next middleware using the `next` function, which recursively processes each middleware until the end of the list is reached.

3. **Control Flow**:
- Each middleware gets the opportunity to perform operations **before** and **after** the next middleware in the chain. This means that while the middleware chain is called in the order of addition, the response unwinds in reverse order.
- For example, if `MiddlewareA`, `MiddlewareB`, and `MiddlewareC` were added to the processor, the order of execution would look like this:
```
1. MiddlewareA -> before logic
2. MiddlewareB -> before logic
3. MiddlewareC -> before logic
4. MiddlewareC -> after logic
5. MiddlewareB -> after logic
6. MiddlewareA -> after logic
```
- This pattern allows each middleware to wrap around the behavior of the next one in the chain, making it possible to perform actions both before and after the core logic of the middleware stack.
#### Execution Example
Let's say we have three middleware components: `MiddlewareA`, `MiddlewareB`, and `MiddlewareC`. The following sequence of events will occur:
1. **Before Execution Phase**:
- `MiddlewareA` is called first. It performs its "before" logic.
- `MiddlewareA` calls the next middleware in the stack, which is `MiddlewareB`.
- `MiddlewareB` performs its "before" logic and then calls `MiddlewareC`.
- `MiddlewareC` performs its "before" logic. Since it's the last middleware, it reaches the core logic or the final result.
2. **After Execution Phase**:
- After `MiddlewareC` completes its process, it returns control back to `MiddlewareB`.
- `MiddlewareB` now performs its "after" logic and returns control to `MiddlewareA`.
- Finally, `MiddlewareA` performs its "after" logic.
This pattern allows for a clean and structured way to handle pre-processing and post-processing in a middleware chain.
#### Key Characteristics
- **Forward Execution**: Middleware is executed in the order it is added.
- **Reverse Unwinding**: Once a middleware finishes its operation, control is returned to the previous middleware, allowing it to complete any after-execution logic.
- **Flexible Processing**: Middleware can modify the request, the response, or even handle errors in a centralized way.
This approach allows middleware authors to easily implement logic that needs to occur both **before** and **after** the main processing logic, creating a powerful mechanism for handling cross-cutting concerns like logging, authentication, error handling, and more.
### Usage
There are many middleware that make use of this pattern in KP.
- Consumer middleware retrieves an item from kafka and adds it in. (before logic)
- Deadletter middleware evaluates the result and determines if the message should go to deadletter instead (after logic)
- Backoff middleware delays execution and decreases or increases the interval for the next process (both before and after logic)
- Gracefulshutdown middleware doesn't have before or after logic, it simply stops the execution flow
- Measurement middleware starts a timer in before logic and measures the time taken in after logic.
- Retry middleware is same as deadletter (after logic)
- RetryCount middleware injects the retry count header onto context (before logic)
- Tracing middleware starts a span before and ends the span after (both before and after logic)
- You might have a custom middleware that might make use of any of the above pattern.
#### Summary
- **Order of execution** is **in the order middleware is added**.
- **Order of unwinding** is **in reverse order**.
- This enables middleware to do processing **before** and **after** the next middleware in the chain.
Understanding this order of execution is crucial for correctly implementing middleware logic, as it allows for both sequential processing and reverse unwinding for post-processing.
2 changes: 1 addition & 1 deletion docs/docs/middlewares/retry.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 7
sidebar_position: 8
---

The retry middleware is a utility middleware that automatically retries processing a message in case of failure. When a message processing fails, the middleware produces the message to a retry topic. The middleware uses the `Producer` interface to produce the message, which means that the user can choose any Kafka client that implements this interface to produce the message.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/middlewares/retrycounter.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 6
sidebar_position: 7
---

# Retry Count
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/middlewares/tracing.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 6
---

# Tracing
Expand Down

0 comments on commit 89dd52f

Please sign in to comment.