{"id":21948145,"url":"https://github.com/evolution-gaming/patch","last_synced_at":"2025-04-23T00:16:51.948Z","repository":{"id":74384499,"uuid":"315139671","full_name":"evolution-gaming/patch","owner":"evolution-gaming","description":"Patch is a building block for event-sourcing","archived":false,"fork":false,"pushed_at":"2024-07-18T09:54:07.000Z","size":61,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-23T00:16:47.118Z","etag":null,"topics":["event-sourcing","scala"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/evolution-gaming.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2020-11-22T21:53:53.000Z","updated_at":"2024-07-18T09:54:10.000Z","dependencies_parsed_at":"2023-12-15T17:27:41.282Z","dependency_job_id":"369c1a70-1b6b-4487-a952-8b585a43402e","html_url":"https://github.com/evolution-gaming/patch","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fpatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fpatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fpatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evolution-gaming%2Fpatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evolution-gaming","download_url":"https://codeload.github.com/evolution-gaming/patch/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250343960,"owners_count":21415042,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["event-sourcing","scala"],"created_at":"2024-11-29T05:12:11.329Z","updated_at":"2025-04-23T00:16:51.928Z","avatar_url":"https://github.com/evolution-gaming.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Patch\n[![Build Status](https://github.com/evolution-gaming/patch/workflows/CI/badge.svg)](https://github.com/evolution-gaming/patch/actions?query=workflow%3ACI)\n[![Coverage Status](https://coveralls.io/repos/github/evolution-gaming/patch/badge.svg?branch=master)](https://coveralls.io/github/evolution-gaming/patch?branch=master)\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/f9d2e05d108c4c259680b4b5f7753001)](https://www.codacy.com/gh/evolution-gaming/patch/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=evolution-gaming/patch\u0026amp;utm_campaign=Badge_Grade)\n[![Version](https://img.shields.io/badge/version-click-blue)](https://evolution.jfrog.io/artifactory/api/search/latestVersion?g=com.evolution\u0026a=patch_2.13\u0026repos=public)\n[![License: MIT](https://img.shields.io/badge/license-MIT-yellowgreen.svg)](https://opensource.org/licenses/MIT)\n\n`Patch` is a monadic data structure - a building block for [event-sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) application\n\n# Motivation\n\nEvent Sourcing is one of the latest advances in software architecture design. It is meant to replace CRUD approach in the areas\nwhere speed of persisting data, scalability and near real-time reactions are essential. The approach does not come without its own\nflaws though. It is a complicated task to build a well working Event Sourcing based application and in a lot of cases it is\nconsidered to be an unnecessary overkill.\n\nThe modern frameworks such as [Akka Persistence](https://doc.akka.io/docs/akka/current/typed/persistence.html) provide\nthe user-friendly API and storage implementations, and, therefore, make the task of building Event Sourced software, easier.\n\nThe problem is that, however, as you are building the larger application, you realize quickly that these frameworks have\na single important flaw: neither [EventSourcedBehavior](https://doc.akka.io/api/akka/2.8/akka/persistence/typed/scaladsl/EventSourcedBehavior.html),\nnor [PersistentActor](https://doc.akka.io/api/akka/2.8/akka/persistence/PersistentActor.html) compose, which is a real shame,\ngiven that Event Sourcing could be really useful for the large and complicated business domains, where high performance and\navailability is also essential.\n\nThis small (~500 LOC) library is meant to make an improvement in this area. The developer will, finally, be able to split\nthe events sourcing code into smaller pieces, write unit tests for each interesting piece, and then compose the parts into\na larger application, all in a type safe and consistent way.\n\n# Maturity\n\nThe code is proven to be stable and, currently, powering the whole set of mission critical applications. Saying that, the\nlibrary itself is provided on AS IS basis, without any promises or guarantees.\n\n# Implementation\n\nIt is a specialized version of [IndexedReaderWriterStateT](https://github.com/typelevel/cats/blob/main/core/src/main/scala/cats/data/IndexedReaderWriterStateT.scala#L34)\n\nIn case you come here and have no clue of what the weird word above means, you can start your learning journey in the following order:\n1. [cats exercises](https://www.scala-exercises.org/cats)\n2. [Reader](https://eed3si9n.com/learning-scalaz/Reader.html)\n3. [Writer](https://typelevel.org/cats/datatypes/writer.html)\n4. [State](https://typelevel.org/cats/datatypes/state.html)\n5. [Monad Transformer](https://eed3si9n.com/learning-scalaz/Monad+transformers.html#:~:text=A%20monad%20transformer%20is%20similar,behaviour%20of%20an%20underlying%20monad.)\n6. [WriterT](https://typelevel.org/cats/datatypes/writert.html)\n7. [StateT](https://typelevel.org/cats/datatypes/statet.html)\n8. If you have gone so far by now and do understand basic principles of event sourcing, you should have no questions about `Patch` :)\n\n# Example\n\nHere is a short example of how this works\n\n```scala\n    final case class Event(value: Int)\n\n    final case class State(value: Int)\n\n    // we need this to make compiler happy\n    implicit val maker = Patch.Maker[IO, State, Event]\n\n    // how to apply newly issued event to state\n    implicit val change = Patch.Change[State, Event] { (state, seqNr, event) =\u003e\n      state\n        .copy(value = state.value + event.value)\n        .pure[IO]\n    }\n\n    import com.evolution.patch.Patch.implicits._ // adds nice syntax\n\n    def enabled: IO[Boolean] = IO.pure(true)\n\n    def log(msg: String): IO[Unit] = IO.unit\n\n    val patch: Patch[IO, State, Event, IO[Unit], Either[String, State]] = for {\n      enabled \u003c- enabled.patchLift // you might need to execute effect in order to decide on how to proceed\n      result  \u003c- if (enabled) {\n        for {\n          before \u003c- Patch.state\n          _      \u003c- Event(+1).patchEvent // event to be appended\n          after  \u003c- Patch.state // state after event is applied\n          seqNr  \u003c- Patch.seqNr // seqNr at this point\n          _      \u003c- log(s\"state changed from $before to $after($seqNr)\").patchEffect\n        } yield {\n          after.asRight[String]\n        }\n      } else {\n        // you might not produce any events and just have side effect\n        log(\"state remains the same\")\n          .patchEffect\n          .as(\"disabled\".asLeft[State])\n      }\n    } yield result\n\n    // now we can run our `Patch` by passing initial `state` and `seqNr`\n    val result = patch.run(State(0), SeqNr.Min)\n\n    // here we have resulting state, list of all events, composition of side effects to be executed in case events are successfully persisted\n    result // IO(Patch.Result(State(1), List(Event(1)), IO.unit, State(1).asRight))\n```\n\n## Setup\n\nin [`build.sbt`](https://www.scala-sbt.org/1.x/docs/Basic-Def.html#What+is+a+build+definition%3F)\n```scala\naddSbtPlugin(\"com.evolution\" % \"sbt-artifactory-plugin\" % \"0.0.2\")\n\nlibraryDependencies += \"com.evolution\" %% \"patch\" % \"0.1.0\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevolution-gaming%2Fpatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevolution-gaming%2Fpatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevolution-gaming%2Fpatch/lists"}