The meaningfulness of Events via standardization ( Part 1 )

Omid EidivandiOmid Eidivandi
9 min read

Events became the core concept of distributed systems and these changes irreversibly evolve the nature of distributed system design.


how it is related to the changes? those changes bring assumptions that are applied in the design phase and depend on the responsibilities that every component in a whole system owns and their coupling level.

How distributed systems behave?

Distributed systems rely on communication where inter-component communication lets the consumers obtain data, reformulate, and aggregate the data to make a useful data model based on their proper needs.

The design at a high level is really simple but there are lots of details to think about and the following important characteristics of a design :

  • Fault tolerance/resiliency

  • Operational expectations

  • Performance / Scale

  • Consistency

These are basic principles that are often applied in ADRs.

Hard Parts?

The hardest part of the design in distributed systems is at the Communication boundary where any unexpected behavior in a dependency level can impact consumer behavior.

Are all these efforts needed? for sure Yes and no, that depends on business requirements and can be handled technically.

Some of the root causes of communication problems are Coupling Level, Consistency needs, and Synchronicity.


Asynchronous Communication:

The asynchronous design tackles the communication problem and reduces the impact at the communication level in most of the use cases.

' Our world is asynchronous ' Dr. Werner Vogels, VP & CTO of Amazon

Asynchronous communication simplifies inter-component communication in a distributed system but has its challenges at different levels

Consistency: The asynchronous design applies by its nature the eventual consistency where a piece of data on the consumer side has not definitively the last committed state, this means the consumer can use the data from another component which has not have the latest real information ***yet (***insist on YET ).

Events: events are the core concept of asynchronous design and thinking about them is a crucial part of your design, the events can be Domain Events, Integration Events, notification events, or Delta events ( To discover more here). Deciding when and where choosing which type of event must be used, is based on the context of consumption and the business process coupling. That depends on the communication boundary level, is it internal or external? do we need to transfer data or just notify a change? what are the consumer needs?

States:The state in asynchronous design is related to the data or entity in which part of the business model is constructed. in the async world, any entity can have multiple states that are related to internal processes or external ones. Synchronizing the state between components is also a real challenge and can produce unintended complexity in design, this is important that these communications be designed per state evaluation and not based on other component's process complexity.

Synchronization: Unlike Restful design, where actions are atomic, Event-driven design relies on the atomicity of event states. Achieving this atomicity requires careful calculation and synchronization of all states involved. This synchronization is essential on the event publisher side and can be even more complex on the consumer side. It necessitates coordinating different states and their order of arrival or logical sequence. Achieving such synchronization demands well-defined documentation and interactive communication between teams, including both publishers/producers and consumers.

Observability: Within Asynchronous communication, achieving observability proves to be particularly challenging. Designing a system with observability requires an initial investment in effort and standardization to streamline the governance of business processes. This entails a thorough examination of enterprise requirements and practices. By standardizing procedures upfront, it becomes feasible to apply them across all bounded contexts, ensuring a consistent mode of communication and facilitating observability.

Discover about Event-Driven Architecture


Simplified Events Governance :

As moving toward event-driven architecture and asynchronous design adds some level of complexity we always look for alternatives to simplify governance barriers by well-instrumenting events via

  • contracts

  • versioning

  • backward compatibilities

  • documenting

In the rest of this article, we focus on the events standardization following a practical example.


When setting up an event base communication consider thinking about ;

***What about adding a new item or removing one?***This will absolutely introduce a new consumption contract.

What about if a new field is added without changing the contract? This will absolutely have a breaking change effect shortly

What happens if the event will add a new state? What if it’s between two existing states? What if it overtakes the created state ( initiated instead of Created )?*This can be harmful as can introduce stale treatment or extra computing effort on the consumer side as they are not aware of the new state.*

What if we need to have both integration events and domain events?*This can be hard if we mix all metadata and data at the same level of payload, we end up having to separate contracts that are not reusable and we add a level of complexity in the maintenance phase and governance.*

Event Envelope:

In event-driven design a contract is a piece of information that presents the schema of something that happened in a system like an OrderCreated, but also some extra information that represents the event envelope.

An example of a notification event:

{
    "spacVersion": "1.0.2",
    "id": "jhgsqdgsqjqshgdqs",
    "source": "commerce:order",
    "type": "order.created",
    "time": "2023-06-01T12:54:00.000Z",
    "idempotencyKey": "b52QCFI6FuXkt5MHltOyX",
    "correlationId": "1744cee7-8041-4f47-b744-a5ae60e96865",
    "dataContentType": "application/json", 
    "dataSchema": "somepath",
    "dataVersion": "1.0.0",
    "data": {
      // Omited at this example per section scope
    }
}

The example presents the basic information about the creation of an order, as the system notifies the consumers of this event it needs a documented contract that presents well the extra details about any element in the payload like the datatype, format, constraints and etc…

Async API example of our event

---
asyncapi: 2.6.0
info:
  version: 1.0.0
  title: Event
servers:
  eventTopic:
    url: source-event-sns-topic
    protocol: HTTP
channels:
  notificationEvents:
    servers:
      - eventTopic
    subscribe:
      summary: Subscribe to receive notification events.
      message:
        $ref: '#/components/messages/NotificationMessage'
components:
  messages:
    NotificationMessage:
      name: notification event
      payload:
        $ref: '#/components/schemas/Event'
  schemas:
    Event:
      type: object
      required:
      - spacVersion
      - source
      - type
      - time
      - idempotencyKey
      - id
      properties:
        spacVersion:
          type: string
        source:
          type: string
        time:
          type: string
          format: date-time
        type:
          type: string
          enum:
          - order.created
          - order.udated
          - order.rejected
        idempotencyKey:
          type: string
        contentType:
          type: string
          enum:
          - application/json
          - application/*+avro
        data:
          type: object
          properties:
            orderId:
              type: string
            state:
              type: string
              enum:
              - OrderCreated
              - OrderCanceled
              - OrderRefused
              - OrderPaymentRejected
              - OrderConfirmed

This basic demonstration contract simplifies the consumer understanding of the event and helps them in the development phase, but as well helps to level up system governance.

  • All Fields have a type

  • The event has a Timestamp that represents the time of change that occurred in the system.

  • The Source helps to avoid confusion if duplicate states are presented in different bounded Contexts and helps to trace the coupling between systems ( loose or tight ).

  • The event has a version number

  • The event type lets us observe the business Changes that occurred.

  • datacontenttype helps the consumer parse the data

  • idempotencykey ensures that any retry will not affect the state of data on the consumer side.

  • dataversion and dataschema help the consumers to validate the incoming event against a spec


Event Metadata:

Event Metadata represents the basic information that helps the consumers integrate the event into their system or treat a business change on their side regardless of details.

The event example for an Order payment with Metadata.

{
    "spacVersion": "1.0.0",
    "source": "commerce:payment",
    "type": "order.created", 
    "time": "2023-06-01T12:54:00.000Z",
    "idempotencyKey": "0002b52QCFI6FuXkt5MHltOyX",
    "dataContentType": "json",
    "dataVersion": "1.0.0",
    "dataSchema": "someshcema path",
    "data": {
      "orderId": "2yo1HBA6iFKd8vuNqWwBq",
      "paymentId": "810fd639-2446-4920-8578-fa380faea175",
      "state": "paymentConfirmed"
    }
}

The event simplifies the integration of a change in the producer side in consumer systems.

  • The type lets the consumer detect the type of event and be aware of the existence of some domain-related data.

  • The data version helps to guarantee the promise due to a contract for the data section.

  • The OrderId helps correlate two Payment and Order contexts and lets the consumers act more intelligently and gives them more autonomy for simple integration.

  • The state represents the type of change that happened in the payment system.

  • The PaymentId acts as the identifier of the entity that relates to that change.

The data are presented side of base envelope information and these are basically two separate promises. Envelope is an standard at enterprise level and can be evolved separately. Data is part of bounded context and must be evolved separately and due to business requirement and changes.

Promises:

When designing any evolutionary system that needs to freely evolve and continue respecting promises we need to think about how to instrument the system to respond well to internal and external needs.

Promises are mostly talked about when we face external communication and that is a perfect topic but what about internal communication? How you can evolve your system when having different consumption needs?

A promise needs an understanding of:

  • Requirements

  • Boundaries

  • Effects

  • Life cycle

Requirements:

To decide the best type of event that fits the systems we need first to understand the consumer requirement so aligning around that requirement needs some simple workshops like Event Storming ( to discover more here )

Boundaries:

Defining the boundaries of the system can help, like where are consumers situated geographically, what are boundaries present, are they internal/external/public or geographically in the same location or not.

Effects:

To choose the best design approach understanding the effects behind a simple design vs an optimized one can help to calculate the risk and take the best decision and affect a promise at a given time. Understanding these effects needs to think about some pros and cons of any approach :

  • How the higher rate of events can be managed

  • How the future of the system can be provisioned

  • How different communication Channels behave

  • ………….

Life Cycle:

The life cycle of the system can vary, When designing a system for partners the lifecycle is different from a system designed for other bounded contexts in the same enterprise and as well for inside the same bounded context. The lifecycle can be defined based on multiple elements like :

  • How simple is communication with that actor?

    💡
    exchanging emails or phone calls with thousands of partners is definitively harder than communicating with internal teams.
  • How do business requirements differ?

  • How do performance requirements differ?

  • How do security requirements differ?

  • How do Compliance requirements differ?

  • How evolutions are applied?

  • ……….


Conclusion:

Events are the real success of software design for 2 decades but understanding their pitfalls helps us to better take a design approach.

The events are the mean to high scale business success but to achieve those goals we need to think better and deeper around them at enterprise level and brings them into play toward business at scale, Observability, Governance and etc …

0
Subscribe to my newsletter

Read articles from Omid Eidivandi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Omid Eidivandi
Omid Eidivandi