/patch

Patch is a building block for event-sourcing

Primary LanguageScalaMIT LicenseMIT

Patch

Build Status Coverage Status Codacy Badge Version License: MIT

Patch is a monadic data structure - a building block for event-sourcing application

Motivation

Event Sourcing is one of the latest advances in software architecture design. It is meant to replace CRUD approach in the areas where speed of persisting data, scalability and near real-time reactions are essential. The approach does not come without its own flaws though. It is a complicated task to build a well working Event Sourcing based application and in a lot of cases it is considered to be an unnecessary overkill.

The modern frameworks such as Akka Persistence provide the user-friendly API and storage implementations, and, therefore, make the task of building Event Sourced software, easier.

The problem is that, however, as you are building the larger application, you realize quickly that these frameworks have a single important flaw: neither EventSourcedBehavior, nor PersistentActor compose, which is a real shame, given that Event Sourcing could be really useful for the large and complicated business domains, where high performance and availability is also essential.

This small (~500 LOC) library is meant to make an improvement in this area. The developer will, finally, be able to split the events sourcing code into smaller pieces, write unit tests for each interesting piece, and then compose the parts into a larger application, all in a type safe and consistent way.

Maturity

The code is proven to be stable and, currently, powering the whole set of mission critical applications. Saying that, the library itself is provided on AS IS basis, without any promises or guarantees.

Implementation

It is a specialized version of IndexedReaderWriterStateT

In 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:

  1. cats exercises
  2. Reader
  3. Writer
  4. State
  5. Monad Transformer
  6. WriterT
  7. StateT
  8. If you have gone so far by now and do understand basic principles of event sourcing, you should have no questions about Patch :)

Example

Here is a short example of how this works

    final case class Event(value: Int)

    final case class State(value: Int)

    // we need this to make compiler happy
    implicit val maker = Patch.Maker[IO, State, Event]

    // how to apply newly issued event to state
    implicit val change = Patch.Change[State, Event] { (state, seqNr, event) =>
      state
        .copy(value = state.value + event.value)
        .pure[IO]
    }

    import com.evolution.patch.Patch.implicits._ // adds nice syntax

    def enabled: IO[Boolean] = IO.pure(true)

    def log(msg: String): IO[Unit] = IO.unit

    val patch: Patch[IO, State, Event, IO[Unit], Either[String, State]] = for {
      enabled <- enabled.patchLift // you might need to execute effect in order to decide on how to proceed
      result  <- if (enabled) {
        for {
          before <- Patch.state
          _      <- Event(+1).patchEvent // event to be appended
          after  <- Patch.state // state after event is applied
          seqNr  <- Patch.seqNr // seqNr at this point
          _      <- log(s"state changed from $before to $after($seqNr)").patchEffect
        } yield {
          after.asRight[String]
        }
      } else {
        // you might not produce any events and just have side effect
        log("state remains the same")
          .patchEffect
          .as("disabled".asLeft[State])
      }
    } yield result

    // now we can run our `Patch` by passing initial `state` and `seqNr`
    val result = patch.run(State(0), SeqNr.Min)

    // here we have resulting state, list of all events, composition of side effects to be executed in case events are successfully persisted
    result // IO(Patch.Result(State(1), List(Event(1)), IO.unit, State(1).asRight))

Setup

in build.sbt

addSbtPlugin("com.evolution" % "sbt-artifactory-plugin" % "0.0.2")

libraryDependencies += "com.evolution" %% "patch" % "0.1.0"