cloudevents / spec

CloudEvents Specification
https://cloudevents.io
Apache License 2.0
4.9k stars 577 forks source link

AsyncAPI v3 Trait for CloudEvents #1276

Open Lazzaretti opened 3 months ago

Lazzaretti commented 3 months ago

I am creating Kafka APIs in AsyncAPI. These APIs should have CloudEvents headers. Now, I was wondering if it would make sense to have an official CloudEvents Trait in AsyncAPI to include these CloudEvents headers. My goal would be to refer to a trait to include the correct CloudEvents headers.

In the structured mode, this is already possible ^1:

components:
  schemas:
    userSignedUpPayload:
      type: object
      allOf:
        - $ref: 'https://raw.githubusercontent.com/cloudevents/spec/v1.0.1/spec.json'
      properties:
        data:
          $ref: '#/components/schemas/userSignedUpData'

For binary, there is no official version. To include this, we would need to add some AsyncAPI-specific files.

Would it make sense to include such an AscynAPI trait?

Here are two samples that I found:

duglin commented 2 months ago

ping @clemensv

github-actions[bot] commented 1 month ago

This issue is stale because it has been open for 30 days with no activity. Mark as fresh by updating e.g., adding the comment /remove-lifecycle stale.

Fannon commented 3 weeks ago

Dear @Lazzaretti ,

what version of AsyncAPI are you using? v2 or v3?

I'm asking, because the AsyncAPI 2 trait behavior will lead to the trait overwriting the target message. So be very careful with v2 here: Don't put anything in the trait that needs to be overwritten for a particular message. In v3, the trait behavior has been changed / fixed, so it should work better.

In general, we (@deissnerk and me) have made the experience that AsyncAPI does not work all to well for describing cloud events. Besides of the issue with the cloud events trait inheritance, there's also the problem that depending on binary or structured content mode, the cloud events header would be merged in different places. And your actual implementation may offer both options, while in the AsyncAPI you need to decide which one you'll describe.

Also, you may have additional opinion on how to further restrict or define the use of some properties like type and source. In that case, you would apply and document this in your cloud event context attributes trait.

Here is an example, how we defined our CloudEvent context header at SAP:

{
  "components": {
    "messageTraits": {
      "CloudEventsContext.v1": {
        "headers": {
          "type": "object",
          "properties": {
            "id": {
              "description": "Identifies the event.",
              "type": "string",
              "examples": [
                "6925d08e-bc19-4ad7-902e-bd29721cc69b"
              ]
            },
            "specversion": {
              "description": "The version of the CloudEvents specification which the event uses.",
              "type": "string",
              "const": "1.0"
            },
            "source": {
              "description": "Identifies the instance the event originated in.",
              "type": "string",
              "format": "uri-reference",
              "examples": [
                "/default/sap.s4.beh/ER9CLNT001",
                "/eu/sap.billing.sb/91dec60d-9757-4e2c-b9e5-21da10016fe9"
              ]
            },
            "type": {
              "description": "Describes the type of the event related to the source the event originated in.",
              "type": "string",
              "examples": [
                "sap.dsc.FreightOrder.Arrived.v1",
                "sap.billing.sb.Subscription.Canceled.v1"
              ]
            },
            "subject": {
              "description": "Describes the subject of the event in the context of the source the event originated in (e.g., the id of the business object the event is about).",
              "type": "string",
              "examples": [
                "ce307052-75a0-4a8f-a961-ebf21669bb80",
                "urn:epc:tag:sgtin-96:1.7332402.026591.1234567890"
              ]
            },
            "datacontenttype": {
              "description": "Content type of the event data.",
              "type": "string",
              "const": "application/json"
            },
            "dataschema": {
              "description": "Identifies the schema that the event data adheres to.",
              "type": "string",
              "format": "uri",
              "examples": [
                "http://example.com/event/sap.billing.sb.Subscription.Canceled/v1.2.0"
              ]
            },
            "time": {
              "description": "Timestamp of when the occurrence happened.",
              "format": "date-time",
              "type": "string",
              "examples": [
                "2018-04-05T17:31:00Z"
              ]
            }
          },
          "required": [
            "id",
            "source",
            "specversion",
            "type"
          ],
          "patternProperties": {
            "^xsap[a-z0-9]+$": {
              "description": "Application defined custom extension context attributes.",
              "type": [
                "boolean",
                "integer",
                "string"
              ]
            }
          }
        }
      }
    }
  }
}
Lazzaretti commented 2 weeks ago

Hi @Fannon,

Thanks a lot for your reply! I would like an AsyncAPI v3 trait (I updated the issue to clarify this). So, the inheritance/overwrite problem should not matter here.

With an AsyncAPI trait, I know that you need to define the exact fields and their placement (in binary or structured mode). This specification is one of the goals I want to achieve. With this, the contract is more specific, and the producer and consumers know exactly what to expect or send.

Thanks a lot for this example!

So, I would propose adding one trait per binding and mode (binary or structured). Of course, inside a company, you can harden some rules and add more accurate examples.

Would this help the project?

Fannon commented 2 weeks ago

Hi @Lazzaretti ,

Ok, so you would define two traits (one for binary, one for structured mode) and then would you duplicate the messages to indicate that they can come either way? This is where i'm unsure how to do that best with AsyncAPI. Maybe that would also be a better question toward the AsyncAPI community than here.

So would you consider writing out the traits and contributing them here? I think it could make sense to have a "baseline" CloudEvent trait as example here. At the least, it could be a good starting point from where you can customize or make it more specific for your own situation.

Lazzaretti commented 2 weeks ago

I would select one trait per message as a binary CloudEvent and a structured CloudEvent is not the same.

However, I would, in most cases, choose one type (binary or structured) per channel. So, for example, channel A over Kafka just accepts the structured kind of CloudEvents with the Kafka binding. So I know exactly how an event will look inside which makes it simpler.

I would like to start with a PR, but currently, I don't have much time. If I find some time, I will start with it :)

Fannon commented 2 weeks ago

Sounds good! Thanks!