Journal
, like a ledger, keeps a record of all occurrences of important happenings in a domain model of a Bounded Context. A Journal
may be thought of in terms of Event Sourcing, because event instances may be journaled over the lifetime of a given Entity/Aggregate that capture the result of actions that were carried out on it. The captured set of ordered events of a given Entity/Aggregate together form its event stream. A single Journal
may be used to hold the event streams of all Entity/Aggregate instances of a Bounded Context.Journal
need not be limited to persisting only events. The XOOM Symbio defines an abstract base class known as Source
. A Source
is parameterized by a specific type. There are no concrete Source
types defined in XOOM Symbio. In XOOM Lattice there are a few concrete Source
types defined: Command
, DomainEvent
, and Process
.Command
is the expression of the intention to execute a command action. Although there is an expressed intention to carry out the command, the choice is up to the command receiver to accept and execute it, or to reject it. The command receiver is generally an Entity/Aggregate, but doesn't have to be; it could be a Domain Service or other kind of service. The Command
type is a Source
, an thus commands may be persisted in a Journal
. Persisting a Command
is generally done to ensure that it is guaranteed to be offered as an intention to be carried out at some future time. Generally Process Managers (or Sagas) will be CommandSourced
because they issue commands to be carried out in response to previous outcomes. These previous outcomes are generally received by the Process Managers as DomainEvent
instances.DomainEvent
is a record of a significant business occurrence within a domain model. A DomainEvent
may not be rejected, in the sense that it is a captured fact. However, it may be ignored by all unconcerned parties. Generally if there will be an action carried out in response to the fact of a DomainEvent
, it will be by means of translating the DomainEvent
to a Command
, and thus the corresponding Command
may be rejected.EventSourced
and a component that is CommandSourced
are really quite similar, but the semantics are inverted for each. An EventSourced
component receives commands and emits events in response. A CommandSourced
component receives notification of event occurrences and emits commands in response. The receipt of a command by an EventSourced
component need not be in the form of an object; it may be the receipt of a protocol message with parameters received by an actor asynchronously. The same applies for a CommandSourced
component, which may be informed of a previous event occurrence by sending it a protocol message with parameters that is received asynchronously by the implementing actor.Command
instances and DomainEvent
instances, the instances will be wrapped in a ProcessMessage
. This enables a Process Manager to stream all of its Source
instances in a generic way.Source
types is that they may be used to represent the state of a given component, either one that is EventSourced
or one that is CommandSourced
, or a ProcessMessage
that may be sourced by both commands and events. The state of any such component, such as an Entity/Aggregate is a stream of such Source
types, ordered by the sequence in which they originally occurred.Journal
as having the following logical columns, and which are actual persistence columns when using a relational database, for example.Journal
need not be implemented in a relational database. It may use a key-value store or another kind of storage. Even so, logically the above elements must be supported by the storage mechanism.Journal
may maintain snapshots of any given sourced type instances as a performance optimization when they have accumulated large streams. If snapshots are used the Journal must maintain them and provide the means to merge the stream from a given version into the snapshot state.EventSourced
, CommandSourced
, or ProcessMessage
, there is no need to learn the operations of the Journal
. You get all storage persistence for free when you use one of the sourced entity abstract base types.Journal
, create an actor with the protocol and the implementation you will use to store the source streams of your Bounded Context.PostgresJournalActor
is the implementation of the Journal protocol for the Postgres database. The Dispatcher
is used to accept newly appended Source
instances, such as for various DomainEvent
types, and relay them to consumers. The consumers may be projection processors that build and maintain CQRS query models, and that feed messaging topics and exchanges to publish the occurrences.Source
instances to the Journal
.Source
instances that will be appended, either one or more than one, and whether or not a snapshot will be persisted, and whether or not Metadata
is provided.AppendResultInterest
is used to asynchronously communicate the result of the append to the sender. The result maybe be a success or failure, and will contain the data provided for the append operation, along with any Object
instance that is optionally sent.Dispatcher
registered with the Journal
is used to guarantee delivery of the original Source
and corresponding persisted serialized Entry
, clients may desire to read the Journal
contents at any future time. To do so the client obtains a JournalReader
.JournalReader
is returned asynchronously by means of a Completes
and is given the name
provided as a parameter. If the same name
is requested in the future of this actor's in-memory lifetime, the same JournalReader
is returned.JournalReader
provides the following protocol.Completes
. The parameterless readNext()
answers the single next available Entry
in the Journal
. The readNext(maximumEntries)
answers the next available Entry
instances up to the maximumEntries
. The rewind()
moves the read start location back to the beginning of the Journal
. The seekTo()
is used to seek to a given Entry
position in the Journal
, or to simply provide the current position.id
Entry
that possesses the id
corresponding to the given id
(e.g. position or sequence).Beginning
Journal
, which is the same as using rewind()
. Answers the current position following the operation.End
Journal
, which is the position past the current last Entry
. Answers the current position following the operation.Query
Source
streams, use the StreamReader
. You may obtain this also from the Journal
.JournalReader
, the StreamReader
is returned asynchronously by means of a Completes
and is given the name
provided as a parameter. If the same name
is requested in the future of this actor's in-memory lifetime, the same StreamReader
is returned.StreamReader
works as follows.Stream
that is answered. Optimizing the stream means that if a snapshot is available it is read first, and only the Entry
instances that follow the snapshot's version are read. This optimization applies to both streamFor()
query messages.streamFor(streamName)
will answer the full Stream
of the Entity/Aggregate uniquely identified by streamName
if a snapshot is unavailable. The streamFor(streamName)
uses streamFor(streamName, 1)
. ThestreamFor(streamName, fromStreamVersion)
answers the sub-portion of theStream
uniquely identified by streamName
starting at the fromStreamVersion
until the stream's end. The Stream
is defined as follows.snapshot
, if any, and the entries
, are all in serialized form; that is, the State<T>
and BaseEntry<T>
respectively. The T
parameter indicates whether it is a text String
based serialization or a binary serialization of byte[]
. This depends on the concrete Journal
implementation used. To render the Entry
instances and possible State
to their native form, use the EntryAdapter
and StateAdapter
respectively.Journal
implementations. We provide specific guidance on using PostgreSQL, but this is nearly the same across other databases when using JDBC .docker-compose
, so you can easily recreate a local development cluster. You can use this docker-compose.yaml
as an example.​Journal
protocol. For JDBC-compatible databases you useJDBCJournalActor
. There is a JournalListener
with useful hooks during the lifecycle of the Event Journal, so you can implement yours or keep it empty (but it can't be null).