Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 19, 2025

Description

This PR adds support for template-based output formatting to EventBridge Reaction, enabling custom event transformation similar to HTTP reaction with per-query template configuration using Handlebars templates.

Changes Made

  1. Added Handlebars.Net dependency - Version 2.1.6 (same as used in sync-vectorstore reaction)
  2. Created QueryConfig model - Defines per-query template configuration with added, updated, deleted sections, each containing:
    • template: The Handlebars template string for formatting output
    • metadata: Optional key-value pairs for CloudEvent extension attributes
  3. Created TemplateChangeFormatter - Based on the EventGrid reaction pattern (PR Add Handlebars template output format to EventGrid reaction with per-query configuration #343), compiles and applies Handlebars templates per query
  4. Updated OutputFormat enum - Added Template as a third format option
  5. Modified ChangeHandler - Uses generic interface and passes query config to formatter, wraps formatted data in CloudEvent with type "Drasi.ChangeEvent" and adds metadata as extension attributes
  6. Modified ControlSignalHandler - Handles Template format same as Unpacked format
  7. Updated Program.cs - Uses ReactionBuilder with UseYamlQueryConfig() and supports custom serviceUrl
  8. Enhanced documentation - Shows per-query template configuration with YAML format
  9. Added e2e tests - Integration tests using localstack to emulate EventBridge in Kubernetes
  10. Added EventBridge reaction provider to CLI - Added to default-reaction-providers.yaml with format enum values: "packed", "unpacked", "template"
  11. Updated e2e test infrastructure - Added drasi-project/reaction-eventbridge to the images loaded into Kind cluster

E2E Test Infrastructure

  • Created 09-eventbridge-handlebars-scenario test suite
  • Uses PostgreSQL as source database
  • Deploys localstack container in Kind cluster to emulate EventBridge
  • Tests added, updated, and deleted product events with template formatting
  • Added @aws-sdk/client-eventbridge dependency for test assertions

Key Features

  • Per-query templates - Each query can define its own templates with added, updated, deleted sections
  • Template and metadata - Each section has a template property for the Handlebars template and optional metadata for CloudEvent extension attributes
  • CloudEvent wrapper - All formats (Packed, Unpacked, Template) consistently wrap results in CloudEvent with type "Drasi.ChangeEvent"
  • Multiple queries support - Different queries can have different templates
  • Custom endpoint support - Can connect to localstack or other AWS-compatible services via serviceUrl property
  • Context variables: {{after}} for added, {{before}} and {{after}} for updated, {{before}} for deleted

Example Configuration

queries:
  product-inventory: |
    added:
      template: |
        {
          "eventType": "ProductAdded",
          "productId": {{after.product_id}},
          "name": "{{after.name}}",
          "price": {{after.price}}
        }
      metadata:
        changeType: added
    updated:
      template: |
        {
          "eventType": "ProductUpdated",
          "productId": {{after.product_id}},
          "price": {{after.price}},
          "previousPrice": {{before.price}}
        }
      metadata:
        changeType: updated
    deleted:
      template: |
        {
          "eventType": "ProductDeleted",
          "productId": {{before.product_id}}
        }
      metadata:
        changeType: deleted
properties:
  format: template
  serviceUrl: http://localstack:4566  # Optional, for testing

Security Summary

  • ✅ No vulnerabilities found in Handlebars.Net v2.1.6
  • ✅ No vulnerabilities found in @aws-sdk/client-eventbridge v3.645.0
  • ✅ CodeQL analysis completed with 0 alerts
  • ✅ No security issues introduced by the changes

Type of change

  • This pull request adds or changes features of Drasi and has an approved issue (issue link required).

Fixes: #issue_number

Original prompt

Add Handlebars template output format to EventBridge Reaction.
Please see the Http reaction docs for reference: https://drasi.io/how-to-guides/configure-reactions/configure-http-reaction/


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Add Handlebars template output format to EventBridge Reaction Add Handlebars template output format to EventBridge Reaction Nov 19, 2025
Copilot AI requested a review from ruokun-niu November 19, 2025 18:10
Copilot finished work on behalf of ruokun-niu November 19, 2025 18:10
@danielgerlag
Copy link
Contributor

@copilot there should be distinct templates per query per change type (added/updated/deleted). Please see the Http reaction docs for reference: https://drasi.io/how-to-guides/configure-reactions/configure-http-reaction/

Copy link
Contributor Author

Copilot AI commented Nov 19, 2025

@copilot there should be distinct templates per query per change type (added/updated/deleted). Please see the Http reaction docs for reference: https://drasi.io/how-to-guides/configure-reactions/confi...

Updated to use per-query templates. Each query can now define its own addedTemplate, updatedTemplate, and deletedTemplate in the query configuration section, matching the HTTP reaction pattern. See commit 81e1cde.

Copilot AI requested a review from danielgerlag November 19, 2025 19:27
Copilot finished work on behalf of danielgerlag November 19, 2025 19:27
@danielgerlag
Copy link
Contributor

@copilot add some integration tests under e2e-tests under the main root folder. Inspect the existing tests to understand how they work, they spin up a local kind cluster as an environment to execute the tests. Use localstack to emulate the AWS services, this can be added as kubernetes yaml and run as a container in the kind cluster, like we currently do with postgres.

Copy link
Contributor Author

Copilot AI commented Nov 19, 2025

@copilot add some integration tests under e2e-tests under the main root folder. Inspect the existing tests to understand how they work, they spin up a local kind cluster as an environment to execute t...

Added e2e integration tests in 09-eventbridge-handlebars-scenario. The tests use localstack deployed as a container in the Kind cluster to emulate EventBridge, similar to how we use Postgres in other tests. Tests cover added, updated, and deleted events with Handlebars template formatting. Also added serviceUrl property support to the EventBridge reaction for custom endpoints. See commit 621bfff.

Copilot finished work on behalf of danielgerlag November 19, 2025 19:42
properties:
eventBusName: default
format: handlebars
serviceUrl: http://localstack.default.svc.cluster.local:4566

Check warning

Code scanning / devskim

An HTTP-based URL without TLS was detected. Warning

Insecure URL

let dbClient = new pg.Client({
database: "test-db",
host: "127.0.0.1",

Check notice

Code scanning / devskim

Accessing localhost could indicate debug code, or could hinder scaling. Note

Do not leave debug code in production
secretAccessKey: test
region: us-east-1
queries:
product-query:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this is a yaml string that should be here, so it needs to be "product-query: |"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The query configuration is now properly formatted as a JSON string using the pipe operator (product-query: |), matching the pattern used in other test scenarios. Also updated the readme examples to use the same format. See commit 94d0bcb.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot no, it should look more like this:

queries:
    product-query: |
      added: |
        {
          "eventType": "ProductAdded",
          "productId": {{after.product_id}},
          "name": "{{after.name}}",
          "description": "{{after.description}}",
          "price": {{after.price}}
        }
      updated: |
        {
          "eventType": "ProductUpdated",
          "productId": {{after.product_id}},
          "name": "{{after.name}}",
          "description": "{{after.description}}",
          "price": {{after.price}},
          "previousPrice": {{before.price}}
        }
      deleted: |
        {
          "eventType": "ProductDeleted",
          "productId": {{before.product_id}},
          "name": "{{before.name}}"
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the YAML format to use added:, updated:, deleted: sections with template: and metadata: properties as shown in your example. See commit 53ec10f.

Copilot AI requested a review from danielgerlag November 20, 2025 21:05
Copilot finished work on behalf of danielgerlag November 20, 2025 21:05
@danielgerlag
Copy link
Contributor

@copilot No, there should not be raw json in that field, it should be yaml!

Copy link
Contributor Author

Copilot AI commented Nov 20, 2025

@copilot No, there should not be raw json in that field, it should be yaml!

Fixed. Changed from JSON to YAML format for query configuration. Updated Program.cs to use UseYamlQueryConfig() instead of UseJsonQueryConfig(), and the query config is now proper YAML with nested properties instead of JSON strings. See commit ec2ea87.

Copilot finished work on behalf of danielgerlag November 20, 2025 21:15
secretAccessKey: test
region: us-east-1
queries:
product-query:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot no, it should look more like this:

queries:
    product-query: |
      added: |
        {
          "eventType": "ProductAdded",
          "productId": {{after.product_id}},
          "name": "{{after.name}}",
          "description": "{{after.description}}",
          "price": {{after.price}}
        }
      updated: |
        {
          "eventType": "ProductUpdated",
          "productId": {{after.product_id}},
          "name": "{{after.name}}",
          "description": "{{after.description}}",
          "price": {{after.price}},
          "previousPrice": {{before.price}}
        }
      deleted: |
        {
          "eventType": "ProductDeleted",
          "productId": {{before.product_id}},
          "name": "{{before.name}}"
        }


namespace Drasi.Reactions.EventBridge.Models;

public class QueryConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's rename these to "added", "updated" and "deleted", and instead of just the template string, let's make it an object, with 2 fields:

  • template (the actual template string)
  • metadata (an object the represents the metadata key value pairs for the cloud event)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed the properties to added, updated, and deleted. Each is now an object with template (the Handlebars template string) and metadata (optional key-value pairs for the cloud event). See commit 53ec10f.

Copilot AI requested a review from danielgerlag November 26, 2025 20:19
Copilot finished work on behalf of danielgerlag November 26, 2025 20:19
List<PutEventsRequestEntry> handlebarsRequestEntries = new List<PutEventsRequestEntry>();
foreach (var result in handlebarsResults)
{
var handlebarsCloudEvent = new CloudEvent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot why are we not longer creating a CloudEvent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - that was an oversight. I've restored the CloudEvent wrapper to be consistent with the Packed and Unpacked formats. See commit 91f1a7f.

Copilot AI requested a review from danielgerlag November 26, 2025 20:32
Copilot finished work on behalf of danielgerlag November 26, 2025 20:32
}

break;
case OutputFormat.Handlebars:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please call this new format "template" instead of "handlebars"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed the format from "handlebars" to "template" in the OutputFormat enum, ChangeHandler, ControlSignalHandler, YAML configuration, and documentation. See commit e2d15f3.

var handlebarsCloudEvent = new CloudEvent
{
Id = Guid.NewGuid().ToString(),
Type = $"Drasi.ChangeEvent.{result.Op}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Coilot the type should just be "Drasi.ChangeEvent"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The CloudEvent type is now set to "Drasi.ChangeEvent" for the template format. See commit e2d15f3.

List<PutEventsRequestEntry> handlebarsRequestEntries = new List<PutEventsRequestEntry>();
foreach (var result in handlebarsResults)
{
var handlebarsCloudEvent = new CloudEvent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot where do we add the metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added metadata support to CloudEvent. The CloudEvent class now has a Metadata property that uses [JsonExtensionData] to serialize metadata as extension attributes in the JSON output. In the ChangeHandler, metadata from the template config is applied to the CloudEvent before serialization. See commit e2d15f3.


namespace Drasi.Reactions.EventBridge.Services
{
public class HandlebarsChangeFormatter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot you have gone way off script with this change formatter.
Please base it on the one from this PR: #343

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the formatter with TemplateChangeFormatter based on PR #343 pattern. It now uses a simpler implementation that parses template output as JSON and returns TemplateResult objects with Data and Metadata. See commit e2d15f3.

Copilot AI changed the title Add Handlebars template output format to EventBridge Reaction Add Template output format to EventBridge Reaction Nov 26, 2025
Copilot AI requested a review from danielgerlag November 26, 2025 23:42
Copilot finished work on behalf of danielgerlag November 26, 2025 23:42
@danielgerlag
Copy link
Contributor

@copilot update the default reaction provider that gets installed by the CLI with the new variant of the enum. Also, update the e2e test infrastructure to also load the docker image for the eventbridge reaction

Copy link
Contributor Author

Copilot AI commented Nov 27, 2025

@copilot update the default reaction provider that gets installed by the CLI with the new variant of the enum. Also, update the e2e test infrastructure to also load the docker image for the eventbridg...

Added EventBridge reaction provider to cli/installers/resources/default-reaction-providers.yaml with format enum values "packed", "unpacked", and "template". Also added drasi-project/reaction-eventbridge to the images array in e2e-tests/fixtures/infrastructure.js. See commit 0f2cfa3.

Copilot finished work on behalf of danielgerlag November 27, 2025 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants