Skip to main content

CloudEvents Standard

Hermodr uses the CloudEvents specification as its event model. Every event published through the framework is represented as a CloudEvent object from the CloudNative.CloudEvents SDK.

Why CloudEvents?

  • Interoperability — CloudEvents is a CNCF specification adopted by Azure, Google Cloud, AWS, and many other platforms. Events can be consumed by any system that understands the standard.
  • Schema-agnostic — The envelope is fixed; the data payload can be any content type.
  • Tooling ecosystem — Schema registries, event bridges, and observability tools often natively support CloudEvents.

The CloudEvent Envelope

A CloudEvent consists of a set of required and optional attributes:

AttributeTypeDescription
idstringUnique identifier for this specific event occurrence
sourceUriIdentifies the context in which the event happened (e.g. your service URL)
specversionstringAlways "1.0"
typestringDescribes the kind of event (e.g. "order.placed")
datacontenttypestring?MIME type of the data payload (e.g. "application/json")
dataschemaUri?URI of the schema that the data conforms to
subjectstring?Additional context about the subject of the event
timeDateTimeOffset?Timestamp when the event occurred
dataobject?The event payload

Event time semantics

In Hermodr, CloudEvent.time is the occurrence timestamp of the business fact, not the transport delivery time.

  • Use time to answer when the event happened in domain terms.
  • Use transport/outbox/dead-letter metadata (NextRetryAt, NextReplayAt, delivery headers) to answer when delivery was attempted.
  • Keep these concepts separate to preserve an accurate event record and make replay/audit timelines reliable.

When the event has no time, EventPublisher fills it through IEventSystemTime.UtcNow during enrichment. This allows deterministic tests by replacing the clock via UseSystemTime<TClock>().

How Hermodr Populates the Envelope

When you call publisher.PublishAsync(data) with an annotated data object, the IEventFactory service reads the [Event] attribute and populates the envelope as follows:

CloudEvent attributeSource
type[Event] attribute's EventType property
idIEventIdGenerator (default: new GUID)
sourceEventPublisherOptions.Source
timeIEventSystemTime.UtcNow
dataschema[Event] attribute's DataSchema, or EventPublisherOptions.DataSchemaBaseUri + event type
datacontenttype[Event] attribute's ContentType
dataThe annotated object itself

Any additional CloudEvent attributes declared via [EventAttributes] (or AMQP-specific attributes) are merged in.

Required-attribute enforcement

After enrichment the publisher checks that the four required attributes (id, source, type, specversion) are non-null and non-empty. If any are missing, an InvalidCloudEventException is thrown before the event reaches any channel. This means:

  • type must always be provided — it is never auto-filled.
  • source must be set on the event itself or via EventPublisherOptions.Source.
  • id is auto-generated by IEventIdGenerator, so it effectively never fails the check.
  • specversion is always "1.0" in the CloudNative SDK.

Full payload validation (checking the data field against its declared schema) is deferred to a later milestone. See Schema Validation for the roadmap item.

Working with CloudEvent Directly

You can bypass the annotation system and construct a CloudEvent manually when you need full control:

using CloudNative.CloudEvents;

var systemTime = serviceProvider.GetRequiredService<IEventSystemTime>();

var @event = new CloudEvent
{
Id = Guid.NewGuid().ToString(),
Type = "com.acme.order.shipped",
Source = new Uri("https://orders.acme.com"),
Time = systemTime.UtcNow,
DataContentType = "application/json",
Data = new { OrderId = 42, TrackingNumber = "1Z999AA1" }
};

await publisher.PublishEventAsync(@event);

Further Reading