{"id":37016420,"url":"https://github.com/fraktalio/fmodel","last_synced_at":"2026-01-14T01:51:21.801Z","repository":{"id":36996250,"uuid":"348104598","full_name":"fraktalio/fmodel","owner":"fraktalio","description":"Functional, Algebraic and Reactive domain modeling with Kotlin (Multiplatform)","archived":false,"fork":false,"pushed_at":"2025-12-31T20:01:12.000Z","size":42689,"stargazers_count":273,"open_issues_count":13,"forks_count":13,"subscribers_count":9,"default_branch":"main","last_synced_at":"2026-01-04T21:29:21.761Z","etag":null,"topics":["arrow-kt","cqrs","event-sourcing","eventsourcing","functional-programming","kotlin","reactive","streaming"],"latest_commit_sha":null,"homepage":"https://fraktalio.com/fmodel/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fraktalio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-03-15T19:54:58.000Z","updated_at":"2026-01-04T18:32:38.000Z","dependencies_parsed_at":"2023-10-14T16:17:13.314Z","dependency_job_id":"71406f3c-ff26-4987-8d56-38d8829ff8d8","html_url":"https://github.com/fraktalio/fmodel","commit_stats":{"total_commits":405,"total_committers":5,"mean_commits":81.0,"dds":0.6024691358024692,"last_synced_commit":"c819e0ffa271692105243db25c1eb26d05c5897b"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/fraktalio/fmodel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fraktalio","download_url":"https://codeload.github.com/fraktalio/fmodel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fraktalio%2Ffmodel/sbom","scorecard":{"id":408854,"data":{"date":"2025-08-11","repo":{"name":"github.com/fraktalio/fmodel","commit":"520db328db384b9ca850dd126f7e983292cfb1e6"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.4,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":3,"reason":"Found 3/9 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/gradle-test-build-publish.yml:1","Warn: no topLevel permission defined: .github/workflows/gradle-test-build.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Pinned-Dependencies","score":1,"reason":"dependency not pinned by hash detected -- score normalized to 1","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle-test-build-publish.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/fraktalio/fmodel/gradle-test-build-publish.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/gradle-test-build-publish.yml:37: update your workflow using https://app.stepsecurity.io/secureworkflow/fraktalio/fmodel/gradle-test-build-publish.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle-test-build.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/fraktalio/fmodel/gradle-test-build.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/gradle-test-build.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/fraktalio/fmodel/gradle-test-build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle-test-build.yml:48: update your workflow using https://app.stepsecurity.io/secureworkflow/fraktalio/fmodel/gradle-test-build.yml/main?enable=pin","Info:   2 out of   5 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/gradle-test-build-publish.yml:17"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 24 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-18T22:06:51.276Z","repository_id":36996250,"created_at":"2025-08-18T22:06:51.276Z","updated_at":"2025-08-18T22:06:51.276Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T00:40:43.272Z","status":"ssl_error","status_checked_at":"2026-01-14T00:40:42.636Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arrow-kt","cqrs","event-sourcing","eventsourcing","functional-programming","kotlin","reactive","streaming"],"created_at":"2026-01-14T01:51:21.174Z","updated_at":"2026-01-14T01:51:21.794Z","avatar_url":"https://github.com/fraktalio.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **f`(`model`)`** - Domain Modeling\n\nWhen you’re developing an information system to automate the activities of the business, you are modeling the business.\nThe abstractions that you design, the behaviors that you implement, and the UI interactions that you build all reflect\nthe business — together, they constitute the model of the domain.\n\n## `IOR\u003cLibrary, Inspiration\u003e`\n\nThis project can be used as a multiplatform library, or as an inspiration, or both. **It provides just enough tactical\nDomain-Driven Design patterns, optimised for Event Sourcing and CQRS.**\n\n- The `domain` model library is fully isolated from the application layer and API-related concerns. It represents a pure\n  declaration of the program logic. It is written in [Kotlin](https://kotlinlang.org/) programming language, without\n  additional\n  dependencies. [![Maven Central - domain](https://img.shields.io/maven-central/v/com.fraktalio.fmodel/domain.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.fraktalio.fmodel%22%20AND%20a:%22domain%22)\n- The `application` libraries orchestrates the execution of the logic by loading state, executing `domain` components\n  and storing new state. It is written in [Kotlin](https://kotlinlang.org/) programming language. Two flavors (\n  extensions of `Application` module) are available:\n  [![Maven Central - application](https://img.shields.io/maven-central/v/com.fraktalio.fmodel/application.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.fraktalio.fmodel%22%20AND%20a:%22application%22)\n    - `application-vanilla` is using plain/vanilla Kotlin to implement the application layer in order to load the state,\n      orchestrate the execution of the logic and save new state.\n    - `application-arrow` is using [Arrow](https://arrow-kt.io/) and Kotlin to implement the application layer in order\n      to load the state, orchestrate the execution of the logic and save new state - managing errors much better (using\n      Either).\n\nThe libraries are non-intrusive, and you can select any flavor, or choose both (`vanila` and `arrow`). You can use\nonly `domain` library and model the orchestration (`application` library) on your own. Or, you can simply be inspired by\nthis project :)\n\n![event-modeling](.assets/event-modeling.png)\n\n## Table of Contents\n\n* [\u003cstrong\u003ef(model)\u003c/strong\u003e - Functional domain modeling](#fmodel---functional-domain-modeling)\n    * [Multiplatform](#multiplatform)\n    * [Abstraction and generalization](#abstraction-and-generalization)\n    * [decide: (C, S) -\u0026gt; Flow\u0026lt;E\u0026gt;](#decide-c-s---flowe)\n    * [evolve: (S, E) -\u0026gt; S](#evolve-s-e---s)\n    * [Event-sourced or State-stored systems](#event-sourced-or-state-stored-systems)\n    * [Decider](#decider)\n        * [Decider extensions and functions](#decider-extensions-and-functions)\n        * [Event-sourcing aggregate](#event-sourcing-aggregate)\n        * [State-stored aggregate](#state-stored-aggregate)\n    * [View](#view)\n        * [View extensions and functions](#view-extensions-and-functions)\n        * [Materialized View](#materialized-view)\n    * [Saga](#saga)\n        * [Saga extensions and functions](#saga-extensions-and-functions)\n        * [Saga Manager](#saga-manager)\n    * [Kotlin](#kotlin)\n    * [Examples](#start-using-the-libraries)\n    * [References and further reading](#references-and-further-reading)\n\n## Multiplatform\n\nSupport for multiplatform programming is one of Kotlin’s key benefits. It reduces time spent writing and maintaining the\nsame code for different platforms while retaining the flexibility and benefits of native programming.\n\n## Abstraction and generalization\n\nAbstractions can hide irrelevant details and use names to reference objects. It emphasizes what an object is or does\nrather than how it is represented or how it works.\n\nGeneralization reduces complexity by replacing multiple entities which perform similar functions with a single\nconstruct.\n\nAbstraction and generalization are often used together. Abstracts are generalized through parameterization to provide\nmore excellent utility.\n\n## `decide: (C, S) -\u003e Flow\u003cE\u003e`\n\nOn a higher level of abstraction, any information system is responsible for handling the intent (`Command`) and based on\nthe current `State`, produce new facts (`Events`):\n\n- given the current `State/S` *on the input*,\n- when `Command/C` is handled *on the input*,\n- expect `flow` of new `Events/E` to be published/emitted *on the output*\n\n## `evolve: (S, E) -\u003e S`\n\nThe new state is always evolved out of the current state `S` and the current event `E`:\n\n- given the current `State/S` *on the input*,\n- when `Event/E` is handled *on the input*,\n- expect new `State/S` to be published *on the output*\n\n## Event-sourced or State-stored systems\n\n- State-stored systems are traditional systems that are only storing the current State by overwriting the previous State\n  in the storage.\n- Event-sourced systems are storing the events in immutable storage by only appending.\n\n### A statement:\n\nBoth types of systems can be designed by using only these two functions and three generic parameters:\n\n- `decide: (C, S) -\u003e Flow\u003cE\u003e`\n- `evolve: (S, E) -\u003e S`\n\n![event sourced vs state stored](.assets/es-ss-system.png)\n\nThere is more to it! You can switch from one system type to another or have both flavors included within your systems\nlandscape.\n\n\u003cdetails\u003e\n  \u003csummary\u003eA proof\u003c/summary\u003e\n\nWe can fold/recreate the new state out of the flow of events by using `evolve` function `(S, E) -\u003e S` and providing the\ninitialState of type S as a starting point.\n\n- `Flow\u003cE\u003e.fold(initialState: S, ((S, E) -\u003e S)): S`\n\nEssentially, this `fold` is a function that is mapping a flow of Events to the State:\n\n- `(Flow\u003cE\u003e) -\u003e S`\n\nWe can now use this function `(Flow\u003cE\u003e) -\u003e S` to:\n\n- contra-map our `decide` function (`(C, S) -\u003e Flow\u003cE\u003e`) over `S` type to: `(C, Flow\u003cE\u003e) -\u003e Flow\u003cE\u003e`  - **this is an\n  event-sourced system**\n- or to map our `decide` function (`(C, S) -\u003e Flow\u003cE\u003e`) over `E` type to: `(C, S) -\u003e S` - **this is a state-stored\n  system**\n\n\u003c/details\u003e\n\nTwo functions are wrapped in a datatype class (algebraic data structure), which is generalized with three generic\nparameters:\n\n```kotlin\ndata class Decider\u003cC, S, E\u003e(\n    val decide: (C, S) -\u003e Flow\u003cE\u003e,\n    val evolve: (S, E) -\u003e S,\n)\n```\n\n`Decider` is the most important datatype, but it is not the only one. There are others:\n\n![onion architecture image](.assets/onion.png)\n\n## Decider\n\n`Decider` is a datatype that represents the main decision-making algorithm. It belongs to the Domain layer. It has three\ngeneric parameters `C`, `S`, `E` , representing the type of the values that `Decider` may contain or use.\n`Decider` can be specialized for any type `C` or `S` or `E` because these types do not affect its\nbehavior. `Decider` behaves the same for `C`=`Int` or `C`=`YourCustomType`, for example.\n\n`Decider` is a pure domain component.\n\n- `C` - Command\n- `S` - State\n- `E` - Event\n\n```kotlin\ndata class Decider\u003cin C, S, E\u003e(\n    override val decide: (C, S) -\u003e Flow\u003cE\u003e,\n    override val evolve: (S, E) -\u003e S,\n    override val initialState: S\n) : IDecider\u003cC, S, E\u003e \n```\n\nAdditionally, `initialState` of the Decider is introduced to gain more control over the initial state of the Decider.\nNotice that `Decider` implements an interface `IDecider` to communicate the contract.\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\nfun restaurantOrderDecider() = Decider\u003cRestaurantOrderCommand?, RestaurantOrder?, RestaurantOrderEvent?\u003e(\n    // Initial state of the Restaurant Order is `null`. It does not exist.\n    initialState = null,\n    // Exhaustive command handler(s): for each type of [RestaurantCommand] you are going to publish specific events/facts, as required by the current state/s of the [RestaurantOrder].\n    decide = { c, s -\u003e\n        when (c) {\n            is CreateRestaurantOrderCommand -\u003e\n                // ** positive flow **\n                if (s == null) flowOf(RestaurantOrderCreatedEvent(c.identifier, c.lineItems, c.restaurantIdentifier))\n                // ** negative flow **\n                else flowOf(RestaurantOrderRejectedEvent(c.identifier, \"Restaurant order already exists\"))\n            is MarkRestaurantOrderAsPreparedCommand -\u003e\n                // ** positive flow **\n                if ((s != null \u0026\u0026 CREATED == s.status)) flowOf(RestaurantOrderPreparedEvent(c.identifier))\n                // ** negative flow **\n                else flowOf(\n                    RestaurantOrderNotPreparedEvent(\n                        c.identifier,\n                        \"Restaurant order does not exist or not in CREATED state\"\n                    )\n                )\n            null -\u003e emptyFlow() // We ignore the `null` command by emitting the empty flow. Only the Decider that can handle `null` command can be combined (Monoid) with other Deciders.\n        }\n    },\n    // Exhaustive event-sourcing handler(s): for each event of type [RestaurantEvent] you are going to evolve from the current state/s of the [RestaurantOrder] to a new state of the [RestaurantOrder]\n    evolve = { s, e -\u003e\n        when (e) {\n            is RestaurantOrderCreatedEvent -\u003e RestaurantOrder(e.identifier, e.restaurantId, CREATED, e.lineItems)\n            is RestaurantOrderPreparedEvent -\u003e s?.copy(status = PREPARED)\n            is RestaurantOrderErrorEvent -\u003e s // Error events are not changing the state / We return current state instead.\n            null -\u003e s // Null events are not changing the state / We return current state instead. Only the Decider that can handle `null` event can be combined (Monoid) with other Deciders.\n        }\n    }\n)\n```\n\n\u003c/details\u003e\n\n![decider image](.assets/decider.png)\n\n### Decider extensions and functions\n\n#### Contravariant\n\n- `Decider\u003cC, S, E\u003e.mapLeftOnCommand(f: (Cn) -\u003e C): Decider\u003cCn, S, E\u003e`\n\n#### Profunctor (Contravariant and Covariant)\n\n- `Decider\u003cC, S, E\u003e.dimapOnEvent(fl: (En) -\u003e E, fr: (E) -\u003e En): Decider\u003cC, S, En\u003e`\n- `Decider\u003cC, S, E\u003e.dimapOnState(fl: (Sn) -\u003e S, fr: (S) -\u003e Sn): Decider\u003cC, Sn, E\u003e`\n\n#### *Commutative* Monoid\n\n- `\u003creified Cx : C_SUPER, Sx, reified Ex : E_SUPER, reified Cy : C_SUPER, Sy, reified Ey : E_SUPER, C_SUPER\u003e Decider\u003cCx?, Sx, Ex?\u003e.combine(\n  y: Decider\u003cCy?, Sy, Ey?\u003e\n  ): Decider\u003cC_SUPER, Pair\u003cSx, Sy\u003e, E_SUPER\u003e`\n\n- with identity element `Decider\u003cNothing?, Unit, Nothing?\u003e`\n\n\u003e A monoid is a type together with a binary operation (`combine`) over that type, satisfying associativity and having an\n\u003e identity/empty element.\n\u003e Associativity facilitates parallelization by giving us the freedom to break problems into chunks that can be computed\n\u003e in parallel.\n\u003e\n\u003e `combine` operation is also commutative. This means that the order in which deciders are combined does not affect the\n\u003e result.\n\n\nWe can now construct event-sourcing or/and state-storing aggregate by using the same `decider`.\n\n### Event-sourcing aggregate\n\n[Event sourcing aggregate](application/src/commonMain/kotlin/com/fraktalio/fmodel/application/EventSourcingAggregate.kt)\nis using/delegating a `Decider` to handle commands and produce events. It belongs to the Application layer. In order to\nhandle the command, aggregate needs to fetch the current state (represented as a list of events)\nvia `EventRepository.fetchEvents` function, and then delegate the command to the decider which can produce new events as\na result. Produced events are then stored via `EventRepository.save` suspending function.\n\n![event sourced aggregate](.assets/es-aggregate.png)\n\n`EventSourcingAggregate` extends `IDecider` and `EventRepository` interfaces, clearly communicating that it is composed\nout of these two behaviours.\n\nThe Delegation pattern has proven to be a good alternative to `implementation inheritance`, and Kotlin supports it\nnatively requiring zero boilerplate code.\n`eventSourcingAggregate` function is a good example:\n\n```kotlin\nfun \u003cC, S, E\u003e EventSourcingAggregate(\n    decider: IDecider\u003cC, S, E\u003e,\n    eventRepository: EventRepository\u003cC, E\u003e\n): EventSourcingAggregate\u003cC, S, E\u003e =\n    object :\n        EventSourcingAggregate\u003cC, S, E\u003e,\n        EventRepository\u003cC, E\u003e by eventRepository,\n        IDecider\u003cC, S, E\u003e by decider {}\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\ntypealias RestaurantOrderAggregate = EventSourcingAggregate\u003cRestaurantOrderCommand?, RestaurantOrder?, RestaurantOrderEvent?\u003e\n\nfun restaurantOrderAggregate(\n    restaurantOrderDecider: RestaurantOrderDecider,\n    eventRepository: EventRepository\u003cRestaurantOrderCommand?, RestaurantOrderEvent?\u003e\n): RestaurantOrderAggregate = eventSourcingAggregate(\n    decider = restaurantOrderDecider,\n    eventRepository = eventRepository,\n)\n```\n\n\u003c/details\u003e\n\n### State-stored aggregate\n\n[State stored aggregate](application/src/commonMain/kotlin/com/fraktalio/fmodel/application/StateStoredAggregate.kt) is\nusing/delegating a `Decider` to handle commands and produce new state. It belongs to the Application layer. In order to\nhandle the command, aggregate needs to fetch the current state via `StateRepository.fetchState` function first, and then\ndelegate the command to the decider which can produce new state as a result. New state is then stored\nvia `StateRepository.save` suspending function.\n\n![state storedaggregate](.assets/ss-aggregate.png)\n\n`StateStoredAggregate` extends `IDecider` and `StateRepository` interfaces, clearly communicating that it is composed\nout of these two behaviours.\n\nThe Delegation pattern has proven to be a good alternative to `implementation inheritance`, and Kotlin supports it\nnatively requiring zero boilerplate code.\n`stateStoredAggregate` function is a good example:\n\n```kotlin\nfun \u003cC, S, E\u003e StateStoredAggregate(\n    decider: IDecider\u003cC, S, E\u003e,\n    stateRepository: StateRepository\u003cC, S\u003e\n): StateStoredAggregate\u003cC, S, E\u003e =\n    object :\n        StateStoredAggregate\u003cC, S, E\u003e,\n        StateRepository\u003cC, S\u003e by stateRepository,\n        IDecider\u003cC, S, E\u003e by decider {}\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\ntypealias RestaurantOrderAggregate = StateStoredAggregate\u003cRestaurantOrderCommand?, RestaurantOrder?, RestaurantOrderEvent?\u003e\n\nfun restaurantOrderAggregate(\n    restaurantOrderDecider: RestaurantOrderDecider,\n    aggregateRepository: StateRepository\u003cRestaurantOrderCommand?, RestaurantOrder?\u003e\n): RestaurantOrderAggregate = stateStoredAggregate(\n    decider = restaurantOrderDecider,\n    stateRepository = aggregateRepository\n)\n```\n\n\u003c/details\u003e\n\n*The logic is orchestrated on the application layer. The components/functions are composed in different ways to support\nvariety of requirements.*\n\n![aggregates-application-layer](.assets/aggregates.png)\n\nCheck, [application-vanilla](application-vanilla) and [application-arrow](application-arrow) modules/libraries for\nscenarios that are offered out of the box.\n\n## View\n\n`View`  is a datatype that represents the event handling algorithm, responsible for translating the events into\ndenormalized state, which is more adequate for querying. It belongs to the Domain layer. It is usually used to create\nthe view/query side of the CQRS pattern. Obviously, the command side of the CQRS is usually event-sourced aggregate.\n\nIt has two generic parameters `S`, `E`, representing the type of the values that `View` may contain or use.\n`View` can be specialized for any type of `S`, `E` because these types do not affect its behavior.\n`View` behaves the same for `E`=`Int` or `E`=`YourCustomType`, for example.\n\n`View` is a pure domain component.\n\n- `S` - State\n- `E` - Event\n\n```kotlin\ndata class View\u003cS, in E\u003e(\n    override val evolve: (S, E) -\u003e S,\n    override val initialState: S\n) : IView\u003cS, E\u003e\n```\n\nNotice that `View` implements an interface `IView` to communicate the contract.\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\nfun restaurantOrderView() = View\u003cRestaurantOrderViewState?, RestaurantOrderEvent?\u003e(\n    // Initial state of the [RestaurantOrderViewState] is `null`. It does not exist.\n    initialState = null,\n    // Exhaustive event-sourcing handling part: for each event of type [RestaurantOrderEvent] you are going to evolve from the current state/s of the [RestaurantOrderViewState] to a new state of the [RestaurantOrderViewState].\n    evolve = { s, e -\u003e\n        when (e) {\n            is RestaurantOrderCreatedEvent -\u003e RestaurantOrderViewState(\n                e.identifier,\n                e.restaurantId,\n                CREATED,\n                e.lineItems\n            )\n            is RestaurantOrderPreparedEvent -\u003e s?.copy(status = PREPARED)\n            is RestaurantOrderErrorEvent -\u003e s // We ignore the `error` event by returning current State/s.\n            null -\u003e s // We ignore the `null` event by returning current State/s. Only the View that can handle `null` event can be combined (Monoid) with other Views.\n\n        }\n    }\n)\n```\n\n\u003c/details\u003e\n\n![view image](.assets/view.png)\n\n### View extensions and functions\n\n#### Contravariant\n\n- `View\u003cS, E\u003e.mapLeftOnEvent(f: (En) -\u003e E): View\u003cS, En\u003e`\n\n#### Profunctor (Contravariant and Covariant)\n\n- `View\u003cS, E\u003e.dimapOnState(fl: (Sn) -\u003e S, fr: (S) -\u003e Sn): View\u003cSn, E\u003e`\n\n#### *Commutative* Monoid\n\n- `\u003cSx, reified Ex : E_SUPER, Sy, reified Ey : E_SUPER, E_SUPER\u003e  View\u003cSx, Ex?\u003e.combine(y: View\u003cSy, Ey?\u003e): View\u003cPair\u003cSx, Sy\u003e, E_SUPER\u003e`\n- with identity element `View\u003cUnit, Nothing?\u003e`\n\n\u003e A monoid is a type together with a binary operation (combine) over that type, satisfying associativity and having an\n\u003e identity/empty element.\n\u003e Associativity facilitates parallelization by giving us the freedom to break problems into chunks that can be computed\n\u003e in parallel.\n\u003e\n\u003e `combine` operation is also commutative. This means that the order in which views are combined does not affect the\n\u003e result.\n\n\nWe can now construct `materialized` view by using this `view`.\n\n### Materialized View\n\nA [Materialized view](application/src/commonMain/kotlin/com/fraktalio/fmodel/application/MaterializedView.kt) is\nusing/delegating a `View` to handle events of type `E` and to maintain a state of denormalized projection(s) as a\nresult. Essentially, it represents the query/view side of the CQRS pattern. It belongs to the Application layer.\n\nIn order to handle the event, materialized view needs to fetch the current state via `ViewStateRepository.fetchState`\nsuspending function first, and then delegate the event to the view, which can produce new state as a result. New state\nis then stored via `ViewStateRepository.save` suspending function.\n\n`MaterializedView` extends `IView` and `ViewStateRepository` interfaces, clearly communicating that it is composed out\nof these two behaviours.\n\nThe Delegation pattern has proven to be a good alternative to `implementation inheritance`, and Kotlin supports it\nnatively requiring zero boilerplate code.\n`materializedView` function is a good example:\n\n```kotlin\nfun \u003cS, E\u003e MaterializedView(\n    view: IView\u003cS, E\u003e,\n    viewStateRepository: ViewStateRepository\u003cE, S\u003e,\n): MaterializedView\u003cS, E\u003e =\n    object : MaterializedView\u003cS, E\u003e, ViewStateRepository\u003cE, S\u003e by viewStateRepository, IView\u003cS, E\u003e by view {}\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\ntypealias RestaurantOrderMaterializedView = MaterializedView\u003cRestaurantOrderViewState?, RestaurantOrderEvent?\u003e\n\nfun restaurantOrderMaterializedView(\n    restaurantOrderView: RestaurantOrderView,\n    viewStateRepository: ViewStateRepository\u003cRestaurantOrderEvent?, RestaurantOrderViewState?\u003e\n): RestaurantOrderMaterializedView = materializedView(\n    view = restaurantOrderView,\n    viewStateRepository = viewStateRepository\n)\n```\n\n\u003c/details\u003e\n\n*The logic is orchestrated on the application layer. The components/functions are composed in different ways to support\nvariety of requirements.*\n\n![materialized-views-application-layer](.assets/mviews.png)\n\nCheck, [application-vanilla](application-vanilla) and [application-arrow](application-arrow) modules/libraries for\nscenarios that are offered out of the box.\n\n## Saga\n\n`Saga` is a datatype that represents the central point of control, deciding what to execute next (`A`). It is\nresponsible for mapping different events from many aggregates into action results `AR` that the `Saga` then can use to\ncalculate the next actions `A` to be mapped to commands of other aggregates.\n\n`Saga` is stateless, it does not maintain the state.\n\nIt has two generic parameters `AR`, `A`, representing the type of the values that `Saga` may contain or use.\n`Saga` can be specialized for any type of `AR`, `A` because these types do not affect its behavior.\n`Saga` behaves the same for `AR`=`Int` or `AR`=`YourCustomType`, for example.\n\n`Saga` is a pure domain component.\n\n- `AR` - Action Result\n- `A`  - Action\n\n```kotlin\ndata class Saga\u003cAR, A\u003e(\n    val react: (AR) -\u003e Flow\u003cA\u003e\n) : I_Saga\u003cAR, A\u003e\n```\n\nNotice that `Saga` implements an interface `ISaga` to communicate the contract.\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\n\nfun restaurantOrderSaga() = Saga\u003cRestaurantEvent?, RestaurantOrderCommand\u003e(\n    react = { e -\u003e\n        when (e) {\n            is RestaurantOrderPlacedAtRestaurantEvent -\u003e flowOf(\n                CreateRestaurantOrderCommand(\n                    e.restaurantOrderId,\n                    e.identifier,\n                    e.lineItems\n                )\n            )\n            is RestaurantCreatedEvent -\u003e emptyFlow() // We choose to ignore this event, in our case.\n            is RestaurantMenuActivatedEvent -\u003e emptyFlow() // We choose to ignore this event, in our case.\n            is RestaurantMenuChangedEvent -\u003e emptyFlow() // We choose to ignore this event, in our case.\n            is RestaurantMenuPassivatedEvent -\u003e emptyFlow() // We choose to ignore this event, in our case.\n            is RestaurantErrorEvent -\u003e emptyFlow() // We choose to ignore this event, in our case.\n            null -\u003e emptyFlow() // We ignore the `null` event by returning the empty flow of commands. Only the Saga that can handle `null` event/action-result can be combined (Monoid) with other Sagas.\n        }\n    }\n)\n\nfun restaurantSaga() = Saga\u003cRestaurantOrderEvent?, RestaurantCommand\u003e(\n    react = { e -\u003e\n        when (e) {\n            //TODO evolve the example ;), it does not do much at the moment.\n            is RestaurantOrderCreatedEvent -\u003e emptyFlow()\n            is RestaurantOrderPreparedEvent -\u003e emptyFlow()\n            is RestaurantOrderErrorEvent -\u003e emptyFlow()\n            null -\u003e emptyFlow() // We ignore the `null` event by returning the empty flow of commands. Only the Saga that can handle `null` event/action-result can be combined (Monoid) with other Sagas.\n        }\n    }\n)\n\n ```\n\n\u003c/details\u003e\n\n![saga image](.assets/saga.png)\n\n### Saga extensions and functions\n\n#### Contravariant\n\n- `Saga\u003cAR, A\u003e.mapLeftOnActionResult(f: (ARn) -\u003e AR): Saga\u003cARn, A\u003e`\n\n#### Covariant\n\n- `Saga\u003cAR, A\u003e.mapOnAction(f: (A) -\u003e An): Saga\u003cAR, An\u003e`\n\n#### Monoid\n\n- `\u003creified ARx : AR_SUPER, Ax : A_SUPER, reified ARy : AR_SUPER, Ay : A_SUPER, AR_SUPER, A_SUPER\u003e Saga\u003cin ARx?, out Ax\u003e.combine(y: Saga\u003cin ARy?, out Ay\u003e): Saga\u003cAR_SUPER, A_SUPER\u003e`\n- with identity element `Saga\u003cNothing?, Nothing?\u003e`\n\n\u003e A monoid is a type together with a binary operation (combine) over that type, satisfying associativity and having an\n\u003e identity/empty element.\n\u003e Associativity facilitates parallelization by giving us the freedom to break problems into chunks that can be computed\n\u003e in parallel.\n\u003e\n\u003e `combine` operation is also commutative. This means that the order in which sagas are combined does not affect the\n\u003e result.\n\n\nWe can now construct `Saga Manager` by using this `saga`.\n\n### Saga Manager\n\n[Saga manager](application/src/commonMain/kotlin/com/fraktalio/fmodel/application/SagaManager.kt) is a stateless process\norchestrator. It is reacting on Action Results of type `AR` and produces new actions `A` based on them.\n\nSaga manager is using/delegating a `Saga` to react on Action Results of type `AR` and produce new actions `A` which are\ngoing to be published via `ActionPublisher.publish` suspending function.\n\nIt belongs to the Application layer.\n\n`SagaManager` extends `ISaga` and `ActionPublisher` interfaces, clearly communicating that it is composed out of these\ntwo behaviours.\n\nThe Delegation pattern has proven to be a good alternative to `implementation inheritance`, and Kotlin supports it\nnatively requiring zero boilerplate code.\n`sagaManager` function is a good example:\n\n```kotlin\nfun \u003cAR, A\u003e SagaManager(\n    saga: ISaga\u003cAR, A\u003e,\n    actionPublisher: ActionPublisher\u003cA\u003e\n): SagaManager\u003cAR, A\u003e =\n    object : SagaManager\u003cAR, A\u003e, ActionPublisher\u003cA\u003e by actionPublisher, ISaga\u003cAR, A\u003e by saga {}\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample\u003c/summary\u003e\n\n```kotlin\n\ntypealias OrderRestaurantSagaManager = SagaManager\u003cEvent?, Command\u003e\n\nfun sagaManager(\n    restaurantOrderSaga: RestaurantOrderSaga,\n    restaurantSaga: RestaurantSaga,\n    actionPublisher: ActionPublisher\u003cCommand\u003e\n): OrderRestaurantSagaManager = sagaManager(\n    // Combining individual choreography Sagas into one orchestrating Saga.\n    saga = restaurantOrderSaga.combine(restaurantSaga),\n    // How and where do you want to publish new commands.\n    actionPublisher = actionPublisher\n)\n```\n\n\u003c/details\u003e\n\n### Experimental features\n\n#### Actors (only on [JVM](https://github.com/fraktalio/fmodel/tree/main/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application))\n\nCoroutines can be executed parallelly. It presents all the usual parallelism problems. The main problem being\nsynchronization of access to shared mutable\nstate. [Actors](https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html#actors) to the rescue!\n\n![kotlin actors](.assets/kotlin-actors.png)\n\n[Dive into the implementation ...](https://github.com/fraktalio/fmodel/tree/main/application-vanilla/src/jvmMain/kotlin/com/fraktalio/fmodel/application)\n\n```kotlin\nprivate fun \u003cC, E\u003e CoroutineScope.commandActor(\n    fanInChannel: SendChannel\u003cE\u003e,\n    capacity: Int = Channel.RENDEZVOUS,\n    start: CoroutineStart = CoroutineStart.DEFAULT,\n    context: CoroutineContext = EmptyCoroutineContext,\n    handle: (C) -\u003e Flow\u003cE\u003e\n) = actor\u003cC\u003e(context, capacity, start) {\n    for (msg in channel) {\n        handle(msg).collect { fanInChannel.send(it) }\n    }\n}\n```\n\n\u003e [Actors](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html)\n\u003e are marked as @ObsoleteCoroutinesApi by Kotlin at the moment.\n\n## Kotlin\n\n*\"Kotlin has both object-oriented and functional constructs. You can use it in both OO and FP styles, or mix elements of\nthe two. With first-class support for features such as higher-order functions, function types and lambdas, Kotlin is a\ngreat choice if you’re doing or exploring functional programming.\"*\n\n## Start using the libraries\n\nAll `fmodel` components/libraries are released to [Maven Central](https://repo1.maven.org/maven2/com/fraktalio/fmodel/)\n\n### Maven coordinates\n\n```\n \u003cdependency\u003e\n    \u003cgroupId\u003ecom.fraktalio.fmodel\u003c/groupId\u003e\n    \u003cartifactId\u003edomain\u003c/artifactId\u003e\n    \u003cversion\u003e3.7.0\u003c/version\u003e\n \u003c/dependency\u003e\n\n \u003cdependency\u003e\n    \u003cgroupId\u003ecom.fraktalio.fmodel\u003c/groupId\u003e\n    \u003cartifactId\u003eapplication-vanilla\u003c/artifactId\u003e\n    \u003cversion\u003e3.7.0\u003c/version\u003e\n \u003c/dependency\u003e\n \n \u003cdependency\u003e\n    \u003cgroupId\u003ecom.fraktalio.fmodel\u003c/groupId\u003e\n    \u003cartifactId\u003eapplication-arrow\u003c/artifactId\u003e\n    \u003cversion\u003e3.7.0\u003c/version\u003e\n \u003c/dependency\u003e\n```\n\n### Examples\n\n![decider demo implementation](.assets/decider-impl.png)\n\n![decider demo test](.assets/decider-test.png)\n\n- Browse the [tests](domain/src/commonTest/kotlin/com/fraktalio/fmodel/domain/DeciderTest.kt)\n- Learn by example on the [playground](https://fraktalio.com/blog/playground)\n- Read the [blog](https://fraktalio.com/blog/)\n- Check the demos\n    - [Spring, R2DBC, Event Sourcing, CQRS, Postgres](https://github.com/fraktalio/fmodel-spring-demo)\n    - [Spring, R2DBC, State-Stored, Postgres](https://github.com/fraktalio/fmodel-spring-state-stored-demo)\n    - [Ktor, R2DBC, Event Sourcing, CQRS, Postgres](https://github.com/fraktalio/fmodel-ktor-demo)\n\n### FModel in other languages\n\n- [FModel in TypeScript](https://github.com/fraktalio/fmodel-ts)\n- [FModel in Rust](https://github.com/fraktalio/fmodel-rust)\n\n## References and further reading\n\n- [Reference Guide](https://fraktalio.com/fmodel/)\n- [Kotlin Conf 2024 Talk](https://www.youtube.com/watch?v=VLuGvEugkGg)\n- https://www.youtube.com/watch?v=kgYGMVDHQHs\n- https://www.manning.com/books/functional-and-reactive-domain-modeling\n- https://www.manning.com/books/functional-programming-in-kotlin\n- https://www.47deg.com/blog/functional-domain-modeling/\n- https://www.47deg.com/blog/functional-domain-modeling-part-2/\n- https://www.youtube.com/watch?v=I8LbkfSSR58\u0026list=PLbgaMIhjbmEnaH_LTkxLI7FMa2HsnawM_\n\n## Credits\n\nSpecial credits to `Jérémie Chassaing` for sharing his [research](https://www.youtube.com/watch?v=kgYGMVDHQHs)\nand `Adam Dymitruk` for hosting the meetup.\n\n---\nCreated with :heart: by [Fraktalio](https://fraktalio.com/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffraktalio%2Ffmodel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffraktalio%2Ffmodel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffraktalio%2Ffmodel/lists"}