{"id":13806846,"url":"https://github.com/imrafaelmerino/vertx-effect","last_synced_at":"2026-02-27T04:30:56.273Z","repository":{"id":262852727,"uuid":"848708696","full_name":"imrafaelmerino/vertx-effect","owner":"imrafaelmerino","description":"Vertx-effect brings functional effects to the Vert.x ecosystem and is inspired by Erlang’s actor model. Its manifesto emphasizes principles such as maximizing the use of verticles for scalability, maintaining single responsibility for each verticle, and prioritizing simplicity to enhance reliability and ease debugging.","archived":false,"fork":false,"pushed_at":"2024-11-23T21:56:32.000Z","size":8306,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-27T10:48:36.794Z","etag":null,"topics":["erlang","functional-programming","java","json-values","persistent-data-structure","vertx","vertx-values","vertx3"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imrafaelmerino.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2024-08-28T09:02:26.000Z","updated_at":"2025-05-07T01:18:31.000Z","dependencies_parsed_at":"2024-11-14T17:13:54.049Z","dependency_job_id":"e3f7c7a2-f816-4a10-b6b9-206a81f3acf2","html_url":"https://github.com/imrafaelmerino/vertx-effect","commit_stats":{"total_commits":251,"total_committers":2,"mean_commits":125.5,"dds":"0.055776892430278835","last_synced_commit":"6ef6c13750a7e13d3f42c49afa837dc56f586be3"},"previous_names":["imrafaelmerino/vertx-effect"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/imrafaelmerino/vertx-effect","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fvertx-effect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fvertx-effect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fvertx-effect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fvertx-effect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imrafaelmerino","download_url":"https://codeload.github.com/imrafaelmerino/vertx-effect/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imrafaelmerino%2Fvertx-effect/sbom","scorecard":{"id":486298,"data":{"date":"2025-08-11","repo":{"name":"github.com/imrafaelmerino/vertx-effect","commit":"d1106094da9f6b8ee4734714fb3170d5c02293b7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.2,"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":"Dangerous-Workflow","score":-1,"reason":"no workflows found","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":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"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":"Code-Review","score":0,"reason":"Found 0/6 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":"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":"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":"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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"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":"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":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"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":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"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":"Vulnerabilities","score":7,"reason":"3 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-prj3-ccx8-p6x4","Warn: Project is vulnerable to: GHSA-389x-839f-4rhx","Warn: Project is vulnerable to: GHSA-4g8c-wm8x-jfhw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T17:54:28.846Z","repository_id":262852727,"created_at":"2025-08-19T17:54:28.846Z","updated_at":"2025-08-19T17:54:28.846Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29884677,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T23:51:21.483Z","status":"online","status_checked_at":"2026-02-27T02:00:06.759Z","response_time":57,"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":["erlang","functional-programming","java","json-values","persistent-data-structure","vertx","vertx-values","vertx3"],"created_at":"2024-08-04T01:01:17.010Z","updated_at":"2026-02-27T04:30:56.247Z","avatar_url":"https://github.com/imrafaelmerino.png","language":"Java","funding_links":["https://www.buymeacoffee.com/imrafaelmerino"],"categories":["Reactive"],"sub_categories":[],"readme":"\u003cimg src=\"./logo/package_twitter_swe2n4mg/black/full/coverphoto/black_logo_white_background.png\" alt=\"logo\"/\u003e\n\n[![Maven](https://img.shields.io/maven-central/v/com.github.imrafaelmerino/vertx-effect/5.0.0)](https://search.maven.org/artifact/com.github.imrafaelmerino/vertx-effect/5.0.0/jar)\n\n[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-%E2%98%95%20Support-yellow)](https://www.buymeacoffee.com/imrafaelmerino)\n\n\n- [vertx-effect manifesto](#manifesto)\n- [How persistent data structures makes a difference working with actors](#persistendata)\n- [vertx-effect in a few lines of code](#fewlinesofcode)\n- [Effects](#effects)\n- [Expressions](#exp)\n- [Being reactive](#reactive)\n- [Modules](#modules)\n- [Logging](#logging)\n  - [Publishing events](#events)\n  - [Publishing correlated events](#correlated-events)\n- [Spawning verticles](#spawning-verticles)\n- [Http client](#http-client)\n- [Oauth Http client](#oauth-client)\n- [Http server](#http-server)\n- [Testing](#testing)\n  - [VIO stubs](#vio-stubs)\n  - [Http server stubs](#http-stubs)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Related projects](#rp)\n\n[![Maven](https://img.shields.io/maven-central/v/com.github.imrafaelmerino/vertx-effect/5.0.0)](https://search.maven.org/artifact/com.github.imrafaelmerino/vertx-effect/5.0.0/jar)\n\n## \u003ca name=\"manifesto\"\u003e\u003ca/\u003e vertx-effect manifesto\n\n    . The more verticles, the better.\n    . A verticle must do only one thing.\n    . Use persistent data structures.\n    . Systems will fail, be prepared.\n    . Simplicity matters.\n    . If there is a bug and you can't spot it quickly, then there are two bugs. Fix both of them.\n\n## \u003ca name=\"persistendata\"\u003e\u003ca/\u003eHow persistent data structures makes a difference working with actors\n\nVertx-effect embraces immutability and persistent data structures. That's why it uses\n[vertx-values](https://github.com/imrafaelmerino/vertx-values) instead of the default Vert.x Json\nwhich is very inefficient.\n\n## \u003ca name=\"fewlinesofcode\"\u003e\u003ca/\u003evertx-effect in a few lines of code\n\n```code\npublic class MyModule extends VertxModule {\n\n    public static Lambda\u003cString, String\u003e toLowerCase, toUpperCase;\n    public static Lambda\u003cInteger, Integer\u003e inc;\n    public static Lambda\u003cJsObj, JsObj\u003e validate, validateAndMap;\n\n    @Override\n    protected void deploy() {\n\n        this.deploy(\"toLowerCase\",\n                    (String str) -\u003e VIO.succeed(str.toLowerCase())\n                   );\n        this.deploy(\"toUpperCase\",\n                    (String str) -\u003e VIO.succeed(str.toUpperCase())\n                   );\n        this.deploy(\"inc\",\n                    (Integer n) -\u003e VIO.succeed(n + 1)\n                   );\n\n        // json-values uses specs to define the structure of a Json: {a:int,b:[str,str]}\n        JsObjSpec spec = JsObjSpec.of(\"a\", integer(),\n                                      \"b\", tuple(str(), str())\n                                     );\n        this.deploy(\"validate\", Validators.validateJsObj(spec));\n\n        Lambda\u003cJsObj, JsObj\u003e map = obj -\u003e\n                JsObjExp.par(\"a\",\n                             inc.apply(obj.getInt(\"a\"))\n                                .map(JsInt::of),\n                             \"b\",\n                             JsArrayExp.par(toLowerCase.apply(obj.getStr(path(\"/b/0\")))\n                                                       .map(JsStr::of),\n                                            toUpperCase.apply(obj.getStr(path(\"/b/1\")))\n                                                       .map(JsStr::of)\n                                           )\n                            )\n                        .retry(RetryPolicies.limitRetries(2));\n        this.deploy(\"validateAnMap\",\n                    (JsObj obj) -\u003e validate.apply(obj).then(map)\n                   );\n\n    }\n\n    @Override\n    protected void initialize() {\n\n        toUpperCase = this.ask(\"toUpperCase\");\n        toLowerCase = this.ask(\"toLowerCase\");\n        inc = this.ask(\"inc\");\n        validate = this.ask(\"validate\");\n        validateAndMap = this.ask(\"validateAnMap\");\n\n    }\n}\n```\n\n**A module is a regular verticle that deploys other verticles and exposes lambdas to communicate\nwith them**. A lambda is just a function that takes an input and produces an output. In the above\nexample, `MyModule` deploys five verticles. It's worth mentioning how the verticle `ValidateAndMap`\nis defined using composition and the expressions `JsObjExp` and `JsArrayExp`. It shows the essence\nand the goal of vertx-effect. Later on, we'll see more expressions like `CondExp`, `SwitchExp`,\n`IfElseExp`, `AllExp`, `AnyExp`, `PairExp`, `TripleExp`, `ListExp`, `MapExp` etc.\n\n`ValidateAndMap` sends a message to `validate`. If the message matches the given spec,\n`ValidateAndMap` computes the output sending messages to the verticles `inc`, `toLowerCase`, and\n`toUpperCase` and composing a Json from their responses in parallel. You can operate sequentially\ninstead of in parallel using the constructors `JsObjExp.seq` and `JsArrayExp.sequential`. Thanks to\nthe `retry` function, if `any` verticle failed to compute their value, it would retry the\ncomputation up to two times.\n\nIt's important to notice that you can still send messages to the module verticles using the Vertx\nAPI, but one of the points of vertx-effect is to use functions for that.\n\nLet's write some tests. Vertx doesn't support json-values, so we need to register a `MessageCodec`\nto send its persistent Json across the event bus.\n\n```java\nimport io.vertx.core.Vertx;\nimport io.vertx.junit5.*;\nimport org.junit.jupiter.api.*;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport jsonvalues.JsArray;\nimport jsonvalues.JsInt;\nimport jsonvalues.JsObj;\nimport vertx.effect.PairExp;\nimport vertx.effect.VertxRef;\nimport vertx.values.codecs.RegisterJsValuesCodecs;\n\n@ExtendWith(VertxExtension.class)\npublic class TestMyModule {\n\n    @BeforeAll\n    // register a MessageCodec for json-values and deploy MyModule\n    public static void prepare(final Vertx vertx,\n                               final VertxTestContext context\n                              ) {\n        VertxRef ref = new VertxRef(vertx);\n        PairExp.seq(ref.deployVerticle(new RegisterJsValuesCodecs()),\n                    ref.deployVerticle(new MyModule())\n                   )\n               .onSuccess(ids -\u003e context.completeNow())\n               .get();\n    }\n\n    @Test\n    public void empty_json_is_sent_and_failure_is_received(VertxTestContext context) {\n\n        MyModule.validateAndMap.apply(JsObj.EMPTY)\n                               .onComplete(result -\u003e\n                                                   context.verify(() -\u003e {\n                                                       Assertions.assertTrue(result.failed());\n                                                       System.out.println(result.cause());\n                                                       context.completeNow();\n                                                   })\n                                          )\n                               .get();\n    }\n\n    @Test\n    public void valid_json_is_sent_and_is_mapped_successfully(VertxTestContext context) {\n\n        JsObj input = JsObj.of(\"a\", JsInt.of(1), \"b\", JsArray.of(\"FOO\", \"foo\"));\n\n        JsObj expected = JsObj.of(\"a\", JsInt.of(2), \"b\", JsArray.of(\"foo\", \"FOO\"));\n\n        MyModule.validateAndMap.apply(input)\n                               .onSuccess(output -\u003e {\n                                   context.verify(() -\u003e {\n                                       Assertions.assertEquals(expected, output);\n                                       context.completeNow();\n                                   });\n                               })\n                               .get();\n    }\n}\n```\n\n**Lambdas are just functions, so you can test them without deploying any verticle!**\n\n```code\n\n Lambda\u003cString, String\u003e toLowerCase = str -\u003e VIO.succeed(str.toLowerCase());\n\n Lambda\u003cString, String\u003e toUpperCase = str -\u003e VIO.succeed(str.toUpperCase());\n\n Lambda\u003cInteger, Integer\u003e inc = n -\u003e VIO.succeed(n+1);\n\n JsObjSpec spec = JsObjSpec.of(\"a\", integer(), \"b\", tuple(str(), str()));\n\n Lambda\u003cJsObj, JsObj\u003e validate = Validators.validateJsObj(spec);\n\n Lambda\u003cJsObj, JsObj\u003e map = obj-\u003e\n            JsObjExp.par(\"a\", inc.apply(obj.getInt(\"a\")).map(JsInt::of),\n                         \"b\", JsArrayExp.par(toLowerCase.apply(obj.getStr(path(\"/b/0\"))).map(JsStr::of),\n                                             toUpperCase.apply(obj.getStr(path(\"/b/1\"))).map(JsStr::of)\n                                            )\n                        );\n\n @Test\n public void valid_json_is_validated_and_mapped(VertxTestContext context) {\n\n    JsObj input = JsObj.of(\"a\", JsInt.of(1), \"b\", JsArray.of(\"FOO\",\"foo\"));\n\n    JsObj expected = JsObj.of(\"a\", JsInt.of(2), \"b\", JsArray.of(\"foo\",\"FOO\"));\n\n    validate.then(map)\n            .apply(input).get()\n            .onComplete(result-\u003e{\n                    context.verify(\n                        () -\u003e Assertions.assertTrue(result.succeeded() \u0026\u0026 result.result().equals(expected))\n                    );\n                    context.completeNow();\n                });\n    }\n\n```\n\n**This is extremely convenient and productive to do your testing. You don't need to mock anything.\nPassing around functions that produce outputs given some inputs is enough to check that your\nverticles will do their job.**\n\n**The takeaway from this section is how using function composition and different expressions, you'll\nbe able to handle complexity and implement and test any imaginable flow very quickly.**\n\n## \u003ca name=\"effects\"\u003e\u003ca/\u003e Effects\n\nFunctional Programming is all about working with pure functions and values. That's all. **One of the\npoints where FP especially shines is dealing with effects**. An effect is something you can't call\ntwice unless you intended to:\n\n```code\n\nFuture\u003cCustomer\u003e a = insertDb(customer);\n\nFuture\u003cCustomer\u003e b = insertDb(customer);\n\n```\n\nBoth calls can fail, or they can create two different customers, or one of them can fail, who knows.\nThat code is not referentially transparent. For obvious reasons, you can't do the following\nrefactoring:\n\n```code\n\nFuture\u003cCustomer\u003e c = insertDb(customer)\n\nFuture\u003cCustomer\u003e a = c;\n\nFuture\u003cCustomer\u003e b = c;\n\n```\n\nA Vertx future represents an asynchronous effect. We don't want to block the event loop because of\nthe latency of a computation. **Haskell** has proven to us how laziness is an essential property to\nstay pure. We need to define an immutable and lazy data structure that allows us to control the\neffect of latency.\n\nSince Java 8, we have suppliers. They are indispensable to do FP in Java. Let's start defining what\nan effect is in vertx-effect:\n\n```code\n\nimport java.util.function.Supplier\nimport io.vertx.core.Future\n\npublic abstract class VIO\u003cO\u003e extends Supplier\u003cFuture\u003cO\u003e\u003e {\n    //from a constant\n    public static VIO\u003cO\u003e succeed(O constant);\n\n    //from a exception\n    public static VIO\u003cO\u003e fail(Throwable error);\n\n    // from an effect\n    public static VIO\u003cO\u003e effect(Supplier\u003cFuture\u003cO\u003e\u003e effect);\n\n}\n\n```\n\nA **VIO** of type **O** is a supplier that will return a Vertx future of type **O**. **It describes\n(and not execute) an asynchronous effect that will compute a value of type O**.\n\nIf we turn Future into VIO in the previous example:\n\n```code\n\nVIO\u003cCustomer\u003e a = VIO.effect( () -\u003e insertDb(customer) );\n\nVIO\u003cCustomer\u003e b = VIO.effect( () -\u003e insertDb(customer) );\n\n```\n\nThe above example is entirely equivalent to:\n\n```code\n\nVIO\u003cCustomer\u003e c = VIO.effect( ()-\u003einsertDb(customer) );\n\nVIO\u003cCustomer\u003e a = c;\n\nVIO\u003cCustomer\u003e b = c;\n\n```\n\nThis property is fundamental. Whenever you see the expression `insertDb(customer)` in your program,\nyou can think of it as it was c. Pure FP programming helps us reason about the programs we write.\nVIO is lazy. It's a description of an effect. **In FP, we describe programs, and it's at the very\nlast moment when they're executed.**\n\nI always wanted to name **Lambda** to something, and I finally got the chance!\n\n```code\n\nimport java.util.function.Function\n\npublic interface Lambda\u003cI,O\u003e extends Function\u003cI, VIO\u003cO\u003e\u003e { }\n\n```\n\nA lambda is a function that returns a **VIO** of type **O** given a type **I**. **It models the\ncommunication with a verticle**: a message is sent, the verticle receives and processes the message,\nand replies with a response. The message and the answer have to be of a type that can be sent across\nthe event bus; otherwise, you must implement a\n[MessageCodec](https://vertx.io/docs/apidocs/io/vertx/core/eventbus/MessageCodec.html).\n\n## \u003ca name=\"exp\"\u003e\u003ca/\u003e Expressions\n\n**Using expressions and function composition is how we deal with complexity in functional\nprogramming**. Let's go over the essential expressions in vertx-effect:\n\n- **VIO constructors**\n\n```code\nVIO\u003cString\u003e str = VIO.succeed(\"hi\");\n\nVIO\u003cThrowable\u003e error = VIO.fail(new RuntimeException(\"something went wrong :(\"));\n\nFuture\u003cJsObj\u003e getProfile(final String id){...}\nVIO\u003cJsObj\u003e profile = VIO.effect( () -\u003e getProfile(id));\n\nVIO\u003cLong\u003e realTime = VIO.effect(() -\u003e System.currentTimeMillis() );\n```\n\n- **IfElseExp**. If the condition is evaluated to true, it computes and returns the consequence;\n  otherwise, the alternative.\n\n```code\n\nimport jio.IfElseExp;\n\n VIO\u003cO\u003e exp = IfElseExp.\u003cO\u003epredicate(VIO\u003cBoolean\u003e condition)\n                       .consequence(Supplier\u003cVIO\u003cO\u003e\u003e consequence)\n                       .alternative(Supplier\u003cVIO\u003cO\u003e\u003e alternative);\n\n VIO\u003cO\u003e exp = IfElseExp.\u003cO\u003epredicate(boolean condition)\n                       .consequence(Supplier\u003cVIO\u003cO\u003e\u003e consequence)\n                       .alternative(Supplier\u003cVIO\u003cO\u003e\u003e alternative);\n\n```\n\nThe alternative and the consequence are lazy computations of IO effects.\n\n- **SwitchExp**. The switch construct implements multiple pattern-value branches. It evaluates an\n  effect or value of type I and allows multiple clauses based on evaluating that value.\n\n```code\n\n// matches a value of type I\n\n VIO\u003cO\u003e exp =\n  SwitchExp\u003cI,O\u003e.match(I value)\n                .patterns(I pattern1, Lambda\u003cI,O\u003e lambda1,\n                          I pattern2, Lambda\u003cI,O\u003e lambda2,\n                          I pattern3, Lambda\u003cI,O\u003e lambda3,\n                          Lambda\u003cI,O\u003e otherwise\n                          );\n\n// matches an effect of type I\n\n VIO\u003cO\u003e exp =\n  SwitchExp\u003cI,O\u003e.match(VIO\u003cI\u003e value)\n                .patterns(I pattern1, Lambda\u003cI,O\u003e lambda1,\n                          I pattern2, Lambda\u003cI,O\u003e lambda2,\n                          I pattern3, Lambda\u003cI,O\u003e lambda3,\n                          Lambda\u003cI,O\u003e otherwise\n                          );\n\n\n// For example, the following expression reduces to \"Wednesday\"\n\n VIO\u003cO\u003e exp =\n  SwitchExp\u003cInteger,String\u003e.match(3)\n                           .patterns(1, _ -\u003e VIO.succeed(\"Monday\"),\n                                     2, _ -\u003e VIO.succeed(\"Tuesday\"),\n                                     3, _ -\u003e VIO.succeed(\"Wednesday\"),\n                                     4, _ -\u003e VIO.succeed(\"Thursday\"),\n                                     5, _ -\u003e VIO.succeed(\"Friday\"),\n                                     _ -\u003e VIO.succeed(\"weekend\")\n                                     );\n```\n\nThe same as before but using lists instead of constants as patterns.\n\n```code\n\n VIO\u003cO\u003e exp = SwitchExp\u003cI,O\u003e.match(I value)\n                          .patterns(List\u003cI\u003e pattern1, Lambda\u003cI,O\u003e lambda1,\n                                    List\u003cI\u003e pattern2, Lambda\u003cI,O\u003e lambda2,\n                                    List\u003cI\u003e pattern3, Lambda\u003cI,O\u003e lambda3,\n                                    Lambda\u003cI,O\u003e otherwise\n                                   );\n\n// For example, the following expression reduces to \"third week\"\n VIO\u003cO\u003e exp =\n  SwitchExp\u003cInteger,String\u003e.match(20)\n                           .patterns(List.of(1, 2, 3, 4, 5, 6, 7), _ -\u003e VIO.succeed(\"first week\"),\n                                     List.of(8, 9, 10, 11, 12, 13, 14), _ -\u003e VIO.succeed(\"second week\"),\n                                     List.of(15, 16, 17, 18, 19, 20, 10), _ -\u003e VIO.succeed(\"third week\"),\n                                     List.of(21, 12, 23, 24, 25, 26, 27), _ -\u003e VIO.succeed(\"forth week\"),\n                                     _ -\u003e VIO.succeed(\"last days of the month\")\n                                    );\n```\n\nLast but not least, you can use predicates as patterns instead of values or list of values:\n\n```code\n\n VIO\u003cO\u003e exp =\n  SwitchExp\u003cI,O\u003e.match(VIO\u003cI\u003e value)\n                .patterns(Predicate\u003cI\u003e pattern1, Lambda\u003cI,O\u003e lambda1,\n                          Predicate\u003cI\u003e pattern2, Lambda\u003cI,O\u003e lambda2,\n                          Predicate\u003cI\u003e pattern3, Lambda\u003cI,O\u003e lambda3,\n                          Lambda\u003cI,O\u003e otherwise\n                          );\n\n// For example, the following expression reduces to the default value, \"greater or equal to twenty\"\n\n VIO\u003cO\u003e exp =\n  SwitchExp\u003cInteger,String\u003e.match(IO.succeed(20))\n                           .patterns(i -\u003e i \u003c 5 , _ -\u003e VIO.succeed(\"lower than five\"),\n                                     i -\u003e i \u003c 10 , _ -\u003e VIO.succeed(\"lower than ten\"),\n                                     i -\u003e i \u003c 20 , _ -\u003e VIO.succeed(\"lower than twenty\"),\n                                     _ -\u003e VIO.succeed(\"greater or equal to twenty\")\n                                    );\n```\n\n- **CondExp**. It's a set of branches, and a default value. Each branch consists of an effect that\n  computes a boolean (the condition) and its associated effect. The effect is computed and the\n  expression reduced to its value if its condition is the first one in the list to be true. This\n  means the order you place branches matters. If no condition is true, it computes the default\n  effect, which is the last clause. You can compute all the conditions values either in parallel or\n  sequentially.\n\n```code\n\n VIO\u003cO\u003e exp = CondExp.\u003cO\u003eseq(VIO\u003cBoolean\u003e cond1, Supplier\u003cVIO\u003cO\u003e\u003e effect1,\n                             VIO\u003cBoolean\u003e cond2, Supplier\u003cVIO\u003cO\u003e\u003e effect2,\n                             VIO\u003cBoolean\u003e cond3, Supplier\u003cVIO\u003cO\u003e\u003e effect3,\n                             Supplier\u003cVIO\u003cO\u003e\u003e otherwise\n                          );\n\n\n VIO\u003cO\u003e exp = CondExp.\u003cO\u003epar(VIO\u003cBoolean\u003e cond1, Supplier\u003cVIO\u003cO\u003e\u003e effect1,\n                             VIO\u003cBoolean\u003e cond2, Supplier\u003cVIO\u003cO\u003e\u003e effect2,\n                             VIO\u003cBoolean\u003e cond3, Supplier\u003cVIO\u003cO\u003e\u003e effect3,\n                             Supplier\u003cVIO\u003cO\u003e\u003e otherwise\n                          );\n\n```\n\n- `AllExp` and `AnyExp`. They are just idiomatic names for the boolean expressions And and Or. You\n  can compute all the boolean effects either in parallel or sequentially.\n\n```code\n\n VIO\u003cBoolean\u003e all = AllExp.par(VIO\u003cBoolean\u003e cond1, VIO\u003cBoolean\u003e cond2, ....);\n VIO\u003cBoolean\u003e all = AllExp.seq(VIO\u003cBoolean\u003e cond1, VIO\u003cBoolean\u003e cond2, ....);\n\n VIO\u003cBoolean\u003e any = AnyExp.par(VIO\u003cBoolean\u003e cond1, VIO\u003cBoolean\u003e cond2, ...);\n VIO\u003cBoolean\u003e any = AnyExp.seq(VIO\u003cBoolean\u003e cond1, VIO\u003cBoolean\u003e cond2, ...);\n\n```\n\n- **PairExp**. A pair is a tuple of two elements. Each element can be computed either in parallel or\n  sequentially.\n\n```code\n\n VIO\u003cPair\u003cA,B\u003e pair = PairExp.par(VIO\u003cA\u003e val1, VIO\u003cB\u003e val2);\n\n VIO\u003cPair\u003cA,B\u003e pair = PairExp.seq(VIO\u003cA\u003e val1, VIO\u003cB\u003e val2);\n\n```\n\nYou can race pairs when A and B have the same type, returning the first value that is computed:\n\n```code\n\n VIO\u003cPair\u003cA,A\u003e pair = PairExp.par(VIO\u003cA\u003e val1, VIO\u003cA\u003e val2);\n\n VIO\u003cA\u003e thefastest = PairExp.race(par);\n\n```\n\n- **TripleExp**. A triple is a tuple of three elements. Each element can be computed either in\n  parallel or sequentially.\n\n```code\n\n VIO\u003cTriple\u003cA,B,C\u003e triple = TripleExp.par(VIO\u003cA\u003e val1, VIO\u003cB\u003e val2, VIO\u003cC\u003e val3);\n\n VIO\u003cTriple\u003cA,B,C\u003e triple = TripleExp.seq(VIO\u003cA\u003e val1, VIO\u003cB\u003e val2, VIO\u003cC\u003e val3);\n\n```\n\nYou can also race triples.\n\n- **JsObjExp** and **JsArrayExp**.\n\n`JsObjExp` and `JsArrayExp` are data structures that look like raw Json. You can compute all the\nvalues either in parallel or sequentially. You can mix all the expressions we've seen so far and\nnest them, going as deep as necessary, like in the following example:\n\n```code\n\nIfElseExp\u003cJsStr\u003e a = IfElseExp.\u003cJsStr\u003epredicate(VIO\u003cBoolean\u003e condition)\n                                     .consequence(VIO\u003cJsStr\u003e consequence)\n                                     .alternative(VIO\u003cJsStr\u003e alternative);\n\nJsArrayExp b =\n        JsArrayExp.seq(SwitchExp\u003cInteger,JsValue\u003e.match(n)\n                                                 .patthers(1, Lambda\u003cInsteger,JsValue\u003e lambda1,\n                                                           2, Lambda\u003cInsteger,JsValue\u003e lambda2,\n                                                           Lambda\u003cInsteger,JsValue\u003e defaultLambda\n                                                          ),\n                      CondExp.par(VIO\u003cBoolean\u003e cond1, Supplier\u003cIO\u003cJsValue\u003e\u003e effect1,\n                                  VIO\u003cBoolean\u003e cond2, Supplier\u003cIO\u003cJsValue\u003e\u003e effect2,\n                                  Supplier\u003cIO\u003cJsValue\u003e\u003e defaultValue\n                                )\n                      );\n\nJsObjExp c =\n       JsObjExp.par(\"d\", AnyExp.seq(VIO\u003cBoolean\u003e cond3, VIO\u003cBoolean\u003e cond4)\n                               .map(JsBool::of),\n                    \"e\", AllExp.par(VIO\u003cBoolean\u003e cond5, VIO\u003cBoolean\u003e cond6)\n                               .map(JsBool::of),\n                    \"f\", JsArrayExp.par(VIO\u003cJsValue\u003e value1, VIO\u003cJsValue\u003e value2)\n                   )\n\nJsObjExp exp = JsObjExp.par(\"a\", a,\n                            \"b\", b,\n                            \"c\", c\n                           );\n\nJsObj json = exp.result();\n```\n\nIt's important to notice that any value of the above expressions can be computed by different\nverticles deployed on different machine's of a cluster. Imagine ten machines collaborating to\ncompute a JsObj. Isn't this amazing?\n\n- **ListExp and MapExp**\n\nThey represent sequences and maps. **Modules use them internally**. For example, the `deploy` method\nuses a MapExp to put the deployed verticles using their addresses as keys. They also use a ListExp\nwhen more than a verticle instance is deployed. As with the other expressions, you can compute their\nvalues either in parallel or sequentially.\n\n```\n\nMapExp\u003cString\u003e map = MapExp.par(\"a\", VIO\u003cString\u003e value1,\n                                \"b\", VIO\u003cString\u003e value2,\n                                \"c\", VIO\u003cString\u003e value3\n                                );\n\nListExp\u003cInteger\u003e seq = ListExp.par(VIO\u003cInteger\u003e, VIO\u003cInteger\u003e);\nVIO\u003cInteger\u003e firstFinishing = seq.race();\n\n```\n\nThe `race` function returns the value that finishes first. You can race a `JsArrayExp` as well.\n\n## \u003ca name=\"reactive\"\u003e\u003ca/\u003e Being reactive\n\nFind below some of the most critical operations defined in the `Val` interface that will help us\nmake our code more resilient:\n\n```code\nimport vertx.effect.RetryPolicy;\n\npublic interface VIO\u003cO\u003e extends Supplier\u003cFuture\u003cO\u003e\u003e {\n    VIO\u003cO\u003e retry(RetryPolicy policy);\n\n    VIO\u003cO\u003e retry(Predicate\u003cThrowable\u003e,\n                 RetryPolicy policy);\n\n    VIO\u003cO\u003e repeat(Predicate\u003cO\u003e predicate,\n                  RetryPolicy policy);\n\n    VIO\u003cO\u003e recoverWith(Lambda\u003cThrowable, O\u003e fn);\n\n    VIO\u003cO\u003e fallbackTo(Lambda\u003cThrowable, O\u003e fn);\n\n    VIO\u003cO\u003e recoverWith(Lambda\u003cThrowable, O\u003e fn);\n}\n```\n\n**recoverWith**: it switches to an alternative lambda when a failure happens.\n\n**fallbackTo**: It's like recoverWith, but if the second lambda fails too, it returns the first one\nerror.\n\n**recover**: returns a constant if the computation fails.\n\n**retry**: retries the computation if an error happens. You can define a predicate to retry only the\nspecified errors. Retry policies are created in a very declarative and composable way, for example:\n\n```code\nimport static vertx.effect.RetryPolicies.*\n\nDelay oneHundredMillis = vertxRef.sleep(Duration.ofMillis(100));\nDelay oneSec = vertxRef.sleep(Duration.ofSeconds(1));\n\n// up to five retries waiting 100 ms\nconstantDelay(oneHundredMillis).append(limitRetries(5))\n\n//during 3 seconds up to 10 times\nlimitRetries(10).limitRetriesByCumulativeDelay(Duration.ofSeconds(3))\n\n//5 times without timer and then, if it keeps failing, an incremental timer from 100 ms up to 1 second\nlimiteRetries(5).followedBy(incrementalDelay(oneHundredMillis).capDelay(oneSec))\n\n```\n\nThere are very interesting policies implemented based on [this\narticle](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/): exponential\nbackoff, full jitter, equal jitter, decorrelated jitter etc\n\n**repeat**: When you get a not expected value (a failure) and want to repeat the computation. A\npredicate is specified to catch the failures. You can define any imaginable policy as well. Imagine\nyou make a http request and you get a 500. That's not an error, it's a server failure. You can\nrepeat the request according to a policy.\n\nFor expressions like **Cond**, **Case**, **IfElse**, **All**, **Any**, **Pair**, **Triple**, you can\nretry each value of the expression instead of the overall expresion with the methods:\n\n```code\n VIO\u003cO\u003e retryEach(RetryPolicy policy);\n\n VIO\u003cO\u003e retryEach(Predicate\u003cThrowable\u003e,\n                  RetryPolicy policy);\n\n```\n\n## \u003ca name=\"modules\"\u003e\u003ca/\u003e Modules\n\nIn vertx-effect, **a module is a special verticle whose purpose is to deploy other verticles and\nexpose lambdas to communicate with them**. Let's put an example.\n\n```code\nimport jsonvalues.JsObj;\nimport jsonvalues.JsStr;\nimport vertx.effect.VIO;\nimport vertx.effect.VertxModule;\nimport vertx.effect.Lambda;\n\npublic class MyModule extends VertxModule {\n\n    private static final String REMOVE_NULL_ADDRESS = \"removeNull\";\n    private static final String TRIM_ADDRESS = \"trim\";\n\n    public static Lambda\u003cJsObj, JsObj\u003e removeNull;\n    public static Lambda\u003cJsObj, JsObj\u003e trim;\n\n    @Override\n    public void deploy() {\n\n        this.deploy(REMOVE_NULL_ADDRESS,\n                    (JsObj o) -\u003e VIO.succeed(o.filterAllValues(pair -\u003e pair.value.isNotNull()))\n                   );\n\n        Function\u003cJsValue, JsValue\u003e trim = JsStr.prism.modify.apply(String::trim);\n        this.deploy(TRIM_ADDRESS,\n                    (JsObj o) -\u003e VIO.succeed(o.mapAllValues(pair -\u003e trim.apply(pair.value)))\n                   );\n    }\n\n    @Override\n    protected void initialize() {\n        removeNull = this.ask(REMOVE_NULL_ADDRESS);\n        trim = this.ask(TRIM_ADDRESS);\n    }\n}\n```\n\nWe usually divide modules into four main blocks:\n\n    . The addresses where the module verticles will be listening on.\n    . The lambdas that are exposed to the outside world to communicate with the deployed verticles.\n    . The `deploy` method, where the module deploys the verticles.\n    . The `initialize` method, where the module initializes the lambdas.\n\nIn our example, we are using the persistent and immutable Json from json-values. The **ask** method\nreturns a lambda to establish bidirectional communication with a verticle. In contrast, the **tell**\nmethod would return a consumer because a response is either not expected or ignored. Let's deploy\nour module and do some testing. We usually divide modules into four main blocks:\n\n```code\n @BeforeAll\n public static void prepare(final Vertx vertx,\n                            final VertxTestContext context)\n {\n    VertxRef vertxRef = new VertxRef(vertx);\n\n    // prints out events published by vertx-effect\n    vertxRef.registerConsumer(EVENTS_ADDRESS, System.out::println);\n\n    Pair.seq(vertxRef.deployVerticle(new RegisterJsValuesCodecs()),\n                    vertxRef.deployVerticle(new MyModule())\n                   )\n        .onSuccess(pair -\u003e {\n                            System.out.println(String.format(\"Ids deployed: %s and %s\",\n                                                             pair._1,\n                                                             pair._2\n                                                            )\n                                              );\n                            context.completeNow();\n                           }\n                  )\n        .get();\n }\n\n @Test\n public void test_remove_and_then_trim(final VertxTestContext context)\n {\n    Lambda\u003cJsObj, JsObj\u003e removeAndTrim = MyModule.removeNull.andThen(MyModule.trim);\n\n    JsObj input = JsObj.of(\"a\", JsStr.of(\"  hi  \"),\n                           \"b\", JsNull.NULL,\n                           \"c\", JsObj.of(\"d\", JsStr.of(\"  bye  \"),\n                                         \"e\", JsNull.NULL\n                                        )\n                          );\n\n    JsObj expected = JsObj.of(\"a\", JsStr.of(\"hi\"),\n                              \"c\", JsObj.of(\"d\", JsStr.of(\"bye\"))\n                             );\n\n    removeAndTrim.apply(input)\n                 .onSuccess(it -\u003e {\n                     context.verify(()-\u003e {\n                                          Assertions.assertEquals(expected,it);\n                                          context.completeNow();\n                                         }\n                                   );\n                                  }\n                           )\n                .get();\n }\n\n```\n\nTo send the persistent objects from [json-values](https://imrafaelmerino.github.io/json-values/)\nacross the event bus, we need to register some codecs. The verticle RegisterJsValuesCodecs does this\ntask. The VertxRef class is a wrapper around the Vertx instance to deploy and spawn verticles from\nlambdas. Modules use this class internally.\n\nThe `VertxRef` class is a wrapper around the Vertx instance to deploy and spawn verticles from\nlambdas. Modules use this class internally.\n\n## \u003ca name=\"logging\"\u003e\u003ca/\u003e Logging\n\nLogging is essential in software. There are many logging libraries. Sometimes it is not clear what\ndependencies you have to use because there isn't a standard solution. Each library uses its own. I\ndidn't want to be opinionated. At the same time, I wanted to provide a simple and decouple solution\nto know what is going on in any system using vertx-effect. That's why I decided to publish\nremarkable events in a specific address. If you want to use your favorite slf4j implementation, just\nimplement it in a consumer. On the other hand, consuming all those events during testing will give\nyou instant feedback on your system and agility spotting bugs. You can disable this future with the\nJava system property **-D\"vertx.effect.enable.log.events\"=false**.\n\n### \u003ca name=\"events\"\u003e\u003ca/\u003e Publishing events\n\n**vertx-effect** publishes events to the address **vertx-effect-events**. Find below some of the\nmost important predefined events:\n\n    - VERTICLE_DEPLOYED\n    - VERTICLE_UNDEPLOYED\n    - MESSAGE_SENT\n    - MESSAGE_RECEIVED\n    - RESPONSE_REPLIED\n    - FAILURE_REPLIED\n    - RESPONSE_RECEIVED\n    - FAILURE_RECEIVED\n    - EXCEPTION_STARTING_VERTICLE\n    - EXCEPTION_STARTING_SHELL\n    - EXCEPTION_PROCESSING_MESSAGE\n    - EXCEPTION_UNDEPLOYING_VERTICLE\n    - EXCEPTION_DEPLOYING_VERTICLE\n    - TIMER_STARTED\n    - TIMER_ENDED\n\nAn example from the previous example would be:\n\n```text\n\n{\"event\":\"VERTICLE_DEPLOYED\",\"address\":\"removeNull\",\"instant\":\"2020-10-10T22:44:42.687633Z\",\"id\":\"3de92ef8-777f-4110-aa45-442fc41900c6\",\"thread\":\"vert.x-eventloop-thread-1\"}\n{\"event\":\"VERTICLE_DEPLOYED\",\"class\":\"vertx.effect.RegisterJsValuesCodecs\",\"instant\":\"2020-10-10T22:44:42.682624Z\",\"id\":\"73181043-ae38-4819-b7de-02f303fcc155\",\"thread\":\"vert.x-eventloop-thread-3\"}\n{\"event\":\"VERTICLE_DEPLOYED\",\"address\":\"trim\",\"instant\":\"2020-10-10T22:44:42.701293Z\",\"id\":\"a866ffdc-38c8-4da2-bcc0-c9f4881f5139\",\"thread\":\"vert.x-eventloop-thread-1\"}\n{\"event\":\"VERTICLE_DEPLOYED\",\"class\":\"vertx.effect.MyModule\",\"instant\":\"2020-10-10T22:44:42.703410Z\",\"id\":\"1473dff2-075c-4fd8-be42-cebcf0a890a0\",\"thread\":\"vert.x-eventloop-thread-6\"}\n\n{\"event\":\"MESSAGE_SENT\",\"to\":\"removeNull\",\"message\":{\"a\":\"  hi  \",\"b\":null,\"c\":{\"d\":\"  bye  \",\"e\":null}},\"instant\":\"2020-10-10T22:44:42.710447Z\",\"thread\":\"main\"}\n{\"event\":\"MESSAGE_RECEIVED\",\"address\":\"removeNull\",\"instant\":\"2020-10-10T22:44:42.713981Z\",\"thread\":\"vert.x-eventloop-thread-4\"}\n{\"event\":\"RESPONSE_REPLIED\",\"address\":\"removeNull\",\"message\":{\"c\":{\"d\":\"  bye  \"},\"a\":\"  hi  \"},\"instant\":\"2020-10-10T22:44:42.723013Z\",\"thread\":\"vert.x-eventloop-thread-4\"}\n{\"event\":\"RESPONSE_RECEIVED\",\"from\":\"removeNull\",\"instant\":\"2020-10-10T22:44:42.723225Z\",\"thread\":\"vert.x-eventloop-thread-8\"}\n\n{\"event\":\"MESSAGE_SENT\",\"to\":\"trim\",\"message\":{\"c\":{\"d\":\"  bye  \"},\"a\":\"  hi  \"},\"instant\":\"2020-10-10T22:44:42.723635Z\",\"thread\":\"vert.x-eventloop-thread-8\"}\n{\"event\":\"MESSAGE_RECEIVED\",\"address\":\"trim\",\"instant\":\"2020-10-10T22:44:42.724047Z\",\"thread\":\"vert.x-eventloop-thread-5\"}\n{\"event\":\"RESPONSE_REPLIED\",\"address\":\"trim\",\"message\":{\"a\":\"hi\",\"c\":{\"d\":\"bye\"}},\"instant\":\"2020-10-10T22:44:42.728636Z\",\"thread\":\"vert.x-eventloop-thread-5\"}\n{\"event\":\"RESPONSE_RECEIVED\",\"from\":\"trim\",\"instant\":\"2020-10-10T22:44:42.728902Z\",\"thread\":\"vert.x-eventloop-thread-8\"}\n\n```\n\n### \u003ca name=\"correlated-events\"\u003e\u003ca/\u003e Publishing correlated events\n\nIn async event-driven systems is extremely difficult to correlate events. Having this solved is a\nkiller future that saves you from working hours trying to gather all the different events associated\nwith a specific transaction. In vertx-effect is really easy! As always, functions and composition\ncome to the rescue. Before checking out an example, let's see what a `Lambdac` is:\n\n```code\nimport io.vertx.core.MultiMap;\n\npublic interface Lambdac\u003cI, O\u003e extends BiFunction\u003cMultiMap, I, VIO\u003cO\u003e\u003e {}\n\n```\n\nA `Lambdac` is a function that takes two arguments, a map representing the context in which an\noperation will be executed, and the message of type I sent to the verticle across the event bus. You\ncan put the user's email into the context to filter all the events associated with that email and a\nrandom value to distinguish between transactions from the same email. That's only an example.\n\n```code\npublic class UserAccountModule extends VertxModule {\n\n    public static Lambdac\u003cInteger, Boolean\u003e isLegalAge;\n    public static Lambdac\u003cString, Boolean\u003e isValidId;\n    public static Lambdac\u003cString, Boolean\u003e isValidEmail;\n    public static Lambdac\u003cJsObj, Boolean\u003e isValid;\n\n    private static final String IS_VALID_ID = \"isValidId\";\n    private static final String IS_LEGAL_AGE = \"isLegalAge\";\n    private static final String IS_VALID_EMAIL = \"isValidEmail\";\n    private static final String IS_VALID = \"isValid\";\n\n\n    @Override\n    protected void deploy() {\n\n        this.deploy(IS_LEGAL_AGE, (Integer age) -\u003e VIO.succeed(age \u003e 16));\n\n        this.deploy(IS_VALID_ID, (String id) -\u003e VIO.succeed(!id.isEmpty()));\n\n        this.deploy(IS_VALID_EMAIL, (String email) -\u003e VIO.succeed(!email.isEmpty()));\n\n        Lambdac\u003cJsObj, Boolean\u003e isValid = (context, obj) -\u003e\n                AllExp.par(isLegalAge.apply(context, obj.getInt(\"age\")),\n                           isValidId.apply(context, obj.getStr(\"id\")),\n                           isValidEmail.apply(context, obj.getStr(\"email\"))\n                          );\n        this.deploy(IS_VALID, isValid);\n    }\n\n    @Override\n    protected void initialize() {\n\n        isLegalAge = this.trace(IS_LEGAL_AGE);\n\n        isValidId = this.trace(IS_VALID_ID);\n\n        isValidEmail = this.trace(IS_VALID_EMAIL);\n\n        isValid = this.trace(IS_VALID);\n    }\n}\n```\n\nAs you can see, we've implemented a module that deploys five verticles and exposes five Lambdac to\ninteract with them. The method `trace` returns a `Lambdac` (in the previous example, we used the\n`ask` method that returns a `Lambda`). The `isValid` lambda is implemented using the `AllExp`\nexpression. The context is passed through all the lambdas of the `AllExp` expression.\n\n```code\n\nFunction\u003cJsObj, MultiMap\u003e context=\n        user -\u003e MultiMap.caseInsensitiveMultiMap()\n                        .add(\"email\",user.getStr(\"email\"));\n\n        JsObj user = JsObj.of(\"email\",JsStr.of(\"imrafaelmerino@gmail.com\"),\n                              \"age\",JsInt.of(17),\n                              \"id\",JsStr.of(\"03786761\")\n                              );\n\n        JsObj user1=JsObj.of(\"email\",JsStr.of(\"example@gmail.com\"),\n                             \"age\",JsInt.of(10),\n                             \"id\",JsStr.of(\"03486761\")\n                            );\n\n        UserAccountModule.isValid\n                         .apply(context.apply(user),\n                                user)\n                         .get();\n\n        UserAccountModule.isValid\n                         .apply(context.apply(user1),\n                                user1)\n                         .get();\n\n```\n\nLet's take a look at the events that are published during the execution of the previous code:\n\n```json\n[\n  {\n    \"event\": \"MESSAGE_SENT\",\n    \"to\": \"isValid\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"message\": {\n      \"email\": \"example@gmail.com\",\n      \"age\": 10,\n      \"id\": \"03486761\"\n    },\n    \"instant\": \"2020-10-11T15:09:26.704145Z\",\n    \"thread\": \"main\"\n  },\n  {\n    \"event\": \"MESSAGE_RECEIVED\",\n    \"address\": \"isValid\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"instant\": \"2020-10-11T15:09:26.708157Z\",\n    \"thread\": \"vert.x-eventloop-thread-8\"\n  },\n  {\n    \"event\": \"MESSAGE_SENT\",\n    \"to\": \"isValid\",\n    \"context\": {\n      \"email\": [\n        \"imrafaelmerino@gmail.com\"\n      ]\n    },\n    \"message\": {\n      \"email\": \"imrafaelmerino@gmail.com\",\n      \"age\": 17,\n      \"id\": \"03786761\u003e\"\n    },\n    \"instant\": \"2020-10-11T15:09:26.708597Z\",\n    \"thread\": \"main\"\n  },\n  {\n    \"event\": \"MESSAGE_SENT\",\n    \"to\": \"isLegalAge\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"message\": 10,\n    \"instant\": \"2020-10-11T15:09:26.709568Z\",\n    \"thread\": \"vert.x-eventloop-thread-8\"\n  },\n  {\n    \"event\": \"MESSAGE_RECEIVED\",\n    \"address\": \"isLegalAge\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"instant\": \"2020-10-11T15:09:26.710185Z\",\n    \"thread\": \"vert.x-eventloop-thread-4\"\n  },\n  {\n    \"event\": \"MESSAGE_SENT\",\n    \"to\": \"isValidId\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"message\": \"03486761\",\n    \"instant\": \"2020-10-11T15:09:26.710136Z\",\n    \"thread\": \"vert.x-eventloop-thread-8\"\n  },\n  {\n    \"event\": \"MESSAGE_SENT\",\n    \"to\": \"isValidEmail\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"message\": \"example@gmail.com\",\n    \"instant\": \"2020-10-11T15:09:26.710672Z\",\n    \"thread\": \"vert.x-eventloop-thread-8\"\n  },\n  {\n    \"event\": \"MESSAGE_RECEIVED\",\n    \"address\": \"isValidId\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"instant\": \"2020-10-11T15:09:26.710713Z\",\n    \"thread\": \"vert.x-eventloop-thread-5\"\n  },\n  {\n    \"event\": \"MESSAGE_RECEIVED\",\n    \"address\": \"isValidEmail\",\n    \"context\": {\n      \"email\": [\n        \"example@gmail.com\"\n      ]\n    },\n    \"instant\": \"2020-10-11T15:09:26.711165Z\",\n    \"thread\": \"vert.x-eventloop-thread-6\"\n  },\n  {\n    \"event\": \"MESSAGE_RECEIVED\",\n    \"address\": \"isValid\",\n    \"context\": {\n      \"email\": [\n        \"imrafaelmerino@gmail.com\"\n      ]\n    },\n    \"instant\": \"2020-10-11T15:09:26.711854Z\",\n    \"thread\": \"vert.x-eventloop-thread-8\"\n  }\n  {\n    \"event\": \"MESSAGE_SENT\",\n    \"to\": \"isLegalAge\",\n    \"context\": {\n      \"email\": [\n        \"imrafaelmerino@gmail.com\"\n      ]\n    },\n    \"message\": 17,\n    \"instant\": \"2020-10-11T15:09:26.712138Z\",\n    \"thread\": \"vert.x-eventloop-thread-8\"\n  }\n]\n```\n\n## \u003ca name=\"spawning-verticles\"\u003e\u003ca/\u003e Spawning verticles\n\nWith vertx-effect, you can spawn verticles, which means that verticles are deployed and undeployed\non the fly. Every time something needs to be computed, a new verticle is deployed. When the\ncomputation is done and the verticle replies, it is undeployed right away. The goal is to get the\nmost out of the cores! Erlang taught us how to develop concurrent software that doubles in speed if\nyou double the number of cores without changing a code line: spawning as many verticles as possible.\nIn Erlang jargon, a verticle is kind of a process.\n\nWill deploy and undeploy verticles continuously slow down the system? It depends, like everything\nrelated to performance. There are times when the cost of reaching a greater level of parallelization\nis worth it. Other times it's not. Let's see how long it takes to deploy and undeploy one million\nverticles:\n\n```code\n\n@Benchmark\n@BenchmarkMode(Mode.AverageTime)\npublic void deploy_undeploy()throws InterruptedException{\n\n        int processes = 1000000;\n        CountDownLatch latch = new CountDownLatch(processes);\n\n        for(int i=0; i\u003cprocesses; i++) {\n          vertxRef.deploy(\"id\"+i,\n                           Lambda.\u003cJsObj\u003eidentity()\n                         )\n                  .onComplete( vr -\u003e {\n                                    vr.result()\n                                      .undeploy()\n                                      .onSuccess(it-\u003elatch.countDown());\n                                     }\n                             )\n                  .get();\n        }\n\n        latch.await(10,SECONDS);\n\n        }\n```\n\nIt takes almost three seconds, 3 microseconds per verticle:\n\n```text\nBenchmark                  Mode  Cnt  Score   Error  Units\nProcesses.deploy_undeploy  avgt   10  2.907 ± 0.658   s/op\n```\n\n## \u003ca name=\"http-client\"\u003e\u003ca/\u003e Http Client\n\nHere's a comprehensive example demonstrating the effortless creation of an HTTP server, deployment\nof a handler using stubs, creation of an HTTP client module, and the sending of both GET and POST\nrequests. Requesting data is simplified to the extent that it merely involves invoking lambdas that\nreturn HTTP responses encapsulated in JsObj.\n\n```java\n\n@ExtendWith(VertxExtension.class)\npublic class HttpClientMethodsTests {\n\n    private static final int PORT = Port.number.incrementAndGet();\n    static HttpClientModule httpClient;\n\n    @BeforeAll\n    public static void prepare(final Vertx vertx,\n                               final VertxTestContext context\n                              ) {\n        VertxRef vertxRef = new VertxRef(vertx);\n        vertxRef.registerConsumer(VertxRef.EVENTS_ADDRESS,\n                                  System.out::println\n                                 );\n        httpClient = new HttpClientModule(new HttpClientOptions().setDefaultHost(\"0.0.0.0\"),\n                                          \"myhttp-client\");\n\n        HttpRespStub mockReqResp =\n                HttpRespStub.when(ALWAYS)\n                            .setBodyResp(n -\u003e body -\u003e req -\u003e JsObj.of(\"req_method\",\n                                                                      JsStr.of(req.method()\n                                                                                  .name()\n                                                                              ),\n                                                                      \"req_body\",\n                                                                      JsStr.of(body.toString()),\n                                                                      \"req_uri\",\n                                                                      JsStr.of(req.uri())\n                                                                     )\n                                                                  .toPrettyString()\n                                        )\n                            .setHeadersResp(HttpHeadersRespStub.JSON_CONTENT_TYPE);\n\n        MapExp.seq(\"json-values-codecs\",\n                   vertxRef.deployVerticle(new RegisterJsValuesCodecs()),\n                   \"http-server\",\n                   new HttpServerBuilder(vertx,\n                                         new HttpReqHandlerStub(mockReqResp)\n                   ).create(PORT),\n                   \"http-client\",\n                   vertxRef.deployVerticle(httpClient)\n                  )\n              .get()\n              .onComplete(Verifiers.pipeTo(context));\n\n\n    }\n\n\n    @Test\n    public void testGet(VertxTestContext context) {\n        VIO\u003cJsObj\u003e getReq = httpClient.get.apply(HttpHeaders.headers()\n                                                            .set(\"method\",\n                                                                 \"get\"\n                                                                ),\n                                                 new GetReq().port(PORT)\n                                                             .uri(\"example\")\n                                                );\n        Verifiers.\u003cJsObj\u003everifySuccess(resp -\u003e {\n                     int status = HttpResp.STATUS_CODE_LENS.get.apply(resp);\n                     String bodyResp = HttpResp.STR_BODY_LENS.get.apply(resp);\n                     JsObj bodyJsObj = JsObj.parse(bodyResp);\n                     return bodyJsObj.getStr(\"req_method\")\n                                     .equals(\"GET\")\n                            \u0026\u0026 bodyJsObj.getStr(\"req_uri\")\n                                        .equals(\"example\")\n                            \u0026\u0026 bodyJsObj.getStr(\"req_body\").isEmpty()\n                            \u0026\u0026 status == 200;\n                 })\n                 .accept(getReq,\n                         context\n                        );\n\n    }\n\n    @Test\n    public void testPost(VertxTestContext context) {\n        VIO\u003cJsObj\u003e postReq = httpClient.post.apply(HttpHeaders.headers()\n                                                              .set(\"method\",\n                                                                   \"post\"\n                                                                  ),\n                                                   new PostReq(\"hi\".getBytes())\n                                                           .port(PORT)\n                                                           .uri(\"example\")\n                                                  );\n        Verifiers.\u003cJsObj\u003everifySuccess(resp -\u003e {\n                     Integer status = HttpResp.STATUS_CODE_LENS.get.apply(resp);\n                     String bodyResp = HttpResp.STR_BODY_LENS.get.apply(resp);\n                     JsObj bodyJsObj = JsObj.parse(bodyResp);\n                     return bodyJsObj.getStr(\"req_method\")\n                                     .equals(\"POST\")\n                            \u0026\u0026 bodyJsObj.getStr(\"req_uri\")\n                                        .equals(\"example\")\n                            \u0026\u0026 bodyJsObj.getStr(\"req_body\")\n                                        .equals(\"hi\")\n                            \u0026\u0026 status == 200;\n                 })\n                 .accept(postReq,\n                         context\n                        );\n    }\n\n\n}\n\n\n```\n\nYou can harness the power of the `VIO` API to make requests with retry policies, as illustrated in\nthe following example:\n\n```java\n\n@ExtendWith(VertxExtension.class)\npublic class HttpClientTestRetryOnFailure {\n\n    private static final int PORT = Port.number.incrementAndGet();\n\n    static HttpClientModule httpClient;\n    static VertxRef vertxRef;\n    static HttpReqHandlerStub httpReqHandlerStub;\n\n    @BeforeAll\n    public static void prepare(final Vertx vertx,\n                               final VertxTestContext context\n                              ) {\n        vertxRef = new VertxRef(vertx);\n\n        vertxRef.registerConsumer(VertxRef.EVENTS_ADDRESS,\n                                  System.out::println\n                                 );\n        httpClient = new HttpClientModule(new HttpClientOptions().setDefaultHost(\"0.0.0.0\"),\n                                          \"myhttp-client\");\n\n        HttpRespStub mockReqErrorResp =\n                HttpRespStub.when((n, req) -\u003e n \u003c= 3)\n                            .setStatusCodeResp(n -\u003e body -\u003e req -\u003e 500)\n                            .setBodyResp(n -\u003e body -\u003e req -\u003e \"{}\")\n                            .setHeadersResp(HttpHeadersRespStub.JSON_CONTENT_TYPE);\n\n        HttpRespStub mockReqErrorSuccess =\n                HttpRespStub.when((n, req) -\u003e n \u003e 3)\n                            .setStatusCodeResp(n -\u003e body -\u003e req -\u003e 200)\n                            .setBodyResp(n -\u003e body -\u003e req -\u003e \"{}\")\n                            .setHeadersResp(HttpHeadersRespStub.JSON_CONTENT_TYPE);\n\n        httpReqHandlerStub = new HttpReqHandlerStub(mockReqErrorResp,\n                                                    mockReqErrorSuccess\n        );\n        TripleExp.seq(vertxRef.deployVerticle(new RegisterJsValuesCodecs()),\n                      new HttpServerBuilder(vertx,\n                                            httpReqHandlerStub\n                      ).create(PORT),\n                      vertxRef.deployVerticle(httpClient)\n                     )\n                 .get()\n                 .onComplete(Verifiers.pipeTo(context));\n    }\n\n\n    @Test\n    public void test_retries(VertxTestContext context) {\n        Verifiers.\u003cJsObj\u003everifySuccess(resp -\u003e {\n                     Integer status = HttpResp.STATUS_CODE_LENS.get.apply(resp);\n                     return status == 200;\n                 })\n                 .accept(httpClient.get.apply(new GetReq().port(PORT)\n                                                          .uri(\"example\")\n                                             )\n                                       .repeat(resp -\u003e HttpResp.STATUS_CODE_LENS.get.apply(resp) == 500,\n                                               limitRetries(3)\n                                              ),\n                         context\n                        );\n\n    }\n\n\n    @Test\n    public void test_retries_constant_delay(VertxTestContext context) {\n        httpReqHandlerStub.resetCounter();\n\n        long tic = Instant.now()\n                          .toEpochMilli();\n\n        VIO\u003cJsObj\u003e getReq =\n                httpClient.get.apply(new GetReq().port(PORT)\n                                                 .uri(\"example\")\n                                    )\n                              .repeat(resp -\u003e HttpResp.STATUS_CODE_LENS.get.apply(resp) == 500,\n                                      limitRetries(3).append(constantDelay(vertxRef.delay(Duration.ofMillis(100))))\n                                     );\n\n        Verifiers.\u003cJsObj\u003everifySuccess(resp -\u003e {\n            long elapsed = Instant.now()\n                                  .toEpochMilli() - tic;\n            int status = HttpResp.STATUS_CODE_LENS.get.apply(resp);\n            System.out.println(elapsed);\n            return status == 200 \u0026\u0026 elapsed \u003e= 300;\n        }).accept(getReq,\n                  context\n                 );\n    }\n\n}\n\n\n```\n\n## \u003ca name=\"oauth-client\"\u003e\u003ca/\u003e Oauth Http client\n\nCreating an HTTP client with OAuth client credentials support is made exceptionally straightforward,\nliberating you from the intricacies of obtaining and refreshing tokens. The provided code showcases\nhow to easily set up a resilient HTTP client using the VIO API, complete with token retrieval and\nautomatic refresh. This not only streamlines the process but also enables you to seamlessly\nintegrate retry policies and other reliability features.\n\n```java\n\n@ExtendWith(VertxExtension.class)\npublic class ClientCredentialsModuleTest {\n\n    static ClientCredentialsModule httpClient;\n    static ClientCredentialsModuleBuilder builder;\n    static VertxRef vertxRef;\n    static int port = Port.number.incrementAndGet();\n\n\n    @BeforeAll\n    public static void prepare(final Vertx vertx,\n                               final VertxTestContext context\n                              ) {\n        builder =\n                new ClientCredentialsModuleBuilder(new HttpClientOptions().setDefaultPort(port)\n                                                                          .setDefaultHost(\"0.0.0.0\"),\n                                                   \"my-httpclient\",\n                                                   new AccessTokenRequest(\"client_id\",\n                                                                          \"client_secret\"\n                                                   ));\n\n        httpClient = builder.createModule();\n\n        vertxRef = new VertxRef(vertx);\n\n        vertxRef.registerConsumer(VertxRef.EVENTS_ADDRESS,\n                                  System.out::println\n                                 );\n\n        PairExp.seq(vertxRef.deployVerticle(new RegisterJsValuesCodecs()),\n                    vertxRef.deployVerticle(httpClient)\n                   )\n               .onComplete(Verifiers.pipeTo(context))\n               .get();\n\n    }\n\n\n    @Test\n    public void test_get_success_after_three_retries_getting_token(Vertx vertx,\n                                                                   VertxTestContext context\n                                                                  ) {\n\n\n        builder.setAccessTokenReqRetryPolicy(e -\u003e true,\n                                             RetryPolicies.limitRetries(3)\n                                            );\n\n        List\u003cHttpRespStub\u003e httpRespStubs =\n                List.of(when(REQ_LET.apply(3))\n                                .setBodyResp(HttpBodyRespStub.cons(JsObj.of(\"token_found\",\n                                                                            FALSE\n                                                                           )\n                                                                  )\n                                            )\n                                .setStatusCodeResp(HttpStatusCodeRespStub._401)\n                                .setHeadersResp(HttpHeadersRespStub.JSON_CONTENT_TYPE),\n                        when(FORTH_REQ)\n                                .setBodyResp(HttpBodyRespStub.cons(JsObj.of(\"token_found\",\n                                                                            TRUE,\n                                                                            \"access_token\",\n                                                                            JsStr.of(\"foooo\")\n                                                                           )\n                                                                  )\n                                            )\n                                .setStatusCodeResp(HttpStatusCodeRespStub._200)\n                                .setHeadersResp(HttpHeadersRespStub.JSON_CONTENT_TYPE),\n                        when(REQ_GT.apply(4))\n                                .setBodyResp(HttpBodyRespStub.cons(JsObj.of(\"name\",\n                                                                            JsStr.of(\"Rafael\")\n                                                                           ))\n                                            )\n                                .setStatusCodeResp(HttpStatusCodeRespStub._200)\n                                .setHeadersResp(HttpHeadersRespStub.JSON_CONTENT_TYPE));\n\n        VIO\u003cJsObj\u003e getReq = httpClient.getOauth.apply(new GetReq().uri(\"/name\"));\n\n        new HttpServerBuilder(vertx,\n                              new HttpReqHandlerStub(httpRespStubs\n\n                              )\n        ).create(port)\n         .get()\n         .onSuccess(server -\u003e {\n             Verifiers.\u003cJsObj\u003everifySuccess(resp -\u003e HttpResp.STATUS_CODE_LENS.get.apply(resp) == 200)\n                      .accept(getReq,\n                              context\n                             );\n         });\n\n    }\n\n  @Test\n  public void test_get_success_after_three_retries(Vertx vertx,\n                                                   VertxTestContext context\n                                                  ) {\n\n\n    List\u003cHttpRespStub\u003e httpRespStubs =\n            List.of(when(REQ_LET.apply(3))\n                            .setBodyResp(HttpBodyRespStub.cons(JsObj.of(\"token_found\",\n                                                                        FALSE\n                                                                       )\n                                                              )\n                                        )\n                            .setStatusCodeResp(HttpStatusCodeRespStub._401),\n                    when(FORTH_REQ)\n                            .setBodyResp(HttpBodyRespStub.cons(JsObj.of(\"token_found\",\n                                                                        TRUE,\n                                                                        \"access_token\",\n                                                                        JsStr.of(\"foooo\")\n                                                                       )\n                                                              )\n                                        )\n                            .setStatusCodeResp(HttpStatusCodeRespStub._200),\n                    when(REQ_LET.apply(7))\n                            .setBodyResp(c -\u003e body -\u003e req -\u003e {\n                                           req.response().close();\n                                           return \"{}\";\n                                         }\n                                        )\n                            .setStatusCodeResp(HttpStatusCodeRespStub._500),\n                    when(REQ_GT.apply(7))\n                            .setBodyResp(HttpBodyRespStub.cons(JsObj.of(\"name\",\n                                                                        JsStr.of(\"Rafael\")\n                                                                       ))\n                                        )\n                            .setStatusCodeResp(HttpStatusCodeRespStub._200));\n\n    VIO\u003cJsObj\u003e getReq = httpClient.getOauth.apply(new GetReq().uri(\"/name\"))\n                                           .retry(RetryPolicies.limitRetries(3));\n\n    new HttpServerBuilder(vertx,\n                          new HttpReqHandlerStub(httpRespStubs)\n    ).create(port)\n     .get()\n     .onSuccess(server -\u003e {\n       Verifiers.\u003cJsObj\u003everifySuccess(resp -\u003e HttpResp.STATUS_CODE_LENS.get.apply(resp) == 200)\n                .accept(getReq,\n                        context\n                       );\n     });\n\n\n  }\n}\n\n```\n\n## \u003ca name=\"http-server\"\u003e\u003ca/\u003e Http server\n\nTO be documented but implemented!\n\n## \u003ca name=\"testing\"\u003e\u003ca/\u003e Testing\n\n### \u003ca name=\"vio-stubs\"\u003e\u003ca/\u003e VIO stubs\n\nTO be documented but implemented!\n\n### \u003ca name=\"http-stubs\"\u003e\u003ca/\u003e Http server stubs\n\nTO be documented but implemented!\n\n## \u003ca name=\"requirements\"\u003e\u003ca/\u003e Requirements\n\nJava 17 or greater\n\n## \u003ca name=\"installation\"\u003e\u003ca/\u003e Installation\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.imrafaelmerino\u003c/groupId\u003e\n    \u003cartifactId\u003evertx-effect\u003c/artifactId\u003e\n    \u003cversion\u003e4.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\n\nJava 21 or greater\n\n## \u003ca name=\"installation\"\u003e\u003ca/\u003e Installation\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.imrafaelmerino\u003c/groupId\u003e\n    \u003cartifactId\u003evertx-effect\u003c/artifactId\u003e\n    \u003cversion\u003e5.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\n\n## \u003ca name=\"rp\"\u003e\u003ca/\u003e Related projects\n\n- [vertx-mongodb-effect](https://github.com/imrafaelmerino/vertx-mongodb-effect)\n- [vertx-values](https://github.com/imrafaelmerino/vertx-values)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimrafaelmerino%2Fvertx-effect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimrafaelmerino%2Fvertx-effect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimrafaelmerino%2Fvertx-effect/lists"}