@fraktalio/fmodel-ts
Version:
Functional domain modeling with TypeScript. Optimized for event sourcing and CQRS
148 lines • 12.3 kB
JavaScript
"use strict";
/*
* Copyright 2023 Fraktalio D.O.O. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
* AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventSourcingOrchestratingAggregate = exports.EventSourcingAggregate = exports.EventOrchestratingComputation = exports.EventComputation = void 0;
/**
* An abstract algorithm to compute new events based on the old events and the command being handled.
*/
class EventComputation {
constructor(decider) {
this.decider = decider;
this.initialState = decider.initialState;
}
decide(command, state) {
return this.decider.decide(command, state);
}
evolve(state, event) {
return this.decider.evolve(state, event);
}
computeNewEvents(events, command) {
const currentState = events.reduce(this.decider.evolve, this.decider.initialState);
return this.decider.decide(command, currentState);
}
}
exports.EventComputation = EventComputation;
/**
* An abstract algorithm to compute new events based on the old events and the command being handled.
* It returns all the events, including the events created by handling commands which are triggered by Saga - orchestration included.
*/
class EventOrchestratingComputation {
constructor(decider, saga) {
this.decider = decider;
this.saga = saga;
this.initialState = decider.initialState;
}
decide(command, state) {
return this.decider.decide(command, state);
}
evolve(state, event) {
return this.decider.evolve(state, event);
}
react(event) {
return this.saga.react(event);
}
computeNewEventsInternally(events, command) {
const currentState = events.reduce(this.decider.evolve, this.decider.initialState);
return this.decider.decide(command, currentState);
}
async computeNewEvents(events, command, fetch) {
// eslint-disable-next-line functional/no-let
let resultingEvents = this.computeNewEventsInternally(events, command);
await asyncForEach(resultingEvents.flatMap((evt) => this.saga.react(evt)), async (cmd) => {
const newEvents = this.computeNewEvents((await fetch(cmd)).map((evt) => evt).concat(resultingEvents), cmd, fetch);
resultingEvents = resultingEvents.concat(await newEvents);
});
return resultingEvents;
}
}
exports.EventOrchestratingComputation = EventOrchestratingComputation;
/**
* Event sourcing aggregate is using/delegating a `EventSourcingAggregate.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 `EventSourcingAggregate.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 E - Version
* @typeParam CM - Command Metadata
* @typeParam EM - Event Metadata
*
* @author Иван Дугалић / Ivan Dugalic / @idugalic
*/
class EventSourcingAggregate extends EventComputation {
constructor(decider, eventRepository) {
super(decider);
this.eventRepository = eventRepository;
}
async fetch(command) {
return this.eventRepository.fetch(command);
}
async versionProvider(event) {
return this.eventRepository.versionProvider(event);
}
async save(events, commandMetadata, versionProvider) {
return this.eventRepository.save(events, commandMetadata, versionProvider);
}
async handle(command) {
const currentEvents = await this.eventRepository.fetch(command);
return this.eventRepository.save(this.computeNewEvents(currentEvents, command), command, async () => currentEvents[currentEvents.length - 1]);
}
}
exports.EventSourcingAggregate = EventSourcingAggregate;
/**
* Event sourcing orchestrating aggregate is using/delegating a `EventSourcingOrchestratingAggregate.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 `EventSourcingOrchestratingAggregate.decider` which can produce new event(s) as a result.
*
* If the `EventSourcingOrchestratingAggregate.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 `EventSourcingOrchestratingAggregate.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
*
* @author Иван Дугалић / Ivan Dugalic / @idugalic
*/
class EventSourcingOrchestratingAggregate extends EventOrchestratingComputation {
constructor(decider, eventRepository, saga) {
super(decider, saga);
this.eventRepository = eventRepository;
}
async fetch(command) {
return this.eventRepository.fetch(command);
}
async versionProvider(event) {
return this.eventRepository.versionProvider(event);
}
async save(events, commandMetadata, versionProvider) {
return this.eventRepository.save(events, commandMetadata, versionProvider);
}
async handle(command) {
const currentEvents = await this.eventRepository.fetch(command);
return this.eventRepository.save(await this.computeNewEvents(currentEvents, command, async (cmd) => await this.eventRepository.fetch(cmd)), command, this.versionProvider.bind(this));
}
}
exports.EventSourcingOrchestratingAggregate = EventSourcingOrchestratingAggregate;
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXZlbnRzb3VyY2luZy1hZ2dyZWdhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL2FwcGxpY2F0aW9uL2V2ZW50c291cmNpbmctYWdncmVnYXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7Ozs7R0FXRzs7O0FBb0dIOztHQUVHO0FBQ0gsTUFBc0IsZ0JBQWdCO0lBQ3BDLFlBQXlDLE9BQTBCO1FBQTFCLFlBQU8sR0FBUCxPQUFPLENBQW1CO1FBQ2pFLElBQUksQ0FBQyxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQztJQUMzQyxDQUFDO0lBSUQsTUFBTSxDQUFDLE9BQVUsRUFBRSxLQUFRO1FBQ3pCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFFRCxNQUFNLENBQUMsS0FBUSxFQUFFLEtBQVE7UUFDdkIsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDM0MsQ0FBQztJQUVTLGdCQUFnQixDQUFDLE1BQW9CLEVBQUUsT0FBVTtRQUN6RCxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsTUFBTSxDQUNoQyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFDbkIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQzFCLENBQUM7UUFDRixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxZQUFZLENBQUMsQ0FBQztJQUNwRCxDQUFDO0NBQ0Y7QUF0QkQsNENBc0JDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBc0IsNkJBQTZCO0lBR2pELFlBQ3FCLE9BQTBCLEVBQzFCLElBQWlCO1FBRGpCLFlBQU8sR0FBUCxPQUFPLENBQW1CO1FBQzFCLFNBQUksR0FBSixJQUFJLENBQWE7UUFFcEMsSUFBSSxDQUFDLFlBQVks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