Skip to main content

Chaine's Architecture

At chaine, we are using a combination of Domain Driven Design, CQRS and Event Sourcing.

Our architecture is entirely event-based. This means that different services or subsystems react to events. For example, if a user is created, the user-subsystem may decide to emit an event called 'userCreated'. Then any other subsystem that needs to know when a user is created (i.e. the chat subsystem), it will "react" to 'userCreated', and listen in on when this event happens.

High-level design

Chaine consists of multiple autonomous services or "subsystems". Each subsystem has its own responsibility (i.e. user management, chat, notifications, shipment management, integrations, etc...).

high level architecture subsystems

Example of different subsystems

Each autonomous service is highly protected from the implementation details of other subsystems, and no autonomous service access data directly from another service. This allows for a highly-decoupled system. When data is needed from another subsystem, it is done through reacting to events.

Each subsystem will emit specific events, and other subsystems will listen and react to events as they see fit.

high level architecture subsystems

Events to and from a subsystem flow through AWS EventBridge

For more details on the flow of events, see the next section.

Packages and flow of events

Each subsystem will have a general package structure set up like this:

|__subsystemA
|__bff-1
|__bff-2
|__esg-gateway
|__esg-1
|__hub
|__events

A ESG and BFF is explained in detail in the autonomous services section.

  • A subsystem may have multiple BFFs and ESGs.
  • A subsystem will have only one esg-gateway, hub, and events.
  • esg-gateway: This package only has 2 responsibilities. 1. Take external events and convert them to an "internal" format for services inside of a subsystem to consume. 2. Take any internal events generated from any of the bffs or esgs, and convert them to an "external" format for other subsystems to use.
  • hub: This package has the EventBridge and Kinesis Stream implementation along with the EventBridge Rules which control the flow of events.
    • Deploys the event bus using AWS EventBridge: Responsible for taking in all events either from any internal service or from other subsystem's EventBridge event buses.
    • Deploys the subsystem's Kinesis Stream: The kinesis stream is responsible for high-throughput handling of all events (ones that come from external subsystems via the subsystem's event bus or events that are published from internal services to be used for internal services). See the flow of events section for more details.
    • Configure event bus Rules: Rules are used to tell the event bus where to send each event. This is done by filtering based on a few fields in the event.
  • events: This is a types package which will contain all the event types for any internal event that is generated and any internal event converted to an external format.
    • This package is used by internal packages when a service in the subsystem is consuming events from another service in the subsystem.
    • This package is also used by external subsystems, usually in the external subsystem's esg-gateway, when they are consuming an event from this subsystem and converting them to an internal format for their own subsystem.

Flow of events

Let's look at an example repo, "relay" that has a basic structure as:

Relay packages

high level architecture subsystems

Subsystem Relay and its packages and services

Domain events generated inside Relay for use in Relay

Services inside of relay can emit "internal" domain events for use in other services in relay. These are labelled as "internal" since right now, we have not published them to external subsystems. Each BFF or ESG can publish events to the event bus. There is a rule configured in the event bus to then publish all domain events that are "internal" to the subsystem's kinesis stream. Once all "internal" domain events are sent to kinesis, now any service inside of this subsystem can listen on the kinesis stream and consume the events they want.

flow of internal domain events

Internal events published by Relay's services, and used by other services in Relay.

The flow is:

  1. Internal events from esgs and bffs go to the eventbus which then sends them to relay's kinesis stream via a Rule.
  2. These events are now emitted by kinesis and any service in relay can setup an ingress-listener. This listener is just a lambda that is configured to invoke from events from this kinesis stream. In the lambda function, we use the pipes and filters design pattern and can filter out only the events we want in a service.

Domain events generated inside Relay for use in other Subsystems

Services inside of relay can emit "internal" domain events that can be used by other subsystems.

flow of internal domain events to be used by external domains

Subsystem Relay and its packages and services

The flow is:

  1. Internal events from esgs and bffs go to the eventbus which then sends them to relay's kinesis stream via a Rule.
  2. Relay's esg-gateway then consumes these "internal" domain events and converts them to an "external" format. We do this so the external format has strong backward's compatibility since other subsystems will be using them.
  3. esg-gateway publishes the "internal" domain events which are now converted to an "external" format to Relay's event bus.
  4. A Rule inside of Relay's event bus (configured in the hub package) then publishes external domain events to other subsystems that want it.

Domain events generated in other subsystem for use in Relay

Events that other subsystems emit can also be sent from an external subsystem's event bus to Relay's eventbus via a Rule (configured in the hub package).

flow of external domain events for internal use

Subsystem Relay and its packages and services

The flow is:

  1. External events other subsystems go to relay's eventbus (via a Rule setup in that external subsystem's hub package). A Rule configured in Relay's hub then immediately publishes these events to Relay's Kinesis Stream.
  2. Relay's esg-gateway can listen in on these events inside of its ingress-listener. esg-gateway's ingress-listener converts these "external" events to an "internal" format.
  3. esg-gateway publishes new events which are now an "internal" format back to Relay's event bus. A Rule inside of Relay's event bus (configured in the hub package) immediately sends these events to Relay's kinesis stream.
  4. Services in relay can now comsume the “external” events which have now been converted to an “internal” format in the esg-gateway.

Summary of the flow of events

Here is the overall flow of all events from the previous steps.

  • Blue lines:
    • Solid lines: Events that are in some "internal" format that are published to Relay's event bus.
    • Dotted lines: Events that are in some "internal" format that are used by services inside of Relay.
  • Purple lines:
    • Solid lines: Events that are "external" which come from external subsystem's event bus are sent to Relay's event bus via a Rule configured in the external event bus (in the hub package of the external subsystem).
    • Dotted lines: Events that are in an "external" format from external subsystems. Only the esg-gateway listens on these events to convert them to an "internal" format for Relay's services to use.
  • Gray lines:
    • Dotted lines: These are Relay's "internal" domain events which have been converted to an "external" format, and is sent to Relay's event bus.
    • Solid lines: These are Relay's "internal" domain events which have been converted to an "external" format which are sent to other subsystems via Rules set up in Relay's `hub' package.
all events in a domain

Subsystem Relay and its packages and services