Skip to main content

Application

The only responsibility of the application layer is to orchestrate the execution of the logic by

  • loading the state from a repository
  • execute logic by executing domain layer components
  • store the new state

Fmodel offers application interfaces/components which are actually composed out of repository interfaces/components (side effects) and core domain components (decision-making):

Event-Sourced vs State-Stored

Event-stored systems are split to command and view/query models, by default. This is making a huge difference as we are not limited to use a single canonical model for writing and reading/presenting!

event-modeling-event-driven-systems

Event-sourcing Aggregate is a formalization of the event-stored/event-sourced system (Command Model) mentioned previously.

interface IEventSourcingAggregate<C, S, E, V, CM, EM>
interface EventSourcingOrchestratingAggregate<C, S, E>
interface IMaterializedView<S, E, V, EM>
interface ISagaManager<AR, A, ARM, AM>

Event-sourcing Aggregate is using/delegating a Decider to handle commands and produce events. It belongs to the Application layer. In order to handle the command, aggregate needs to fetch the current state (represented as a list of events) via EventRepository.fetchEvents function, and then delegate the command to the decider which can produce new events as a result. Produced events are then stored via EventRepository.save suspending function.

/**
* Event sourcing aggregate interface is using/delegating a `decider` of type `IDecider`<`C`, `S`, `E`> to handle commands and produce events.
* In order to handle the command, aggregate needs to fetch the current state (represented as a list of events) via `IEventRepository.fetchEvents` function, and then delegate the command to the `decider` which can produce new event(s) as a result.
*
* Produced events are then stored via `IEventRepository.save` function.
*
* @typeParam C - Commands of type `C` that this aggregate can handle
* @typeParam S - Aggregate state of type `S`
* @typeParam E - Events of type `E` that this aggregate can publish
* @typeParam V - Version
* @typeParam CM - Command Metadata
* @typeParam EM - Event Metadata
*
*/
export interface IEventSourcingAggregate<C, S, E, V, CM, EM>
extends IDecider<C, S, E>,
IEventRepository<C, E, V, CM, EM> {
/**
* Handles the command of type `C`, and returns new persisted list of pairs of event and its version.
*
* @param command - Command of type `C` with Command Metadata
* @return list of persisted events with Version and Event Metadata
*/
readonly handle: (command: C & CM) => Promise<readonly (E & V & EM)[]>;
}

/**
* Event sourcing orchestrating aggregate interface is using/delegating a `decider` of type `IDecider`<`C`, `S`, `E`> to handle commands and produce events.
* In order to handle the command, aggregate needs to fetch the current state (represented as a list of events) via `IEventRepository.fetchEvents` function, and then delegate the command to the `decider` which can produce new event(s) as a result.
*
* If the `decider` is combined out of many deciders via `combine` function, an optional `EventSourcingOrchestratingAggregate.saga` could be used to react on new events and send new commands to the `decider` recursively, in one transaction.
*
* Produced events are then stored via `IEventRepository.save` function.
*
* @typeParam C - Commands of type `C` that this aggregate can handle
* @typeParam S - Aggregate state of type `S`
* @typeParam E - Events of type `E` that this aggregate can publish
* @typeParam V - Version
* @typeParam CM - Command Metadata
* @typeParam EM - Event Metadata
*
*/
export interface IEventSourcingOrchestratingAggregate<C, S, E, V, CM, EM>
extends IEventSourcingAggregate<C, S, E, V, CM, EM>,
ISaga<E, C> {}

Example of a monolith scenario, in which Order and Restaurant deciders are combined/aggregated in one big decider and then wrapped by one aggregate component:

const restaurantDecider: Decider<
RestaurantCommand,
Restaurant | null,
RestaurantEvent
>;
const orderDecider: Decider<OrderCommand, Order | null, OrderEvent>;

class AggregateEventRepository
implements
IEventRepository<
Command,
Event,
StreamVersion,
CommandMetadata,
EventMetadata
> {
// Implement the repository using the DB you find fit!
}

const eventRepository = new AggregateEventRepository();

const aggregate: ApplicationAggregate = new EventSourcingAggregate(
restaurantDecider.combine(orderDecider),
eventRepository
);

/**
* Start handling all commands!
*/
const result = await aggregate.handle(command);

Materialized View is a formalization of the event-stored/event-sourced system (View Model) mentioned previously.

export interface IMaterializedView<S, E, V, EM>
extends IView<S, E>,
IViewStateRepository<E, S, V, EM>

Materialized view is using/delegating a View (domain component) to handle events of type E and to maintain a state of denormalized projection(s) as a result.

Example of a monolith scenario, in which Order and Restaurant views are combined in one big view and then wrapped by one materialized-view component:

const restaurantView: View<RestaurantView | null, RestaurantEvent>;
const orderView: View<OrderView | null, OrderEvent>;

const viewRepository: IViewStateRepository<
RestaurantEvent | OrderEvent,
ViewState,
StreamVersion,
EventMetadata
> = new DenoViewStateRepository(kv);

const materializedView: ApplicationMaterializedView = new MaterializedView(
restaurantView.combine(orderView),
viewRepository
);

// Handle the events of all types
const result = await materializedView.handle(event);