Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/factories/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Brighter Factories

This Factory folder was created to work with agents by providing descriptions of how we create new Messaging Gateways (called Transports) and Boxes (Inboxes/Outboxes) in Brighter, allowing for the creation of skills to add new ones, based on gateway or Db documentation.
13 changes: 13 additions & 0 deletions docs/factories/transports/channelfactory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Channel Factory

A channel factory is used to create instances of Brighter's `Channel` and `ChannelAsync` classes which are [Channel Adapter](https://www.enterpriseintegrationpatterns.com/patterns/messaging/ChannelAdapter.html)s that abstract away details of how we consume from a transport (a queue or stream).

The channel factory creates the `IAmAMessageConsumer' for the transport and passes it into the channel to allow it to read messages from the consumer.

## Implementation

You MUST create an implementation of `IAmAChannelFactory`. Your implementation of [`IAmAChannelFactory`](../../src/Paramore.Brighter/IAmAChannelFactory.cs) MUST use `Channel` or `ChannelAsync` as appropriate.

The factory MUST create an instance of `Channel` in response to a call to `CreateSyncChannel` or an instance of `ChannelAsync` in response to a call to `CreateAsyncChannel` or `CreateAsyncChannelAsync`. You MUST pass the `Channel` or `ChannelAsync` an instance of the implementation of `IAmAMessageConsumer` for the matching middleware, see [Consumers](./consumers.md) for more.

See for example [ChannelFactory](../../../src/Paramore.Brighter.MessagingGateway.RMQ.Sync/ChannelFactory.cs) which implements the `ChannelFactory` for Rabbit MQ (RMQ) creates and passes an [RmqMessageConsumer](../../../src/Paramore.Brighter.MessagingGateway.RMQ.Sync/RmqMessageConsumer.cs) as a parameter the `Channel` having created one from the [RmqMessageConsumerFactory](../../../src/Paramore.Brighter.MessagingGateway.RMQ.Sync/RmqMessageConsumerFactory.cs).
26 changes: 26 additions & 0 deletions docs/factories/transports/channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Channels

A `channel` is a virtual pipe that connects the message pump to the broker. We use the interface as an abstraction to allow the message pump to talk to multiple implementations of the channel for different brokers.

## Channels are Brighter Implemented

You MUST NOT to implement `IAmAChannel`, `IAmAChannelSync` or `IAmAChannelAsync` as these are provided by `Paramore.Brighter` through it's `Channel` class.

You MUST implement `IAmAChannelFactory` and configure an instance of `Channel`. See [Channel Factory](./channelfactory.md) for how to implement a channel factory.

## Key Interfaces

The key interfaces are:

- `IAmAChannel` the base interface for talking to a channel. `IAmAChannelSync` and `IAmAChannelAsync` derive from it.
- `Name` an internal identifier for the channel, may be used to name a queue if required.
- `RoutingKey` lets us identify the channel
- `Enqueue` adds a message from the middleware into the message pump's buffer.
- `Stop` stops reading from the channel, by posting a `WM-QUIT`message to the message pump
- along with the ability to enqueue messages read from the middleware or stop the channel when we are done reading from it.
- `IAmAChannelSync` and `IAmAChannelAsync` provide the main lifecycle events for the `MessagePump`.
- `Acknowledge` or `AcknowledgeAsync` indicates that a handler is done with a message and it can be ack'd (which may result in the middleware deleting the message from a queue or updating the offset into a stream).
- `Purge` or `PurgeAsync` which drains a queue, discarding messages, or advances a stream to the latest message. Used for load-shedding or removing the results of testing.
- `Receive` or `ReceiveAsync` which consumes a message from the middleware and returns it to the caller.
- `Reject` or `RejectAsync` which rejects a message because it cannot be processed. It send to a dead-letter queue (DLQ) if one is available.
- `Requeue` and `RequeueAsync` are used with transient errors to place them back in the queue or stream, after a delay.
Empty file.
73 changes: 73 additions & 0 deletions docs/factories/transports/producers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Producers

A producer sends a message via middleware. It may be a [point-to-point](https://www.enterpriseintegrationpatterns.com/patterns/messaging/PointToPointChannel.html) channel, in which case the producer writes directly to the channel, or it may be via a [broker](https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageBroker.html) in which case the message is sent to the broker, which routes it to the correct channel.

## Implementation

You MUST implement the `IAmAMessageProducer` interface and its derived sync interface `IAmAMessageProducerSync` and its derived async interface `IAmAMessageProducerAsync`. You SHOULD name the publisher `[XXXXX]MessageProducer` where `[XXXX]` is the name of the middleware or an abbreviation for it. For example we name the RabbitMQ publisher `RMQMessageProducer` and we name the Kafka publisher `KafkaMessageProducer`.

- `IAmAMessageProducer` has the following properties and methods:
- `Publication` a property to store the publication (see [Publication](./publication.md)).
- `Span` a property that enables us to set the `Activity` on an `IAmAMessageProducer`to participate in Open Telemetry.
- `Scheduler` the scheduler that we will use for delayed publication.
- `IAmAMessageProducerSync` and `IAmAMessageProducerAsync` derive from `IAmAMessageProducer` and are used to send messages to middleware. The separated interfaces allows clients to depend on either the sync or async operations. We use the `Async` suffix for async interfaces or methods.
- `Send` and `SendAsync` are used to send a message to middleware.
- `SendWithDelay` and `SendWithDelayAsync` are used to send a message to middleware with a delay. Either the middleware natively supports a delayed message send, or the consumer should use the scheduler to delay sending.

### Publisher

You SHOULD use a publisher to create the message to be sent over middleware. You SHOULD name the publisher `[XXXXX]MessagePublisher` where `[XXXX]` is the name of the middleware or an abbreviation for it. For example we name the RabbitMQ publisher `RMQMessagePublisher` and the Kafka publisher `KafkaMessagePublisher`.

The publisher has a `PublishMessage` or `PublishMessageAsync` method, which is implemented as follows:

```pseudo

create the middleware message
populate the middleware's message headers from the Brighter `Message`'s `Header` property, which is of type `MessageHeader`
populate the middleware's message headers from the Brighter `Message`'s `Header` property's `Bag` property, which is of type `Dictionary<string, object>` and contains user-defined values
populate the middleware message's body from the `Brighter` `Message`'s `Body' property. //Depending on the format of the middleware's message you may need to access this as `Bytes()` or just as the `Value` property for a string.
publish the message to the middleware

```

You SHOULD break these steps up into separate methods to reduce the cyclomatic complexity.

### Producer

The producer's implementation of the `Send` or `SendAsync` methods uses the publisher. Typically we implement `SendWithDelay` or `SendWithDelayAsync` and then call those from `Send` and `SendAsync` with a `TimeSpan.Zero` to indicate no delay, for example:

```csharp
public void Send(Message message)
{
SendWithDelay(message, TimeSpan.Zero);
}
```

We implement `SendWithDelayAsync` as follows:

```pseudo
ensure that we have a connection to the broker
create an instance of the publisher - passing any connection information needed to send the message
if the delay is `TimeSpan.Zero` or the broker natively supports a delayed publish, call the publisher's `PublishMessage` or `PublishMessageAsync`
else
use the `Scheduler` set on the producer to schedule the message with the delay.
endif

```

#### Marking Messages as Dispatched in the Outbox

<<<<<<<< HEAD:docs/factories/transports/producers.md
When the message has been sent, e need to mark it as dispatched in the Outbox.

Some middleware will asynchronously confirm delivery of the message via a callback. For example, RabbitMQ has [Publisher Confirms](https://www.rabbitmq.com/docs/confirms) and Kafka. Other middleware, for example SQS, returns a value indicating whether we successfully published.

- When the middleware returns a value the `OutboxProducerMediator` handles marking the message as dispatched in the `Outbox` and you MUST NOT handle this in the producer.
- When the middleware uses a callback, you MUST tag the `Producer` with the interface `ISupportPublishConfirmation` to indicate that.
========
When the message has been sent, e need to mark it as dispatched in the Outbox. Some middleware will asynchronously confirm delivery of the message via a callback. For example, RabbitMQ has [Publisher Confirms](https://www.rabbitmq.com/docs/confirms) and Kafka. Other middleware, for example SQS, returns a value indicating whether we successfully published.

If a producer uses callbacks then the producer should implement the interface `ISupportPublishConfirmation`. This interface contains an `event` which is fired when a callback occurs. Your producer will fire this event when the callback from the middleware indicates that your event has been published successfully. This allows `OutboxProducerMediator` to mark the message as dispatched in the `Outbox`. There is already code in `OutboxProducerMediator` to handle this and you MUST not write code there.

* If the transport does not the latter case, the `OutboxProducerMediator` handles marking the message as dispatched in the `Outbox` and you MUST NOT handle this in the producer. In the former case, you should hook up the callback to
>>>>>>>> master:docs/transports/producers.md
2 changes: 2 additions & 0 deletions docs/factories/transports/publication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Publication

2 changes: 2 additions & 0 deletions docs/factories/transports/subscription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Subscription

33 changes: 33 additions & 0 deletions docs/factories/transports/transports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Writing a Transport

A transport is an assembly that implements the required interfaces to expose specific messaging middleware to Brighter. For example, the assembly [Paramore.Brighter.MessagingGateway.Kafka](../../../src/Paramore.Brighter.MessagingGateway.Kafka/) exposes Kafka to Brighter.

When using messaging, Brighter itself acts as a [Messaging Gateway](https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessagingGateway.html). Within the Gateway, a transport is a [Channel Adapter](https://www.enterpriseintegrationpatterns.com/patterns/messaging/ChannelAdapter.html) as it abstracts a specific channel type (i.e. broker) to Brighter.

## Naming of Assemblies

We name the assembly `Paramore.Brighter.MessagingGateway.X` where `X` is the name of the messaging middleware or broker we are adapting. For example, `Paramore.Brighter.MessagingGateway.Kafka` is the name of the assembly that acts as an adapter for `Kafka`.

We name the test assembly for `Paramore.Brighter.MessagingGateway.X` as `Paramore.Brighter.X.Tests`. You MUST put all tests for the messaging gateway in that assembly. We add a Docker Compose file to enable us to create the middleware locally. We name the Docker Compose file for the middleware. so `docker-compose-X.yaml` where X is the name of the middleware, for example These tests use I/O to the middleware over test doubles. You SHOULD NOT write tests using test doubles here, unless it provides some specificity or clarity.

## Modularity

- You MUST only write code in the assemblies that you create for these transports.
- The assemblies you SHOULD write code in will be `Paramore.Brighter.MessagingGateway.X` and `Paramore.Brighter.X.Tests` where X is the name of the middleware that you are writing the messaging gateway for.
- You MUST NEVER write code in `Paramore.Brighter` or `Paramore.Brighter.ServiceActivator`. If you need changes in those assemblies, write a specification of what you need and request help by raising a GitHub issue.

## Overview of a Transport With Links

The following section provides an overview of writing a transport, with links to more detailed specifications.

### Required Interfaces

The following interfaces are required to implement a transport.

- `IAmAMessageProducer` allows us to send messages via the middleware. There are derived interfaces for sync and async producers. See [producers](./producers.md) for more information on how to implement.
- You SHOULD derive a type from [`Publication`](../../../src/Paramore.Brighter/Publication.cs) to store any configuration details needed to integrate with your implementation of `IAmAMessageProducer`. See [Publication](publication.md) for more details on how to implement.
- `IAmAMessageConsumer` allows us to read messages from the middleware (via a queue or stream). There are derived interfaces for sync and async producers. See [consumers](./consmers.md) for more information on how to implement.
- You SHOULD derive a type from [`Subscription`](../../../src/Paramore.Brighter/Subscription.cs) to store any configuration details needed to integrate with your implementation of `IAmAMessageConsumer`
- `IAmAChannelFactory` is a factory that allows Brighter to create instances of a `IAmAChannel` for a specific middleware. The channel factory injects a middleware-specific instance of `IAmAMessageConsumer` into the channel.
- You MUST implement `IAmAChannelFactory` see [channel factory](./channelfactory.md) for more information on how to implement.
- A channel factory returns a `Channel`, which implements`IAmAChannel`. The `IAmAChannel` interface allows our message pump to consume messages from a channel. There are derived interfaces for sync and async producers. See [channels](./channels.md) for more information on channels, but you MUST NOT implement `IAmAChannel` as Brighter implements this interface for you in `Channel`.
Loading