{"id":18342553,"url":"https://github.com/danhyun/mastering-async-ratpack","last_synced_at":"2025-07-05T02:03:34.571Z","repository":{"id":141748480,"uuid":"92835673","full_name":"danhyun/mastering-async-ratpack","owner":"danhyun","description":"Notes on async behavior in Ratpack","archived":false,"fork":false,"pushed_at":"2017-05-31T13:35:52.000Z","size":918,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-06T07:34:52.943Z","etag":null,"topics":["async","gr8conf","greach","groovy","java","jvm","netty","nio","ratpack"],"latest_commit_sha":null,"homepage":"https://danhyun.github.io/mastering-async-ratpack/","language":"Groovy","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/danhyun.png","metadata":{"files":{"readme":"README.adoc","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":"2017-05-30T13:28:13.000Z","updated_at":"2018-12-08T17:54:36.000Z","dependencies_parsed_at":"2023-07-29T01:36:27.561Z","dependency_job_id":null,"html_url":"https://github.com/danhyun/mastering-async-ratpack","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/danhyun/mastering-async-ratpack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danhyun%2Fmastering-async-ratpack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danhyun%2Fmastering-async-ratpack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danhyun%2Fmastering-async-ratpack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danhyun%2Fmastering-async-ratpack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danhyun","download_url":"https://codeload.github.com/danhyun/mastering-async-ratpack/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danhyun%2Fmastering-async-ratpack/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263645556,"owners_count":23494005,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","gr8conf","greach","groovy","java","jvm","netty","nio","ratpack"],"created_at":"2024-11-05T20:31:32.175Z","updated_at":"2025-07-05T02:03:34.547Z","avatar_url":"https://github.com/danhyun.png","language":"Groovy","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Mastering Async with Ratpack\nDaniel Hyun \u003chttps://github.com/danhyun[icon:github[] @danhyun] https://twitter.com/LSpacewalker[icon:twitter[] @LSpacewalker]\u003e\n2017 June 1\n\n== Details\n\nCode: https://github.com/danhyun/mastering-async-ratpack/\n\nNotes: https://danhyun.github.io/mastering-async-ratpack/\n\nPDF: https://danhyun.github.io/mastering-async-ratpack/notes.pdf\n\n== Need for Async\n\nWe want to do more with less.\n\nResources are expensive: You pay for memory/compute/network usage\n\n----\ncost    | $$$                | $$      | $\n--------|-------------------------------------------\nmethod  | Multiple Processes | threads | event loop\nexample | apache             | servlet | netty\n----------------------------------------------------\n----\n\nhttp://www.kegel.com/c10k.html\n\n== Scalability\n\nimage::dhh-1.png[]\n\nimage::dhh-2.png[]\n\nSource: https://twitter.com/dhh/status/649260226521210880\n\n2000 peak rps for 30 servers sounds expensive...\n\nMeanwhile at Apple...\n\nimage::apple.png[]\n\nhttps://speakerdeck.com/normanmaurer/connectivity\n\nThat's pretty big scale... Still expensive xD\n\nTakeaways::\n* Need async to reduce footprint, especially important with each service that gets deployed\n* Async is difficult, not everyone knows Netty\n* Need to find balance between scale and usability\n\nSome arguments::\n\"Bottle neck is db calls\"\n\nThis depends on the nature of your application.\nNot every app is a CRUD app, and even those that are serve of static/in memory content\n\n\n== State of async in Java land\n\nThreads/Executors/Mutexes/AtomicReferences\n\nWe have the means, but...\n\n* Testing\n* Readability, ease of understanding\n* Resource sharing (memory, system resources)\n\n== Async is hard\n\n* Non-deterministic\n* Callback hell\n* Error handling/propagation\n\nHow many times have you forgotten to send a response to the user after making some async call in nodejs or Playframework?\nSusceptible to brain damage trying to track down what is happening.\n\n== Libraries to the rescue\n\nIt's no secret that concurrency is hard.\nDifferent concurrency models have found their way into the JVM ecosystem over the years:\n\n* Futures (callbacks)\n* Queues\n* Fork/Join\n* Actors\n* Disruptor\n* Continuations (Quasar/parallel universe)\n\nLast few models try to limit or eliminate resource sharing between running threads.\n\nhttps://shipilev.net/blog/2014/jmm-pragmatics/\n\nhttps://shipilev.net/blog/2016/close-encounters-of-jmm-kind/\n\n== Ratpack\n\nRatpack is async and non-blocking (Netty Rocks!)\n\nIt provides its own concurrency model (execution model) for managing and handling web requests\n\n== Ratpack Thread Model\n\nNetty's event loop used for compute bound code:\n\n* Entry point to executing your Chain\n* Handling NIO events (e.g. read/write)\n* Scheduling/Coordinating executions\n\nWARNING: Never block the compute thread!\nDon't make any syscalls that block CPU until operation completes!\n\nThread pool for running blocking code\n\n* For long running computations or code that blocks CPU until further notice\n\n== Ratpack Execution\n\nAn execution is a collection of units of work to be executed and managed by Ratpack\n\nThese units of work are called execution segment\n\nAny http handling code is always done within an execution\n\nUsers schedule execution segments via Promise/Operation/Blocking facilities\n\nExecution segments for a given execution are always executed on the same thread\n\nhttp://ldaley.com/post/97376696242/ratpack-execution-model-part-1\n\nhttp://ldaley.com/post/102495950257/ratpacks-execution-model-in-practice\n\n== Ratpack Async Primitives\n\n* Promise\n* Operation\n* Blocking\n\nAll Ratpack primitives are implemented with the Execution api\n\nIf you can't find a method in Promise/Operation/Blocking you can always build it yourself!\n\n== Promises/Operations\n\n* Creating Promises schedules execution segments\n* Promises are executed in the order they were created (except for forked promises)\n* They run on cpu or blocking threads, determined at time of creation.\n* Easy to adapt with other async libraries (rx-mongo, thread pools)\n\n== Testability\n\nRatpack provides ExecHarness test fixture for easy testability\n\nIt allows you to run executions without starting a Ratpack server\n\n== ExecHarness\n\n* java.lang.AutoCloseable\n* Convenience methods that let ExecHarness manage start/close\n\nCan use Java 7 try-with-resources using new Parrot project (Bridging the gap between Groovy and Java syntax)\n\n== ExecHarness#yield\n\nGreat for unit testing, executes a given promise and returns the value from the Promise in a blocking fashion\n\nAutomatically subscribes to the Promise, no need to call Promise#then\n\nComes in two varieties:\n\nExecHarness#yield::\n  Keeps the ExecHarness running\n\n[source, groovy]\n----\ninclude::{test}/ExecHarnessSpec.groovy[tags=execHarnessYield,indent=0]\n----\n\u003c1\u003e Get an instance of ExecHarness\n\u003c2\u003e Invoke yield on the instance\n\u003c3\u003e Return a Ratpack Promise from the Closure\n\u003c4\u003e Extract the value from the Promise\n\u003c5\u003e Remember to clean up after ourselves!\n\nExecHarness.yieldSingle::\n  Creates and cleans up ExecHarness on each invocation\n\n[source, groovy]\n----\ninclude::{test}/ExecHarnessSpec.groovy[tags=execHarnessYieldSingle, indent=0]\n----\n\u003c1\u003e Invoke static method ExecHarness.yieldSingle\n\u003c2\u003e Return Ratpack Promise from Closure to ExecHarness\n\u003c3\u003e Extract value from Promise\n\n== ExecHarness#run\n\nGreat for seeing Promises in action, closer to coding experience in Ratpack code\n\nNo return value\n\nPromises are not automatically subscribed\n\nComes in two varieties:\n\nExecHarness#run::\n  Starts and blocks execution until completed\n\n[source, groovy]\n----\ninclude::{test}/ExecHarnessSpec.groovy[tags=execHarnessRun, indent=0]\n----\n\u003c1\u003e Get an instance of ExecHarness\n\u003c2\u003e Pass a closure to ExecHarness#run method\n\u003c3\u003e Create Ratpack Promise\n\u003c4\u003e Subscribe to the Promise\n\u003c5\u003e Use Groovy Power Assert to make assertion on value from Promise\n\nExecHarness.runSingle::\n  Creates and cleans up ExecHarness on each invocation\n\n[source, groovy]\n----\ninclude::{test}/ExecHarnessSpec.groovy[tags=execHarnessRunSingle, indent=0]\n----\n\u003c1\u003e Invoke static method ExecHarness.runSingle()\n\u003c2\u003e Create Ratpack Promise\n\u003c3\u003e Subscribe to Ratpack Promise\n\u003c4\u003e Use Groovy Power assert to assert the value resolved from the Promise\n\nNOTE: When making assertions from within closures you need to make sure that you use Groovy Power Assert. Spock does not apply assertions to values from within the Closure\n\n== Advanced Async\n\n* Promised\n* SerialBatch/ParallelBatch\n\n== Examples\n\n.Creating promise\n\n.Promise.sync\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=create-sync,indent=0]\n----\n\nPromises don't execute when you create them.\n\n.Promise#then\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=unmanaged,indent=0]\n----\n\nPromises execute with you subscribe via Promise#then\n\n.Promise.sync yield\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=yieldSync,indent=0]\n----\n\nPromises need to execute in Ratpack managed thread.\n\nExecHarness provides Ratpack managed threads as do RatpackServers of all varieties.\n\n.Promise.sync run\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=runSync,indent=0]\n----\n\n.Promise.value\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-value,indent=0]\n----\n\u003c1\u003e Promise.value creates promise from an already available value, unlike Promise.sync which will wait until the promise is subscribed in order to generate the value.\n\n.Blocking.get()\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=blocking-get,indent=0]\n----\n\n.Blocking runs on different threadpool\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=blocking-thread-name,indent=0]\n----\n\n.Promise.async\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=third-party-async,indent=0]\n\ninclude::{test}/PromiseSpec.groovy[tags=promise-async,indent=0]\n----\n\nYou can think of Operations as a `Promise\u003cVoid\u003e`, they don't share a common type but there are ways to switch back and forth betwteen Promises and Operations\n\n.Operation.of\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=operation,indent=0]\n----\n\u003c1\u003e Factory to queue up an Operation\n\u003c2\u003e Note that Operations don't return anything so there is nothing to receive in the subscriber\n\n.Operation to Promise\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=operation-to-promise,indent=0]\n----\n\u003c1\u003e Invoke Operation#promise to create a Promise\u003cVoid\u003e\n\u003c2\u003e See that we get null\n\u003c3\u003e Note that we can still work with this transformed Promise\n\n.Promise to Operation\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-to-operation,indent=0]\n----\n\u003c1\u003e We can turn a Promise into an Operation, however note that we still get the previous Promise value\n\u003c2\u003e See that Operation doesn't return anything\n\n=== Anatomy of a Promise\n\n[source, groovy]\n----\nPromise.sync {\n  return 'hello'\n}.map { s -\u003e\n  s.toUpperCase()\n}.then { s -\u003e\n  ctx.render(s)\n}\n----\n\nAs the methods `sync`, `map`, `then` are invoked, execution segments get queued.\n\n[source, groovy]\n----\n[{Promise.sync { return 'hello' }.map { s -\u003e s.toUpperCase() }.then { s -\u003e ctx.render(s) }}]\n           |                       |                            |\n           |                       |                            |\n           v                       |                            |\n[{}, { return 'hello' }]           v                            |\n[{}, { return 'hello' }, { s -\u003e s.toUpperCase() }]              v\n[{}, { return 'hello' }, { s -\u003e s.toUpperCase() }, { s -\u003e ctx.render(s) }]\n----\n\nThe output from the first promise is then used as input for the second segment, et cetera.\n\n[source, groovy]\n----\nPromise.sync {\n  return 'hello'\n}.flatMap { s -\u003e\n  Promise.sync {\n    s.toUpperCase()\n  }\n}.then { s -\u003e\n  ctx.render(s)\n}\n----\n\n\n[source, groovy]\n----\n[{Promise.sync { return 'hello' }.flatMap { s -\u003e Promise.sync { s.toUpperCase() } }.then { s -\u003e ctx.render(s) }}]\n           |                       |                                                  |\n           |                       |                                                  |\n           v                       |                                                  |\n[{}, { return 'hello' }]           v                                                  |\n[{}, { return 'hello' }, { s -\u003e Promise.sync { s -\u003e s.toUpperCase() }}]               v\n[{}, { return 'hello' }, { s -\u003e Promise.sync { s -\u003e s.toUpperCase() }}, { s -\u003e ctx.render(s)}]\n                                         |\n                                         |_____________________________________\n                                                                              |\n                                                                              v\n[{}, { return 'hello' }, { s -\u003e Promise.sync { s -\u003e s.toUpperCase() }}, { s -\u003e s.toUpperCase() }, { s -\u003e ctx.render(s)}]\n----\n\nFlatmap will queue up the promise, if you use map instead it just passes the promise to the next execution segment in the queue.\n\n.Handling errors\n\nExceptions can be thrown from Promises\n\n.Exception thrown from Promise\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=exception,indent=0]\n----\n\nBut we can handle it and short circuit\n\n.Promise#onError\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-onerror,indent=0]\n----\n\nOr we can handle and continue processing\n\n.Promise#mapError\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-maperror,indent=0]\n----\n\nNOTE: Promises are immutable, methods like `Promise#map` always return new promises\n\n.Promises are immutable\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=immutable,indent=0]\n----\n\u003c1\u003e Promise#onError returns a _new_ Promise\n\u003c2\u003e Promise#map returns a _new_ Promise\n\n.Promise api allows you to chain promise manipulation\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=mapPromise,indent=0]\n----\n\u003c1\u003e Promise#onError returns a new Promise\n\nError mapping also available in flatmap flavor Promise#flatmapError.\n\n.Transforming Promises\n\n.Promise#map\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-map,indent=0]\n----\n\u003c1\u003e Promise#map runs on compute thread, don't block here\n\n.Promise#flatMap\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-flatmap,indent=0]\n----\n\u003c1\u003e Promise#flatMap runs on compute thread, don't block here\n\u003c2\u003e Since Blocking#get returns a Promise, need to use Promise#flatMap in order to \"unpack\" the nested promise and continue working with the value as in \u003c3\u003e\n\n.Promise#blockingMap\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-blockingmap,indent=0]\n----\n\u003c1\u003e A convenience method for executing blocking code inline without having to do `Promise#flatMap { Blocking.get {} }` as in the previous example\n\n.Promise#flatMap with async\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-flatmap-async,indent=0]\n----\n\u003c1\u003e Integrating with an externally managed threadpool/async library\n\n.Composing promises\n\n.Promise#left/right\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=promise-composition-pair,indent=0]\n----\n\u003c1\u003e Imagine some blocking jdbc lookup by name\n\u003c2\u003e Promise#right takes the result of the previous promise and creates a tuple like graph datastructure called Pair. The value returned from the closure/lambda is then pushed to the \"right\" position of the Pair\n\u003c3\u003e Imagine some in memory lookup for interests by user that we just looked up from \u003c1\u003e\n\u003c4\u003e The result from the previous call is of type `Pair\u003cA, B\u003e` where `A` is the result of the `Blocking.get{}` call and `B` is ther result of `Promise#right` call\n\nPairs are handy when working with small number of arguments.\n\nYou can also nest Pairs, you can have something like `Pair\u003cPair\u003cA, B\u003e, C\u003e` but this quickly becomes hard to track.\n\nIf only Java had tuple support :(\n\nThis is also available in flatMap flavor Promise#flatLeft/flatRight\n\n* ParallelBatch, SerialBatch\nYou'll often want to take a list of promises and transform them into a list of resolved values. ParallelBatch and SerialBatch help achieve this.\n\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=batch-basic,indent=0]\n----\n\u003c1\u003e Simulate price lookup in a blocking manner\n\u003c2\u003e Use a Groovy range to generate a list of promises to lookup the price for the given id\n\u003c3\u003e Invoke either `SerialBatch.of` or `ParallelBatch.of` and yield a `Promise\u003cList\u003cMap\u003cString, Object\u003e\u003e\u003e`\n\u003c4\u003e Use the handy value as a single list\n\u003c5\u003e Spock datatable for making `batch` pluggable\n\nBatch is great for when you have `List\u003cPromise\u003cA\u003e\u003e` but want to work with `Promise\u003cList\u003cA\u003e\u003e` in subsequent calculations.\n\nForking execution\n\n.Promise#fork\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=fork,indent=0]\n----\n\u003c1\u003e Convenience closure for printing and adding to list\n\u003c2\u003e Add foo in a blocking manner\n\u003c3\u003e Add bar in async manner\n\u003c4\u003e Fork the bar async promise\n\u003c5\u003e Assert that bar was entered before foo\n\nNOTE: When forking Promises, they execute immediately!\n\n\"Flow control\"\n\n.Promise#mapIf\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=map-if,indent=0]\n----\n\u003c1\u003e Convenience closure\n\u003c2\u003e Example of using `Promise#mapIf(Predicate, Function)`, only maps if predicate passes\n\u003c3\u003e Execute these promises in parallel\n\u003c4\u003e Assert that our list is fizzbuzzed correctly\n\nAlso available in flatMap variety Promise#flatMapIf\n\nOther useful methods:\n\n* Promise#onNull\n* Promise#route\n\nWARNING: Promise#route is a terminating call, the rest of the promise chain is no longer executed!!\n\n.Promise#route(Predicate, Action)\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=route,indent=0]\n----\n\u003c1\u003e Note that we terminate the promise chain here if predicate passes\n\n.Throttling Promises\nThrottle acts as a semaphore only allowing n number of promises to run in parallel.\n\n.Promise#throttled\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=throttle,indent=0]\n----\n\u003c1\u003e Declare a throttle of size 3\n\u003c2\u003e Make sure that the promise we submit to the ParallelBatch is throttled\n\n.Sample output\n----\n2017-05-30T07:42:48.294 EXECUTING FIZZBUZZ for 4\n2017-05-30T07:42:48.308 EXECUTING FIZZBUZZ for 5\n2017-05-30T07:42:48.294 EXECUTING FIZZBUZZ for 1\n2017-05-30T07:42:50.367 EXECUTING FIZZBUZZ for 10\n2017-05-30T07:42:50.367 EXECUTING FIZZBUZZ for 12\n2017-05-30T07:42:50.367 EXECUTING FIZZBUZZ for 2\n2017-05-30T07:42:52.371 EXECUTING FIZZBUZZ for 13\n2017-05-30T07:42:52.371 EXECUTING FIZZBUZZ for 9\n2017-05-30T07:42:52.371 EXECUTING FIZZBUZZ for 3\n2017-05-30T07:42:54.374 EXECUTING FIZZBUZZ for 7\n2017-05-30T07:42:54.374 EXECUTING FIZZBUZZ for 6\n2017-05-30T07:42:54.374 EXECUTING FIZZBUZZ for 11\n2017-05-30T07:42:56.377 EXECUTING FIZZBUZZ for 8\n2017-05-30T07:42:56.377 EXECUTING FIZZBUZZ for 15\n2017-05-30T07:42:56.377 EXECUTING FIZZBUZZ for 14\n----\n\nYou can see there is a tight grouping of 3 per time period\n\n.Spying\n\nIf you wish to observe items as they get processed in the promise chain, you can make use of Promise#wiretap\n\n.Promise#wiretap\n[source,groovy]\n----\ninclude::{test}/PromiseSpec.groovy[tags=wiretap,indent=0]\n----\n\u003c1\u003e Invoke wiretap method\n\u003c2\u003e Note that we have to invoke Result#getValue in order to get the value produced from the previous Promise\n\u003c3\u003e Note that the next processor gets the same result as the wiretap\n\n\n== Best practices\n\n* Avoid multiple then blocks\n* Try to linearize/flatten data flow\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanhyun%2Fmastering-async-ratpack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanhyun%2Fmastering-async-ratpack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanhyun%2Fmastering-async-ratpack/lists"}