Basic Concepts
Last updated
Was this helpful?
Last updated
Was this helpful?
A command is an instruction to carry out work. It exercises the domain and results in a change of state. It expects a single handler.
An may be used to indicate the outcome of a command.
In Brighter, a command processor allows you to use the Command Pattern to separate caller from the executor, typically when separating I/O from domain code. It acts both as a Command Dispatcher which allows the separation of the parameters of a from the that executes that request and a Command Processor that allows you to use a middleware to provide additional and re-usable behaviors when processing that request.
The Command Processor may dispatch to an or an .
Command-Query separation is the principle that because a should never have the unexpected side-effect of updating state, a query should clearly be distinguished from a . A query reports on the state of a domain, a command changes it.
An event is a fact. The domain may be updated to reflect the fact represented by the event. There may be no subscribers to an event. It may be skinny, a notification, where the fact is the event itself, or fat, a document, where the event provides facts describing a change.
An event may be used to indicate the outcome of a .
Examples: Kafka, Kinesis, Redis Streams
Brighter abstracts a specific type of message-oriented middleware by a Transport.
For simplicity, Brighter only supports transports that have a broker configuration, not point-to-point. If you need point-to-point semantics, configure your routing table entry so that it only delivers to one consuming queue or stream.
Each individual transport has code to turn a Brighter format message into a message oriented middleware compatible message, and vice versa, so your code only needs to translate to and from the Brighter format.
Examples: SQS, AMQP 0-9-1 (Rabbit MQ), AMQP 1-0 (Azure Service Bus).
Brighter and Darker's pipelines use a "Russian Doll Model" that is, each handler in the pipeline encompasses the call to the next handler, allowing the handler chain to behave like a call stack.
It is analogous to a method on an ASP.NET Controller.
It is analogous to a method on an ASP.NET Controller.
Request-Reply is a pattern in which there is a request for work and a response.
A common approach is to change state via Brighter and query for the results of that state change via Darker (and return those results to the caller).
A routing key, also called a topic is the key used by message broker to route published messages to a subscriber.
In , an event stream delivers (or records) via a steam. A consumer reads the stream at a specific offset from the start. Consumers can store their offsets to resume reading the stream for where they left off, or reset their offset to re-read a stream. Consumers neither lock, nor delete messages from the stream. For consuming apps to scale, the stream can be partitioned, allowing offsets to be maintained of a partition of the stream. By using separate consumer threads or processes to read a partition, an application can ensure that it is able to reduce the latency of reading the stream.
An external bus allows a or to be turned into a and sent over message-oriented-middleware via broker to a or .
Brighter also offers a to listen for messages published to a queue or stream and forward them to an within another process.
A , or is executed in-process, passed from the or to a .
A message is a packet of data sent over message-oriented-middleware. It's on-the-wire representation is defined by the protocol used by .
The class of applications that deliver a from one process to another. MoM may send messages either point-to-point (with just a ) between sender and receiver, or via a broker, which acts as a dynamic router for messages between sender and receiver. With a broker, the receiver often establishes a subscription to a routing table entry (a routing key or topic) via a or an .
A message mapper turns domain code into a message: a header and a body, or turns a message into domain code. Because typically looks in a header for routing information, it is also where you add routing information via the header.
In , a message queue delivers via a queue. A consumer locks a message, processes it, and when it acknowledges it, it is deleted from the queue. Other consumers can process the same queue, and read past any locked messages. This allows scaling via the competing consumers pattern. A nack will release the lock and make a message visible in the queue again, sometimes with a delay. A dead-letter-queue (DLQ) can be used with a nack, to limit the number of retries before a message is considered to be "posion pill" and moved to another queue for undeliverable messages.
A pipeline is a sequence of that respond to a or . The last handler in the sequence is the "target" handler, which forms the pipeline sink. Handlers prior to that form "middleware" that can transform or respond to the request before it reaches the target handler.
A query asks the domain for facts. The of the query reports these facts - the state of the domain. A query does not change the state of the domain, for that use a .
A handler is the entry point to domain code. It receives a query and returns a to the caller. A handler is always part of an . As such a handler forms part of a .
In Darker, a query processor allows you to use the Query Object Pattern to separate caller from the executor, typically when separating the code required to execute a query on a specific database/backing store from the parameters of that query. It acts both as a Query Dispatcher which allows the separation of the parameters of a from the that executes that query and a Query Processor that allows you to use a middleware to provide additional and re-usable behaviors when processing that query.
The Query Processor dispatches to an .
The Query Processor returns a .
The return value from a . The result is returned from a and exposed to the caller via the .
In Brighter, either a or an , a request for the domain to (potentially) change state in response to an instruction or new facts.
A handler is the entry point to domain code. It receives a request, which may be a or an . A handler is always part of an even when the call to the handler was triggered by a receiving a sent by another process to an . As such a handler forms part of a .
To enforce Brighter handles commands/events and Darker handles queries.
Where the request changes state, Brighter models this as a and a matching which describes the change. (See for a discussion of returning a response directly to the sender of a Command).
Where the request queries for state, Darker models this as a , which returns a directly to the caller.
If the call to Brighter results in a new entity, and the id for the new entity was not given to the command (for example it relies on the Database generating the id), a common problem is how to then request the details of that newly created entity via Darker. A simple solution is to update the command with the id (as a conceptual out parameter), and then retrieve it from there to use in the Darker query. See for more.
A Service Activator triggers execution of your code due to an external input, such as an HTTP call, or a sent over middleware.
In Brighter, the Dispatcher acts as a Service Activator, listening for a message from middleware, which it delivers via the to a . As such, it turns messages sent over middleware to a call on your .