{"id":32978001,"url":"https://github.com/parapet-io/parapet","last_synced_at":"2026-01-14T02:35:28.769Z","repository":{"id":43224432,"uuid":"199203940","full_name":"parapet-io/parapet","owner":"parapet-io","description":"A purely functional library to build distributed and event-driven systems","archived":false,"fork":false,"pushed_at":"2023-04-30T02:37:57.000Z","size":3602,"stargazers_count":136,"open_issues_count":8,"forks_count":7,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-05-02T10:18:30.531Z","etag":null,"topics":["distributed-computing","distributed-systems","effects","event-driven","functional-programming","parallel-computing","parapet","scala"],"latest_commit_sha":null,"homepage":"http://parapet.io/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/parapet-io.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}},"created_at":"2019-07-27T19:08:31.000Z","updated_at":"2023-12-06T03:14:40.000Z","dependencies_parsed_at":"2022-07-25T00:02:15.562Z","dependency_job_id":"706d1486-be05-4aa6-8a87-7c40c6bc9a5e","html_url":"https://github.com/parapet-io/parapet","commit_stats":{"total_commits":288,"total_committers":6,"mean_commits":48.0,"dds":"0.16666666666666663","last_synced_commit":"2b130ec8b0ee45de5a47da0cf64b5fd3508e4f09"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/parapet-io/parapet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parapet-io%2Fparapet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parapet-io%2Fparapet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parapet-io%2Fparapet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parapet-io%2Fparapet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/parapet-io","download_url":"https://codeload.github.com/parapet-io/parapet/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parapet-io%2Fparapet/sbom","scorecard":{"id":720286,"data":{"date":"2025-08-11","repo":{"name":"github.com/parapet-io/parapet","commit":"2b130ec8b0ee45de5a47da0cf64b5fd3508e4f09"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 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":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/scala.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":"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":0,"reason":"Found 0/17 approved changesets -- score normalized to 0","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":"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":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"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":"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":"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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/parapet-io/parapet/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/parapet-io/parapet/scala.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned 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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"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":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'","Warn: branch protection not enabled for branch 'release/0.0.1-RC2'"],"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 18 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-22T11:05:01.831Z","repository_id":43224432,"created_at":"2025-08-22T11:05:01.836Z","updated_at":"2025-08-22T11:05:01.836Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["distributed-computing","distributed-systems","effects","event-driven","functional-programming","parallel-computing","parapet","scala"],"created_at":"2025-11-13T06:00:36.408Z","updated_at":"2026-01-14T02:35:28.760Z","avatar_url":"https://github.com/parapet-io.png","language":"Scala","funding_links":["https://www.patreon.com/bePatron?u=6107081"],"categories":["微服务库","Distributed Systems"],"sub_categories":["Spring Cloud框架"],"readme":"\n\n# Parapet - purely functional library to develop distributed and event-driven systems\n\n[![Build Status](https://travis-ci.org/parapet-io/parapet.svg?branch=master)](https://travis-ci.org/parapet-io/parapet)\n[![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/io-parapet/parapet)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.parapet/core_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.parapet/core_2.12)\n\n## Motivation\n\nIt's not a secret that writing distributed systems is a challenging task that can be logically broken into two main aspects: implementing distributed algorithms and running them. Parapet plays the role of execution framework for distributed algorithms - it can be viewed as an intermediate layer between a low-level effect library and high-level operations exposed in the form of DSL. Distributed engineers who mainly focused on designing and implementing distributed algorithms don't need to be worried about low-level abstractions such as `IO` or have a piece of deep knowledge in certain computer science subjects, for instance, _Concurrency_. All they need to know is what _properties_ the library satisfies and what _guarantees_ it provides. On the other hand, engineers who are specializing in writing low-level libraries can concentrate on implementing core abstractions such as `IO` or `Task`, working on performance optimizations and implementing new features. \nParapet is the modular library where almost any component can be replaced with a custom implementation. \n\nDistributed engineers unite!\n\n\n```\nSure, here are some high-level comparisons between Parapet and Akka:\n\n1. Architecture: Parapet is designed to be a minimalistic, purely functional library that provides only the core abstractions needed to build concurrent and distributed applications, whereas Akka is a much more comprehensive toolkit that includes many more features and abstractions, such as actors, streams, and distributed data.\n\n2. Language: Parapet is primarily designed for Scala, although it also supports other JVM languages such as Java and Kotlin. Akka, on the other hand, supports both Scala and Java, making it more accessible to developers who are more comfortable with Java.\n\n3. Performance: Parapet is built on top of the Cats Effect library, which provides high-performance abstractions for asynchronous and concurrent programming in Scala. Akka is also designed for high performance, but its performance characteristics may depend on which features are being used and how they are configured.\n\n4. Complexity: Because Parapet is a smaller and more focused library, it can be easier to reason about and use than Akka, which has a much larger API surface area and many more features.\n\n5. Community: Akka has a larger and more established community, with more resources and examples available online. Parapet is a newer library with a smaller community, although it has been gaining popularity in recent years.\n\nUltimately, the choice between Parapet and Akka depends on your specific needs and preferences. If you are looking for a more lightweight and functional library for building concurrent and distributed applications in Scala, Parapet may be a good choice. If you need a more comprehensive toolkit with a larger community and more resources, Akka may be a better fit.\n```\n\n**NOTE** - Github documentation may be out of date. It's preferable to use the [official documentation](http://parapet.io/).\n\n## Contents\n\n* [Key Features](#key-features)\n* [DSL](#dsl)\n* [Process](#process)\n* [Channel](#channel)\n* [Error Handling and DeadLetterProcess](#error-handling-and-deadletterprocess)\n* [EventLog](#eventlog)\n* [Configuration](#configuration)\n* [Correctness Properties](#correctness-properties)\n* [Distributed Algorithms in Parapet](#distributed-algorithms-in-parapet)\n* [Performance Analysis](#performance-analysis)\n* [Contribution](#contribution)\n\n## Key Features\n\n* Purely functional library written in scala using Tagless-Final Style and Free Monads; thoughtfully designed for people who prefer functional style over imperative\n* Modular - almost any component can be replaced with a custom implementation\n* DSL provides a set of operations sufficient to write distributed algorithms\n* Lightweight and Performant. The library utilizes resources (CPU and Memory) smartly, the code is optimized to reduce CPU consumption when your application in idle state\n* Built-in support for the following effect libraries: [Cats Effect](https://typelevel.org/cats-effect/), [Monix](https://monix.io/), and [Scalaz ZIO](https://zio.dev/). The library can be extended to support other effect libraries\n\n## Getting started\n\nThe first thing you need to do is to add two dependencies into your project: `parapet-core` and `interop-{effect_library}` for a specific effect library. You can find the latest version in maven central.\n\n```scala\nlibraryDependencies += \"io.parapet\" %% \"core\" % \"0.0.1-RC1\"\n```\n\nFor Cats Effect add `libraryDependencies += \"io.parapet\" %% \"interop-cats\" % \"0.0.1-RC1\"`\n\nFor Monix add `libraryDependencies += \"io.parapet\" %% \"interop-monix\" % \"0.0.1-RC1\"`\n\nFor Scalaz ZIO add `libraryDependencies += \"io.parapet\" %% \"interop-scalaz-zio\" % \"0.0.1-RC1\"`\n\nOnce you added the library, you can start writing your first program.  However, it's worth taking a few minutes and getting familiar with two main approaches to write processes: generic and effect specific. I'll describe both in a minute. For those who aren't familiar with effect systems like Cats Effect, I'd strongly recommend you to read some articles about IO monad. Fortunately, you don't need to be an expert in Cats Effect to use Parapet.\n\nThe first approach we'll consider is Generic. It's recommended to stick to this style when writing processes. Let's develop a simple printer process that will print users requests to the system output.\n\n```scala\nimport io.parapet.core.{Event, Process}\n\nclass Printer[F[_]] extends Process[F] {\n\n  import Printer._ //  import Printer API\n\n  import dsl._ // import DSL operations\n\n  override def handle: Receive = {\n    case Print(data) =\u003e eval(println(data))\n  }\n}\n\nobject Printer {\n\n  case class Print(data: Any) extends Event\n\n}\n```\n\nLet's walk through this code. You start writing your processes by extending `Process` trait and parameterizing it with an effect type. In this example, we left so-called hole `F[_]` in our `Printer` type which can be any type constructor with a single argument, e.g. `F[_]` is a generic type constructor, cats effect `IO` is a specific type constructor and `IO[Unit]` is a concrete type. Starting from this moment, it should become clear what it means for a process to be generic. Simply speaking, it means that a process doesn't depend on any specific effect type e.g. `IO`. Thus we can claim that our `Printer` process is surely generic. The next step is to define a process API or contract that defines a set of events that it can send and receive. Process contract is an important part of any process specification that should be taken seriously. API defines a protocol that other processes will use to communicate with your process. Please remember that it's a very important aspect of any process definition and take it seriously. The next step would be importing `DSL`, Parapet DSL is a small set of operations that we will consider in detail in the next chapters. In this example, we need only `eval` operator that  suspends a side effect in `F`, in our Printer process we suspend `println` effectful computation. Finally, every process should override `handle` function defined in `Process` trait. `handle` function is a partial function that matches input events and produces an executable `flows`. If you ever tried Akka framework you may find this approach familiar (for the curious, `Receive` is simply a type alias for `PartialFunction[Event, DslF[F, Unit]]`). In our Printer process, we match on `Print` event using a well known pattern-matching feature in Scala language. If you are new in functional programming, I'd strongly recommend to read about pattern-matching - it's a very powerful instrument. \nThat's it. We have considered every important aspect of our `Printer` process. Let's move forward and write a simple client process that will talk to our `Printer`.\n\n\n```scala\nimport io.parapet.core.Event.Start\nimport io.parapet.core.{Process, ProcessRef}\nimport io.parapet.examples.Printer._ // import Printer API\n\nclass PrinterClient[F[_]](printer: ProcessRef) extends Process[F] {\n  override def handle: Receive = {\n    // Start is a lifecycle event that gets delivered when a process started\n    case Start =\u003e Print(\"hello world\") ~\u003e printer\n  }\n}\n```\n\nAs you already might have noticed, we are repeating the same steps we made when were writing our `Printer` process: \n* Create a new Process with a  _hole_ `F[_]` in its type definition\n* Extend `io.parapet.core.Process` trait and parametrizing it with generic effect type `F`\n* Implement `handle` partial function\n\nLet's consider some new types and operators we have used to write our client: `ProcessRef`, `Start` lifecycle event and `~\u003e` (send) infix operator. Let's start from  `ProcessRef`. `ProcessRef` is a unique process identifier (UUID by default). It represents a process address in Parapet system and *must* be unique - it's recommended to use `ProcessRef` instead of a `Process` object directly unless you are sure you want otherwise. It's not prohibited to use `Process` object directly, however using a process reference may be useful in some scenarios. Let's consider one such case. Imagine we want to dynamically change the current `Printer` process in our client so that it will store data in a file on disk instead of printing it to the console. We can add a new event `ChangePrinter`:\n\n```scala\ncase class ChangePrinter(printer: ProcessRef) extends Event\n```\n\nThen our client will look like this:\n\n```scala\nclass PrinterClient[F[_]](private var printer: ProcessRef) extends Process[F] {\n\n  import PrinterClient._\n  import dsl._\n\n\n  override def handle: Receive = {\n    case Start =\u003e Print(\"hello world\") ~\u003e printer\n    case ChangePrinter(newPrinter) =\u003e eval(printer = newPrinter)\n  }\n}\n\nobject PrinterClient {\n\n  case class ChangePrinter(printer: ProcessRef) extends Event\n\n}\n```\n\nThis design cannot be achieved when using direct processes b/c it's not possible to send `Process`  objects,  processes are not serializable in general. One more thing, you can override a `Process#ref` field, only make sure it's unique otherwise Parapet system will return an error during the startup. \n\nOk, we are almost done! There are a few more things left we need to cover: `Start` lifecycle event and `~\u003e` operator and there is nothing special about these two. Parapet has two lifecycle events: \n\n* `io.parapet.core.Event.Start` is sent to a process once it's created in Parapet system\n* `io.parapet.core.Event.Stop` is sent to a process when an application is interrupted with `Ctrl-C` or when some other process sent `Stop` or `Kill` event to that process. The main difference between `Stop` and `Kill` is that in the former case a process can finish processing all pending events before it will receive `Stop` event, whereas `Kill` will interrupt a process and then deliver `Stop` event, all pending events will be discarded. If you familiar with Java `ExecutorService` then you can think of `Stop` as `shutdown` and `Kill` as `shutdownNow`. \n\nFinally `~\u003e` is the most frequently used operator that is defined for any type that extends `io.parapet.core.Event` trait. `~\u003e` is just a symbolic name for `send(event, processRef)` operator.\n\nBy this moment we have two processes: `Printer` and `PrinterClient`, nice! But wait, we need to run them somehow, right?\nFortunately, it's extremely easy to do so, all we need is to create `PrinterApp` object which represents our application and extend it from `CatsApp` abstract class. `CatsApp` extends ParApp by specifying concrete effect type `IO`:\n\n```scala\nabstract class CatsApp extends ParApp[IO]\n```\n\n`CatsApp` is provided by the library.\n\n\n```scala\nimport cats.effect.IO\nimport io.parapet.CatsApp\nimport io.parapet.core.Process\n\nobject PrinterApp extends CatsApp {\n  override def processes: IO[Seq[Process[IO]]] = IO {\n    val printer = new Printer[IO]\n    val printerClient = new PrinterClient[IO](printer.ref)\n    Seq(printer, printerClient)\n  }\n}\n```\n\nThis is Cats Effect specific application, meaning it uses Cats IO type under the hood. If you run your program you should see `hello world` printed to the console. Also notice that we are using concrete effect type IO to fill the hole in our `Printer` type, e.g.: `new Printer[IO]` in practice it can be any other effect type like `Task`, although it requires some extra work in the library.\nIn our example, we created `PrinterClient` which does nothing but sending `Print` event at the startup. In my opinion, it doesn't deserve to be a standalone process, would be better if we create a process in place:\n\n```scala\nobject PrinterApp extends CatsApp {\n  override def processes: IO[Seq[Process[IO]]] = IO {\n    val printer = new Printer[IO]\n    val start = Process[IO](_ =\u003e {\n      case Start =\u003e Printer.Print(\"hello world\") ~\u003e printer.ref\n    })\n    Seq(start, printer)\n  }\n}\n```\nAlthough it's a matter of taste, there is no hard rule.\n\n## DSL\n\nThis chapter describes each DSL operator in details. Let's get started.\n\n**Contents** \n\n* [unit](#unit)\n* [flow](#flow)\n* [send](#send)\n* [forward](#forward)\n* [par](#par)\n* [delay](#delay)\n* [withSender](#withSender)\n* [fork](#fork)\n* [register](#register)\n* [race](#race)\n* [suspend](#suspend)\n* [suspendWith](#suspendWith)\n* [eval](#eval)\n* [evalWith](#evalWith)\n\n### unit\n\n`unit` -  semantically this operator is equivalent with `Monad.unit` and obeys the same laws. Having said that the following expressions are equivalent:\n\n```scala\nevent ~\u003e process \u003c-\u003e unit ++ event ~\u003e process\nevent ~\u003e process \u003c-\u003e event ~\u003e process ++ unit\n```\n\nThis operator can be used in `fold` operator to combine multiple flows. Example:\n\n```scala\nprocesses.map(event ~\u003e _).fold(unit)(_ ++ _)\n```\n\nIt also can be used to represent an empty flow:\n\n```scala\n{\n  case Start =\u003e unit // do nothing\n  case Stop =\u003e unit // do nothing\n}\n```\n\n### flow\n\n`flow` - suspends the thunk that produces flow. Semantically this operator is equivalent with `suspend` for effects however it's strongly not recommended to perform any side effects within `flow`.\n\nNot recommended:\n\n```scala\ndef print(str: String) = flow {\n  println(str)\n  unit\n}\n```\n\nRecommended:\n\n```scala\ndef print(str: String) = flow {\n  eval(println(str))\n}\n```\n\n`flow` may be useful to implement recursive flows. Example:\n\n```scala\ndef times[F[_]](n: Int) = {\n  def step(remaining: Int): DslF[F, Unit] = flow {\n    if (remaining == 0) unit\n    else eval(print(remaining)) ++ step(remaining - 1)\n  }\n\n  step(n)\n}\n```\n\nIf you try to remove `flow` you will get `StackOverflowError`\n\nAnother useful application is using lazy values inside `flow`. Example:\n\n```scala\n  lazy val lazyValue: String = {\n    println(\"evaluated\")\n    \"hello\"\n  }\n\n  val useLazyValue = flow {\n    val tmp = lazyValue + \" world\"\n    eval(println(tmp))\n  }\n```\n\n### send\n\n`send` - sends an event to one or more receivers. Event will be delivered to all receivers in the specified order.\nParapet provides a symbolic name for this operator `~\u003e` although in the current implementation it doesn't allow to send an event to multiple receivers. It will be added in the future releases.\n\nExamples:\n\n```scala\nsend(Ping, processA, processB, processC)\n```\n\n`Ping` event will be sent to the `processA` then `processB` and finaly `processC`. It's not guaranteed that `processA` will receive `Ping` event before `processC` as it depends on it's processing speed and current workload.\n\n\n```scala\nPing ~\u003e processA\n```\n\nNot supported: \n\n```scala\nPing ~\u003e Seq(processA, processB, processC)\n```\n\nPossible workaround:\n\n```scala\n Seq(processA, processB, processC).map(Ping ~\u003e _).fold(unit)(_ ++ _)\n```\n\nSend multiple events to a process:\n\n```scala\nSeq(e1, e2, e3) ~\u003e process\n```\n\n### forward\n\n`forward` - sends an event to the receiver using original sender reference. This may be useful for implementing a proxy process.\nExample:\n\n```scala\nval server = Process[IO](_ =\u003e {\n  case Request(body) =\u003e withSender(sender =\u003e eval(println(s\"$sender-$body\")))\n})\n\nval proxy = Process[IO](_ =\u003e {\n  case Request(body) =\u003e forward(Request(s\"proxy-$body\"), server.ref)\n})\n\nval client = Process.builder[IO](_ =\u003e {\n    case Start =\u003e Request(\"ping\") ~\u003e proxy\n  }).ref(ProcessRef(\"client\")).build\n```\n\nThe code above will print: `client-proxy-ping`\n\n### par\n\n`par` - executes operations from the given flow in parallel. Example:\n\n```scala\npar(eval(print(1)) ++ eval(print(2))) \n```\n\nPossible outputs: `12 or 21`\n\n### delay\n\n`delay` - delays every operation in the given flow for the given duration.\nFor sequential flows the flowing expressions are semantically equivalent:\n\n```scala\n delay(duration, x~\u003ep ++ y~\u003ep) \u003c-\u003e delay(duration, x~\u003ep) ++ delay(duration, y~\u003ep)\n delay(duration, x~\u003ep ++ y~\u003ep) \u003c-\u003e delay(duration) ++ x~\u003ep ++ delay(duration) ++ y~\u003ep\n```\n\nFor parallel flows:\n\n```scala\ndelay(duration, par(x~\u003ep ++ y~\u003ep)) \u003c-\u003e delay(duration) ++ par(x~\u003ep ++ y~\u003ep)\n```\n\nNote: since the following flow will be executed in parallel the second operation won't be delayed:\n\n```scala\npar(delay(duration) ++ eval(print(1)))\n```\n\ninstead, use:\n\n```scala\npar(delay(duration, eval(print(1))))\n```\n\n\n### withSender\n\n`withSender` - accepts a callback function that takes a sender reference and produces a new flow. Example:\n\n```scala\nval server = Process[IO](_ =\u003e {\n  case Request(data) =\u003e withSender(sender =\u003e eval(print(s\"$sender says $data\")))\n})\n\nval client = Process.builder[IO](_ =\u003e {\n  case Start =\u003e Request(\"hello\") ~\u003e server\n}).ref(ProcessRef(\"client\")).build\n```\n\nThe code above will print: `client says hello`\n\n### fork\n\n`fork` - does what exactly the name says, executes the given flow concurrently. Example:\n\n```scala\nval process = Process[IO](_ =\u003e {\n  case Start =\u003e fork(eval(print(1))) ++ fork(eval(print(2)))\n})\n```\n\nPossible outputs: `12 or 21`\n\n### register\n\n`register` - registers a child process in the Parapet context. It's guaranteed that a child process will receive `Stop` event before its parent. Example: \n\n```scala\n  val server = Process[IO](ref =\u003e {\n    case Start =\u003e register(ref, Process[IO](_ =\u003e {\n      case Stop =\u003e eval(println(\"stop worker\"))\n    }))\n    case Stop =\u003e eval(println(\"stop server\"))\n  })\n```\nThe code above will print: \n\n```\nstop worker\nstop server\n```\n\n### race\n\n`race` - runs two flows concurrently. The loser of the race is canceled. \n\nExample:\n\n```scala\n  val forever = eval(while (true) {})\n\n  val process: Process[IO] = Process[IO](_ =\u003e {\n    case Start =\u003e race(forever, eval(println(\"winner\")))\n  })\n```\n\nOutput: `winner`\n\n### suspend\n\n`suspend` - adds an effect which produces `F` to the current flow. Example:\n\n```scala\nsuspend(IO(print(\"hello world\")))\n```\n\nOutput: `hello world`\n\nNot recommended:\n\n```scala\nsuspend {\n println(\"hello world\")\n IO.unit\n}\n```\n\n### suspendWith\n\n`suspendWith` - suspends an effect which produces `F` and then feeds that into a function that takes a normal value and returns a new flow. All operations from produced flow added to the current flow. Example:\n\n```scala\nsuspend(IO.pure(1))) { i =\u003e eval(print(i)) } \n```\n\nOutput: `1`\n\n### eval\n\n`eval` - suspends a side effect in `F` and then adds that to the current flow. Example:\n\n```scala\neval(println(\"hello world\"))\n```\n\nOutput: `hello world`\n\n### evalWith\n\n`evalWith` - Suspends a side effect in `F` and then feeds that into a function that takes a normal value and returns a new flow. All operations from a produced flow will be added to the current flow. Example:\n\n```scala\nevalWith(\"hello world\")(a =\u003e eval(println(a)))\n```\n\nOutput: `hello world`\n\n## Process\n\n`Process` is a key abstraction in Parapet, any application must have a least one process. If you try to run an application w/o processes you will get an error saying that at least one process required. This section covers some useful features that we haven't seen yet, below you will find a shortlist of features:\n\n* Predefined processes and reserved references\n* Switching process behavior\n* Direct process call\n* Process combinators: `and` and `or`\n* Testing your processes\n* Basic patterns and tips: implementing timeouts, designing API\n\n### Predefined processes and reserved references\n\nParapet has some reserved process references, e.g.: `KernelRef(parapet-kernel)`, `SystemRef(parapet-system)`, `DeadLetterRef(parapet-deadletter)`, `UndefinedRef(parapet-undefined)`. The general rule is that any reference that starts with `parapet-` prefix can be used by the platform code for any purpose.\nParapet has a `SystemProcess` that cannot be overridden by users. `SystemProcess` is a starting point, i.e. it's created before any other process. Lifecycle event `Start` is sent by `SystemProcess`. Any event sent to the `SystemProcess` will be ignored and dropped. Don't try to send any events to `SystemProcess` b/c it can lead to unpredictable errors.\n\n`DeadLetterProcess` is another process that is created by default, although it can be overridden,  for more details check  `DeadLetterProcess` section under `Event Handling` \n\n### Switching process behavior\n\nSometimes it might be useful to dynamically switch a process behavior, e.g.: from `uninitialized` to `ready` state. Thankfully `Process` provides `switch` method that does exactly that.\n\nExample:\n\nLazy server:\n\n```scala\n  // for some effect `F[_]`\n  val server = new Process[F] {\n\n    val init = eval(println(\"acquire resources: create socket and etc.\"))\n\n    def ready: Receive = {\n      case Request(data) =\u003e withSender(Success(data) ~\u003e _)\n      case Stop =\u003e eval(println(\"release resources: close socket and etc.\"))\n    }\n\n    def uninitialized: Receive = {\n      case Start =\u003e unit // ignore Start event, wait for Init\n      case Stop =\u003e unit // process is not initialized, do nothing\n      case Init =\u003e init ++ switch(ready)\n      case _ =\u003e withSender(Failure(\"process is not initialized\", ErrorCodes.ProcessUninitialized) ~\u003e _)\n    }\n\n    override def handle: Receive = uninitialized\n  }\n\n  // API\n\n  object Init extends Event\n\n  case class Request(data: Any) extends Event\n\n  sealed trait Response extends Event\n  case class Success(data: Any) extends Event\n  case class Failure(data: Any, errorCode: Int) extends Event\n\n  object ErrorCodes {\n    val ProcessUninitialized  = 0\n  }\n```\n\nA client which sends `Request` event w/o sending `Init`:\n\n```scala\n  val impatientClient = Process[F](_ =\u003e {\n    case Start =\u003e Request(\"PING\") ~\u003e server\n    case Success(_) =\u003e eval(println(\"that is not going to happen\"))\n    case f:Failure =\u003e eval(println(f))\n  })\n```\n\nThe code above will print: `Failure(process is not initialized,0)`\n\nA client which sends `Init` first and then `Request`:\n\n```scala\n  val humbleClient = Process[F](_ =\u003e {\n    case Start =\u003e Seq(Init, Request(\"PING\")) ~\u003e server\n    case Success(data) =\u003e eval(println(s\"client receive response from server: $data\"))\n    case _:Failure =\u003e eval(println(\"that is not going to happen\"))\n  })\n```\n\n\nThe code above will print: \n\n```\nacquire resources: create socket and etc.\nclient receive response from server: PING\nrelease resources: close socket and etc.\n```\n\n`switch` is **NOT** an atomic operation, avoid using `switch` in concurrent flows because it may result in an error or lead to unpredictable behavior.\n\nBad:\n\n```scala\n  val process = new Process[F] {\n    def ready: Receive = _\n\n    override def handle: Receive = {\n      case Init =\u003e fork(switch(ready)) // bad, may lead to unpredictable behaviour\n    }\n  }\n\n```\n\nIf you need to switch behavior from a concurrent flow just send an event e.g. `Swith(State.Ready)` to itself. Process will *eventually* switch its behavior:\n\n```scala\n  val process = new Process[F] {\n    def ready: Receive = _\n\n    override def handle: Receive = {\n      case Init =\u003e fork {\n        eval(println(\"do some work in parallel\"))\n        Switch(Ready) ~\u003e ref // notify the process that it's time to switch it's behaviour\n      }\n      case Switch(Ready) =\u003e  switch(ready)\n    }\n  }\n\n  sealed trait State\n  object Ready extends State\n\n  case class Switch(next: State) extends Event\n```\n\n\n### Direct process call\n\nSometimes it may be useful to call a process directly. Especially it's a common case for short living processes. For instance, you may want to create a process, call it and then abandon, garbage collector will do its job. However, if you try to send an event to a process that doesn't exist in the system you will receive `Failure` event with `UnknownProcessException`. This is where `direct  call` comes to rescue.\n\nExample:\n\n```scala\n  // API\n  case class Sum(a: Int, b: Int) extends Event\n  case class Result(value: Int) extends Event\n\n  class Calculator[F[_]] extends Process[F] {\n    override def handle: Receive = {\n      case Sum(a, b) =\u003e withSender(Result(a + b - 1) ~\u003e _) // yes, very poor calculator\n    }\n  }\n\n  val student = Process[F](ref =\u003e {\n    case Start =\u003e new Calculator().apply(ref, Sum(2, 2))\n    case Result(value) =\u003e eval(println(s\"2 + 2 = $value\"))\n  })\n\n```\n\nOutput: `2 + 2 = 3`\n\nNote that `apply` method doesn't return a normal value rather it returns a *program* which will be executed as normal flow. \nIn other words the following expressions are equivalent:\n\n```\nSum(2, 2) ~\u003e calculator \u003c-\u003e new Calculator().apply(ref, Sum(2, 2)) // where ref belongs to the same process in both cases\n```\n\n### Process combinators\n\nProcesses can be combined using two logical operators: `or` and `and`.\n\n`and` - combines two processes by producing a new process with `ref` of the first process; combines flows iff 'handle' function is defined for the given event in both processes. Sends an error to the sender if either of two processes isn't defined for the given event.\n\nExample:\n\n```scala\nimport cats.effect.IO\nimport io.parapet.CatsApp\nimport io.parapet.core.Event.Start\nimport io.parapet.core.{Event, Process}\n\nobject Example extends CatsApp {\n\n  import dsl._\n\n  case class Print(data: Any) extends Event\n\n  override def processes: IO[Seq[Process[IO]]] =\n    for {\n      printerA \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Print(data) =\u003e eval(println(s\"printerA: $data\"))\n      }))\n      printerB \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Print(data) =\u003e eval(println(s\"printerB: $data\"))\n      }))\n\n      client \u003c- IO.pure(Process[IO](ref =\u003e {\n        case Start =\u003e printerA.and(printerB).apply(ref, Print(\"test\"))\n      }))\n\n    } yield Seq(printerA, printerB, client)\n\n}\n```\n\nIf you want to register a combined process then you don't need to register `printerA`.\n\nExample:\n\n```scala\nimport cats.effect.IO\nimport io.parapet.CatsApp\nimport io.parapet.core.Event.Start\nimport io.parapet.core.{Event, Process}\n\nobject Example extends CatsApp {\n\n  import dsl._\n\n  case class Print(data: Any) extends Event\n\n  override def processes: IO[Seq[Process[IO]]] =\n    for {\n      printerA \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Print(data) =\u003e eval(println(s\"printerA: $data\"))\n      }))\n      printerB \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Print(data) =\u003e eval(println(s\"printerB: $data\"))\n      }))\n\n      combined \u003c- IO.pure(printerA.and(printerB))\n\n      client \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Start =\u003e Print(\"test\") ~\u003e combined\n      }))\n\n    } yield Seq(combined, printerB, client)\n\n}\n```\n\n`or` - creates a new process with `ref` of the first process. A combined process refers to the first process if its `handle` is defined for the given event, otherwise, to the second process. Sends an error to the sender if neither process is defined for the given event.\n\nExample:\n\n```scala\nimport cats.effect.IO\nimport io.parapet.CatsApp\nimport io.parapet.core.Event.Start\nimport io.parapet.core.{Event, Process}\n\nobject Example extends CatsApp {\n\n  import dsl._\n\n  case class Print(data: Any) extends Event\n\n  override def processes: IO[Seq[Process[IO]]] =\n    for {\n      printerA \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Print(data: Int) =\u003e eval(println(s\"printerA: $data\"))\n      }))\n      printerB \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Print(data: String) =\u003e eval(println(s\"printerB: $data\"))\n      }))\n\n      combined \u003c- IO.pure(printerA.or(printerB))\n\n      client \u003c- IO.pure(Process[IO](_ =\u003e {\n        case Start =\u003e Print(\"test\") ~\u003e combined ++ Print(1) ~\u003e combined\n      }))\n\n    } yield Seq(combined, printerB, client)\n}\n```\n\n### Testing your processes\n\nIntegration tests in parapet written in a generic style that we discussed before so that the same tests can be run against any effect system. Let's try to write a simple test for a proxy process. The first thing you need to do is to add `test-utils` library into your project:\n\n```scala\nlibraryDependencies += \"io.parapet\" %% \"test-utils\" % version\n```\n\nA simple proxy process that receives requests and forwards them to a `service`\n\n```scala\n  class Proxy(service: ProcessRef) extends Process[F] {\n    override def handle: Receive = {\n      case Request(data) =\u003e Request(s\"proxy-$data\") ~\u003e service\n    }\n  }\n```\n\nTest for our `Proxy`:\n\n```scala\nimport io.parapet.core.{Event, Process, ProcessRef}\nimport io.parapet.tests.intg.ProxySpec._\nimport io.parapet.testutils.{EventStore, IntegrationSpec}\nimport org.scalatest.FunSuite\nimport org.scalatest.Matchers._\nimport org.scalatest.OptionValues._\n\n\nabstract class ProxySpec[F[_]] extends FunSuite with IntegrationSpec[F] {\n\n  import dsl._\n  \n  test(\"proxy\") {\n    val eventStore = new EventStore[F, Event]\n    val testService = Process(ref =\u003e {\n      case req: Request =\u003e eval(eventStore.add(ref, req))\n    })\n\n    val proxy = new Proxy[F](testService.ref)\n\n    val init = onStart(Request(\"req\") ~\u003e proxy)\n\n    unsafeRun(eventStore.await(1, createApp(ct.pure(Seq(init, testService, proxy))).run))\n\n    eventStore.get(testService.ref).headOption.value shouldBe Request(\"proxy-req\")\n  }\n\n}\n```\n\nIn order to run this test against Cats Effect IO you need to extend `BasicCatsIOSpec`:\n\n```scala\nimport cats.effect.IO\nimport io.parapet.testutils.BasicCatsIOSpec\n\nclass ProxySpec extends io.parapet.tests.intg.ProxySpec[IO] with BasicCatsIOSpec\n```\n\n\n\n\n### Basic patterns and tips\n\n\n\nTODO\n\n## Channel\n\nChannel is a process that implements strictly synchronous request-reply dialog. The channel sends an event to a receiver and then waits for a response in one step, i.e. it blocks asynchronously until it receives a response. Doing any other sequence, e.g., sending two request or reply events in a row will return a failure to the sender.\n\nExample for some `F[_]`: \n\n```scala\n  val server = new Process[F] {\n    override def handle: Receive = {\n      case Request(data) =\u003e withSender(sender =\u003e Response(s\"echo: $data\") ~\u003e sender)\n    }\n  }\n\n  val client = new Process[F] {\n\n    lazy val ch = Channel[F]\n\n    override def handle: Receive = {\n      case Start =\u003e register(ref, ch) ++\n        ch.send(Request(\"PING\"), server.ref, {\n          case scala.util.Success(Response(data)) =\u003e eval(println(data))\n          case scala.util.Failure(err) =\u003e eval(println(s\"server failed to process request. err: ${err.getMessage}\"))\n        })\n\n    }\n  }\n\n\n  case class Request(data: Any) extends Event\n\n  case class Response(data: Any) extends Event\n```\n\n## Error Handling and DeadLetterProcess\n\nThere are some scenarios when a process may receive a `Failure` event:\n\n* When a target process failed to handle an event sent by another process.\n\nExample: \n\n```scala\n\n  // for some effect F[_]\n  val faultyServer = Process.builder[F](_ =\u003e {\n    case Request(_) =\u003e eval(throw new RuntimeException(\"server is down\"))\n  }).ref(ProcessRef(\"server\")).build\n\n  val client = Process.builder[F](_ =\u003e {\n    case Start =\u003e Request(\"PING\") ~\u003e faultyServer\n    case Failure(Envelope(me, event, receiver), EventHandlingException(errMsg, cause)) =\u003e eval {\n      println(s\"self: $me\")\n      println(s\"event: $event\")\n      println(s\"receiver: $receiver\")\n      println(s\"errMsg: $errMsg\")\n      println(s\"cause: ${cause.getMessage}\")\n    }\n  }).ref(ProcessRef(\"client\")).build\n```\n\nThe code above will output: \n\n```\nself: client\nevent: Request(PING)\nreceiver: server\nerrMsg: process [name=undefined, ref=server] has failed to handle event: Request(PING)\ncause: server is down\n```\n\n`EventHandlingException` indicates that a receiver process failed to handle an event.\n\n* When a process event queue is full. It's possible when a process experiencing performance degradation due to heavy load.\n\nExample:\n\nFor this example we need to tweak SchedulerConfig: \n\n```\nqueueSize = 10000\nprocessQueueSize = 100\n```\n\n```scala\n  // for some effect F[_]\n  val slowServer = Process.builder[F](_ =\u003e {\n    case Request(_) =\u003e eval(while (true) {}) // very slow process...\n  }).ref(ProcessRef(\"server\")).build\n\n  val client = Process.builder[F](_ =\u003e {\n    case Start =\u003e\n      generateRequests(1000) ~\u003e slowServer\n    case Failure(Envelope(me, event, receiver), EventDeliveryException(errMsg, cause)) =\u003e eval {\n      println(s\"self: $me\")\n      println(s\"event: $event\")\n      println(s\"receiver: $receiver\")\n      println(s\"errMsg: $errMsg\")\n      println(s\"cause: ${cause.getMessage}\")\n      println(\"=====================================================\")\n    }\n  }).ref(ProcessRef(\"client\")).build\n\n  def generateRequests(n: Int): Seq[Event] = {\n    (0 until n).map(Request)\n  }\n```\n\nThe code above will print a dozens of lines, four lines per `Failure` event:\n\n```\nclient sent events\nself: client\nevent: Request(101)\nreceiver: server\nerrMsg: System failed to deliver an event to process [name=undefined, ref=server]\ncause: process [name=undefined, ref=server] event queue is full\n=====================================================\nself: client\nevent: Request(102)\nreceiver: server\nerrMsg: System failed to deliver an event to process [name=undefined, ref=server]\ncause: process [name=undefined, ref=server] event queue is full\n=====================================================\nself: client\nevent: Request(103)\nreceiver: server\nerrMsg: System failed to deliver an event to process [name=undefined, ref=server]\ncause: process [name=undefined, ref=server] event queue is full\n=====================================================\n```\n\n`EventDeliveryException` indicates that the system failed to deliver an event. Handling such types of errors may be useful for runtime analysis, e.g. a sender process might consider lowering event send rate or even stop sending events to let a target process to finish processing pending events. It's worth noting that you should avoid any long-running computations when processing `Failure` events because it could lead to *cascading failures*.\n\n* A process event handler isn't defined for some events.\n\nExample:\n\n```scala\n  // for some effect F[_]\n  val uselessService = Process.builder[F](_ =\u003e {\n    case Start =\u003e unit\n    case Stop =\u003e unit\n  }).ref(ProcessRef(\"server\")).build\n\n  val client = Process.builder[F](_ =\u003e {\n    case Start =\u003e\n      Request(\"PING\") ~\u003e uselessService\n    case Failure(Envelope(me, event, receiver), EventMatchException(errMsg)) =\u003e eval {\n      println(s\"self: $me\")\n      println(s\"event: $event\")\n      println(s\"receiver: $receiver\")\n      println(s\"errMsg: $errMsg\")\n    }\n  }).ref(ProcessRef(\"client\")).build\n```\n\nThe code above will print:\n\n```\nself: client\nevent: Request(PING)\nreceiver: server\nerrMsg: process [name=undefined, ref=server] handler is not defined for event: Request(PING)\n```\n\n* A process doesn't exist in Parapet system.\n\nExample:\n\n```scala\n  // for some effect F[_]\n  val unknownService = Process.builder[F](_ =\u003e {\n    case Start =\u003e unit\n    case Stop =\u003e unit\n  }).ref(ProcessRef(\"server\")).build\n\n  val client = Process.builder[F](_ =\u003e {\n    case Start =\u003e\n      Request(\"PING\") ~\u003e unknownService\n    case Failure(Envelope(me, event, receiver), UnknownProcessException(errMsg)) =\u003e eval {\n      println(s\"self: $me\")\n      println(s\"event: $event\")\n      println(s\"receiver: $receiver\")\n      println(s\"errMsg: $errMsg\")\n    }\n  }).ref(ProcessRef(\"client\")).build\n```\n\nThe code above will print:\n\n```\nself: client\nevent: Request(PING)\nreceiver: server\nerrMsg: there is no such process with id=server registered in the system\n```\n\nFinal notes regarding error handling:\n\n* All `Failure` events sent by `parapet-system` process (if you are curious you can check it by yourself using `withSender`).\n* If a process has no error handling then `Failure` event will be sent to `DeadLetterProcess`. More about `DeadLetterProcess` you will  find below\n\n### DeadLetterProcess\n\nThe library by default provides an implementation of `DeadLetterProcess` which just logs failures. Although it might be not very practical, for instance, you may prefer to store failures into a database for further analyses. The library allows providing a custom implementation of `DeadLetterProcess`.\n\nExample using `CatsApp`:\n\n```scala\nimport cats.effect.IO\nimport io.parapet.CatsApp\nimport io.parapet.core.Event.{DeadLetter, Start}\nimport io.parapet.core.processes.DeadLetterProcess\nimport io.parapet.core.{Event, Process, ProcessRef}\n\n\nobject CustomDeadLetterProcessDemo extends CatsApp {\n\n  import dsl._\n\n  override def deadLetter: IO[DeadLetterProcess[IO]] = IO.pure {\n    new DeadLetterProcess[IO] {\n      override def handle: Receive = {\n        // can be stored in database\n        case DeadLetter(envelope, error) =\u003e eval {\n          println(s\"sender: ${envelope.sender}\")\n          println(s\"receiver: ${envelope.receiver}\")\n          println(s\"event: ${envelope.event}\")\n          println(s\"errorType: ${error.getClass.getSimpleName}\")\n          println(s\"errorMsg: ${error.getMessage}\")\n        }\n      }\n    }\n  }\n\n  val faultyServer = Process.builder[IO](_ =\u003e {\n    case Request(_) =\u003e eval(throw new RuntimeException(\"server is down\"))\n  }).ref(ProcessRef(\"server\")).build\n\n  val client = Process.builder[IO](_ =\u003e {\n    case Start =\u003e Request(\"PING\") ~\u003e faultyServer\n    // no error handling\n  }).ref(ProcessRef(\"client\")).build\n\n  override def processes: IO[Seq[Process[IO]]] = IO {\n    Seq(client, faultyServer)\n  }\n\n  case class Request(data: Any) extends Event\n\n}\n```\n\nThe code above will print:\n\n```\nsender: client\nreceiver: server\nevent: Request(PING)\nerrorType: EventHandlingException\nerrorMsg: process [name=undefined, ref=server] has failed to handle event: Request(PING)\n```\n\n## Configuration\n\nParapet system can be configured by providing an instance of `ParConfig`.\n\nExample:\n\n```scala\nimport cats.effect.IO\nimport io.parapet.core.Parapet.ParConfig\nimport io.parapet.{CatsApp, core}\n \nobject ConfigExample extends CatsApp{\n  override def processes: IO[Seq[core.Process[IO]]] = _\n \n  override val config: ParConfig = ParConfig(...)\n}\n```\n\n`ParConfig` has the following properties:\n\n* schedulerConfig: \n  * queueSize - size  of event queue shared  by workers\n  * numberOfWorkers - number of workers; default = availableProcessors\n  * processQueueSize -  size  of event queue  per individual  process, `-1` - unbounded \n\nYou should set `queueSize` to a value that would match the expected workload. For example, if you are going to send 1M events within the same flow it's recommended to set  `queueSize` to 1M. However, it depends on how fast your consumer processes and amount of available memory, if that's possible to keep some amount of events in memory -  go for it, if not - you will probably need to reconsider your design decisions. \nIn a case the event queue is full all events will be redirected to `EventLog` (see the corresponding section).\n\n## EventLog\n\n`EventLog` can be used to store events on disk. Latter, events can be retrieved and resubmitted.\nIn a case, the event queue is full unsubmitted events will be redirected to `EventLog`.   The default implementation just logs such events. In future releases, more practical implementation will be provided.\n\n## Correctness Properties\n\nSafty properties: \n* It's guaranteed that events will be delivered to a process in a strictly synchronous request-reply dialog, i.e. a process will receive a new event iff it completed processing the current one. \n* All events  delivered in send order\n\nLiveness  properties:\n\n* Sent events  eventually delivered\n* A sender  eventually receives  a response\n\n\n## Distributed Algorithms in Parapet\n\nPlease refer to [components/algorithms](https://github.com/parapet-io/parapet/tree/master/components/algorithms/) subproject\n\n## Performance Analysis\n\nPerformance mainly dependents on the underlying effect system. In general, there is always some performance and memory overhead associated with the use of Monads and immutable data structures.\n\nPerformance test  spec:\n\n* 1M requests + 1M responses  = 2M events\n* CatsApp based\n* Number of workers - 12\n* Number of processes -  2 (one publisher, one consumer)\n* CPU:  Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, 2208 Mhz, 6 Core(s), 12 Logical Processor(s)\n* RAM:  32GB\n* OS: Windows  10  x64\n\nTotal time: `25206 ms`\n\n\n##  Contribution\n\nThe project in its early stage and many things are subject to change. Now is a good time to join!\nIf you want to become a contributor please send me [email](mailto:dmgcodevil@gmail.com) or text in [gitter](https://gitter.im/io-parapet/parapet) channel.\n\nIf you'd like to donate in order to help with ongoing development and maintenance:\n\n\u003ca href=\"https://www.patreon.com/bePatron?u=6107081\"\u003e\u003cimg label=\"Become a Patron!\" src=\"https://c5.patreon.com/external/logo/become_a_patron_button@2x.png\" height=\"40\" /\u003e\u003c/a\u003e\n\n\n## License\n\n```\n   Copyright [2019] The Parapet Project Developers\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n```\n\n`∏åRÂπ∑†`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparapet-io%2Fparapet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparapet-io%2Fparapet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparapet-io%2Fparapet/lists"}