Add EventTransform documentation (#6234)

Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
This commit is contained in:
Pierangelo Di Pilato 2025-03-04 10:08:58 +01:00 committed by GitHub
parent 6de5caa459
commit 1309acd77d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 681 additions and 0 deletions

View File

@ -265,6 +265,9 @@ nav:
- Using Sequences in series: eventing/flows/sequence/sequence-reply-to-sequence/README.md
- Create additional events: eventing/flows/sequence/sequence-terminal/README.md
- Using with Broker and Trigger: eventing/flows/sequence/sequence-with-broker-trigger/README.md
- Event Transformations:
- About Event Transformations: eventing/transforms/README.md
- Event Transformations for JSON with JSONata: eventing/transforms/event-transform-jsonata.md
# about triggers docs
# - Administrator configuration options: eventing/triggers/trigger-admin-config-options.md
# - Developer configuration options: eventing/triggers/trigger-developer-config-options.md

View File

@ -0,0 +1,268 @@
# Event Transformation
## Overview
`EventTransform` is a Knative API resource that enables declarative transformations of HTTP requests and responses
without requiring custom code. It allows you to modify event attributes, extract data from event payloads, and reshape
events to fit different systems' requirements.
EventTransform is designed to be a flexible component in your event-driven architecture that can be placed at various
points in your event flow to facilitate seamless integration between diverse systems.
## Key Features
- **Declarative transformations** using standard Kubernetes resources
- **JSONata expressions** for powerful data extraction and transformation
- **Addressable resource** that can be referenced from any Knative source, trigger, or subscription
- **Flexible deployment options** within your event flow
- **Sink configuration** to direct transformed events to specific destinations
- **Reply support** to leverage Broker's built-in reply feature
## Common Use Cases
### Field Extraction
Extract specific fields from event payloads and promote them as CloudEvent attributes for filtering:
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: extract-user-id
spec:
jsonata:
expression: |
{
"specversion": "1.0",
"id": id,
"type": "user.extracted",
"source": "transform.user-extractor",
"time": time,
"userid": data.user.id,
"data": $
}
```
### Event Format Conversion
Transform events from one format to another to ensure compatibility between systems:
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: format-converter
spec:
sink:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: destination-service
jsonata:
expression: |
{
"specversion": "1.0",
"id": id,
"type": "order.converted",
"source": "transform.format-converter",
"time": time,
"data": {
"orderId": data.id,
"customer": {
"name": data.user.fullName,
"email": data.user.email
},
"items": data.items
}
}
```
### Event Enrichment
Add additional context or metadata to events:
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: event-enricher
spec:
jsonata:
expression: |
{
"specversion": "1.0",
"id": id, /* Add the "id", "type", "source", and "time" attributes based on the input JSON object fields */
"type": type,
"source": source,
"time": time,
"environment": "production", /* Add fixed environment and region attributes to the event metadata */
"region": "us-west-1",
"data": $ /* Add the event transform input JSON body as CloudEvent "data" field */
}
```
### Event Response Reply Transformation
When using the EventTransform with a sink, you can also transform the responses from the sink:
!!! important
The same type of transformation must be used for Sink and Reply transformations.
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: request-reply-transform
spec:
sink:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: processor-service
jsonata:
expression: |
# Request transformation
{
"specversion": "1.0",
"id": id,
"type": "request.transformed",
"source": source,
"time": time,
"data": data
}
reply:
jsonata:
expression: |
# Reply transformation
{
"specversion": "1.0",
"id": id,
"type": "reply.transformed",
"source": "transform.reply-processor",
"time": time,
"data": data
}
```
## Deployment Patterns
EventTransform can be used in different positions within your event flow:
### Source → EventTransform → Broker
Transform events before they reach the Broker:
```yaml
apiVersion: sources.knative.dev/v1
kind: ApiServerSource
metadata:
name: k8s-events
spec:
serviceAccountName: event-watcher
resources:
- apiVersion: v1
kind: Event
sink:
ref:
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
name: event-transformer
---
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: event-transformer
spec:
sink:
ref:
apiVersion: eventing.knative.dev/v1
kind: Broker
name: default
jsonata:
expression: |
# transformation expression
```
### Broker → Trigger → EventTransform → Service or Sink
Transform events after they're filtered by a Trigger:
```yaml
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: transform-trigger
spec:
broker: default
filter:
attributes:
type: original.event.type
subscriber:
ref:
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
name: event-transformer
---
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: event-transformer
spec:
sink:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: destination-service
jsonata:
expression: |
# transformation expression
```
### Using Broker Reply Feature
Transform events and republish them back to the Broker:
!!! important
Preventing infinite event loops: When using the reply feature with a Broker, you must ensure that your transformed
events don't trigger the same Trigger that sent them to the EventTransform in the first place.
```yaml
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: transform-trigger
spec:
broker: default
filter:
attributes:
type: original.event.type
subscriber:
ref:
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
name: event-transformer
---
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: event-transformer
spec:
# No sink specified - will use reply feature
jsonata:
expression: |
{
"specversion": "1.0",
"id": id,
"time": time,
"type": "transformed.event.type",
"source": "transform.event-transformer",
"data": $
}
```
## Next Steps
- [JSONata Transformations](./event-transform-jsonata.md) - Learn about using JSONata expressions for event
transformations
- [Trigger Filtering](./../triggers/README.md#trigger-filtering) - Learn how to use Trigger filtering
- [EventTransform API Reference](./../reference/eventing-api.md) - Detailed API reference for EventTransform

View File

@ -0,0 +1,410 @@
# Event Transformations for JSON with JSONata
## Introduction to JSONata
[JSONata](https://jsonata.org/) is a lightweight query and transformation language for JSON data. In Knative EventTransform, JSONata expressions allow you to:
- Extract values from event data
- Promote data fields to CloudEvent attributes
- Restructure event payloads
- Add computed values
- Apply conditional logic
## Basic Usage
To use JSONata in an EventTransform resource, specify the expression in the `spec.jsonata.expression` field:
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: simple-transform
spec:
jsonata:
expression: |
{
"specversion": "1.0",
"id": id,
"time": time,
"type": "transformed.type",
"source": "transform.simple",
"data": data
}
```
## CloudEvent Structure
The input to the JSONata expression is the entire CloudEvent, including all its attributes and data. Your expression must produce a valid CloudEvent with at least these required fields:
- `specversion`: Should be set to "1.0"
- `id`: A unique identifier for the event
- `type`: The event type
- `source`: The event source
- `data`: The event payload
## Common Transformation Patterns
### Preserving Original Event Structure
To preserve the original event structure while adding or modifying attributes:
```jsonata
{
"specversion": "1.0",
"id": id,
"type": type,
"source": source,
"time": time,
"data": data,
"newattribute": "static value"
}
```
### Extracting Fields as Attributes
To extract fields from the data and promote them to CloudEvent attributes:
```jsonata
{
"specversion": "1.0",
"id": id,
"type": "user.event",
"source": source,
"time": time,
"userid": data.user.id,
"region": data.region,
"data": $
}
```
The `$` symbol in JSONata represents the entire input object, so `data: $` preserves the entire original event data.
### Restructuring Event Data
To completely reshape the event data:
```jsonata
{
"specversion": "1.0",
"id": order.id,
"type": "order.transformed",
"source": "transform.order-processor",
"time": order.time,
"orderid": order.id,
"data": {
"customer": {
"id": order.user.id,
"name": order.user.name
},
"items": order.items.{ "sku": sku, "quantity": qty, "price": price },
"total": $sum(order.items.(price * qty))
}
}
```
Given the transformation above, and this JSON object as input:
```json
{
"order": {
"time" : "2024-04-05T17:31:05Z",
"id": "8a76992e-cbe2-4dbe-96c0-7a951077089d",
"user": {
"id": "bd9779ef-cba5-4ad0-b89b-e23913f0a7a7",
"name": "John Doe"
},
"items": [
{"sku": "KNATIVE-1", "price": 99.99, "qty": 1},
{"sku": "KNATIVE-2", "price": 129.99, "qty": 2}
]
}
}
```
It would produce:
```json
{
"specversion": "1.0",
"id": "8a76992e-cbe2-4dbe-96c0-7a951077089d",
"type": "order.transformed",
"source": "transform.order-processor",
"time": "2024-04-05T17:31:05Z",
"orderid": "8a76992e-cbe2-4dbe-96c0-7a951077089d",
"data": {
"customer": {
"id": "bd9779ef-cba5-4ad0-b89b-e23913f0a7a7",
"name": "John Doe"
},
"items": [
{
"sku": "KNATIVE-1",
"quantity": 1,
"price": 99.99
},
{
"sku": "KNATIVE-2",
"quantity": 2,
"price": 129.99
}
],
"total": 359.97
}
}
```
### Conditional Transformations
To apply different transformations based on conditions:
```jsonata
{
"specversion": "1.0",
"id": id,
"type": type = "order.created" ? "new.order" : "updated.order",
"source": source,
"time": time,
"priority": data.total > 1000 ? "high" : "normal",
"data": $
}
```
## Advanced JSONata Features
### Array Processing
JSONata makes it easy to process arrays in your event data:
```jsonata
{
"specversion": "1.0",
"id": id,
"type": "order.processed",
"source": source,
"time": $now(),
"itemcount": $count(order.items),
"multiorder": $count(order.items) > 1,
"data": {
"order": order.id,
"items": order.items[quantity > 1].{
"product": name,
"quantity": quantity,
"lineTotal": price * quantity
},
"totalvalue": $sum(order.items.(price * quantity))
}
}
```
Given the transformation above, and this JSON object as input:
```json
{
"id": "12345",
"source": "https://example.com/orders",
"order": {
"id": "order-67890",
"items": [
{
"name": "Laptop",
"price": 1000,
"quantity": 1
},
{
"name": "Mouse",
"price": 50,
"quantity": 2
},
{
"name": "Keyboard",
"price": 80,
"quantity": 3
}
]
}
}
```
It would produce:
```json
{
"specversion": "1.0",
"id": "12345",
"type": "order.processed",
"source": "https://example.com/orders",
"time": "2025-03-03T09:13:23.753Z",
"itemcount": 3,
"multiorder": true,
"data": {
"order": "order-67890",
"items": [
{
"product": "Mouse",
"quantity": 2,
"lineTotal": 100
},
{
"product": "Keyboard",
"quantity": 3,
"lineTotal": 240
}
],
"totalvalue": 1340
}
}
```
### Using Built-in Functions
JSONata provides many useful functions:
```jsonata
{
"specversion": "1.0",
"id": id,
"type": "user.event",
"source": source,
"time": time,
"timestamp": $now(),
"username": $lowercase(data.user.name),
"initials": $join($map($split(data.user.name, " "), function($v) { $substring($v, 0, 1) }), ""),
"data": $
}
```
## Transforming Replies
When using the EventTransform with a sink, you can also transform the responses:
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: request-reply-transform
spec:
sink:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: processor-service
jsonata:
expression: |
# Request transformation
{
"specversion": "1.0",
"id": id,
"type": "request.transformed",
"source": source,
"time": time,
"data": data
}
reply:
jsonata:
expression: |
# Reply transformation
{
"specversion": "1.0",
"id": id,
"type": "reply.transformed",
"source": "transform.reply-processor",
"time": time,
"data": data
}
```
## Best Practices
1. **Always produce valid CloudEvents**: Ensure your expressions include all required CloudEvent fields.
2. **Test expressions thoroughly**: Use the [JSONata Exerciser](https://try.jsonata.org/) to validate complex expressions.
3. **Keep expressions readable**: Use line breaks and indentation in your YAML to make expressions easier to read and maintain.
4. **Handle missing data**: Use the `?` operator to provide default values for potentially missing fields.
5. **Avoid infinite loops**: When using the reply feature with a Broker, make sure to change the event type or add filters to prevent infinite loops.
## Examples
### User Registration Event Transformer
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: user-registration-transformer
spec:
sink:
ref:
apiVersion: eventing.knative.dev/v1
kind: Broker
name: default
jsonata:
expression: |
{
"specversion": "1.0",
"id": id,
"type": "user.registered.processed",
"source": "transform.user-processor",
"time": time,
"userid": data.user.id,
"region": data.region ? data.region : "unknown",
"tier": data.subscription.tier ? data.subscription.tier : "free",
"data": {
"userId": data.user.id,
"email": $lowercase(data.user.email),
"displayName": data.user.name ? data.user.name : $substring(data.user.email, 0, $indexOf(data.user.email, "@")),
"registrationDate": $now(),
"subscription": data.subscription ? data.subscription : { "tier": "free" }
}
}
```
### Order Processing Event Transformer
```yaml
apiVersion: eventing.knative.dev/v1alpha1
kind: EventTransform
metadata:
name: order-processor
spec:
jsonata:
expression: |
{
"specversion": "1.0",
"id": id,
"type": "order.processed",
"source": "transform.order-processor",
"time": time,
"orderid": data.id,
"customerid": data.customer.id,
"region": data.region,
"priority": $sum(data.items.(price * quantity)) > 1000 ? "high" : "standard",
"data": {
"orderId": data.id,
"customer": data.customer,
"items": data.items.{
"productId": productId,
"name": name,
"quantity": quantity,
"unitPrice": price,
"totalPrice": price * quantity
},
"total": $sum(data.items.(price * quantity)),
"tax": $sum(data.items.(price * quantity)) * 0.1,
"grandTotal": $sum(data.items.(price * quantity)) * 1.1,
"created": data.created,
"processed": $now()
}
}
```
## Further Resources
- [EventTransform Overview and deployment patterns](./README.md)
- [JSONata Documentation](https://jsonata.org/documentation.html)
- [JSONata Exerciser](https://try.jsonata.org/)
- [CloudEvents Specification](https://github.com/cloudevents/spec)