From aa81edfe67745d1d1b5e18241466dea24308dde1 Mon Sep 17 00:00:00 2001 From: Aldwin Vlasblom Date: Sun, 3 Dec 2023 13:56:03 +0100 Subject: [PATCH] Improve the introduction to the library in the README I was reading the intro, realizing it assumed a lot of up-front knowledge and doesn't really get the benefits across. I hope these changes improve it. --- README.md | 67 ++++++++++++++++++++++++++------------ example/services/server.ts | 3 ++ package.json | 2 +- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c55439f..92faf15 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,43 @@ # FP-TS Bootstrap -This is a module aimed at application bootstrapping using types from [fp-ts][]. -Its ideas and most of the code were ported from the [fluture-hooks][] library. +> Service orchestration made functional -This module mainly provides a [Bracket type](#bracket) with accompanying type -class instances. The Bracket type is a drop-in replacement for the Cont type -from [fp-ts-cont][], but specialized in returning `TaskEither`. This solves the -problem stipulated at the end of [application bootstrapping with fp-ts][] by -allowing the return type to be threaded through the program. Furthermore, it -makes the `ApplicativePar` instance possible, which allows for parallel -composition of bracketed resources. +An application bootstrapping framework around Monadic composition of application +services. Based on types from [fp-ts][] with ideas that were tried and tested +with the [fluture-hooks][] library. -Besides the Bracket type, this module also provides a [Service type](#service) -which is a small layer on top for managing dependencies through the Reader monad. +## Features + +- 🧑‍🤝‍🧑 A "service" is a combination of its acquisition and disposal logic +- 🚦 Easy management of asynchronously acquired services +- 🛬 Resources are disposed gracefully after consumption +- 🪂 Resources are disposed even if the consumption program crashes +- 🔀 Automatic sequencing of acquisition and disposal of dependent services +- 🛣️ Faster app startup times with parallel acquisition of independent services +- 🏗 Use the Monadic API to compose larger services out of multiple smaller ones +- 🧃 Monads all the way down! Learn more in [this article about the approach][] [fp-ts]: https://gcanti.github.io/fp-ts/ [fluture-hooks]: https://github.com/fluture-js/fluture-hooks -[fp-ts-cont]: https://github.com/joshburgess/fp-ts-cont -[application bootstrapping with fp-ts]: https://dev.to/avaq/application-bootstrapping-with-fp-ts-59b5 +[this article about the approach]: https://dev.to/avaq/application-bootstrapping-with-fp-ts-59b5 ## Example -Define your service. See the full example in -[`./example/services/server.ts`](./example/services/server.ts). +We start with a service definition. It consists of an acquisition function, +a disposal function, bundled with the `bracket` utility. + +See the full example in [`./example/services/server.ts`](./example/services/server.ts). ```ts export const withServer: Service.Service = ( ({port, app}) => Bracket.bracket( + // Acquire: () => new Promise(resolve => { const server = HTTP.createServer(app); server.listen(port, () => resolve(E.right(server))); }), + + // Dispose: server => () => new Promise(resolve => { server.close((e: unknown) => resolve( e instanceof Error ? E.left(e) : E.right(undefined) @@ -40,7 +47,8 @@ export const withServer: Service.Service = ( ); ``` -Combine multiple such services with ease using Do notation. See the full example +Multiple services can be combined in a host of different ways to form larger +services. One powerful way to do so is with Do notation. See the full example in [`./example/services/index.ts`](./example/services/index.ts). ```ts @@ -60,7 +68,14 @@ export const withServices = pipe( ); ``` -Consume your service. See the full example in [`./example/index.ts`](./example/index.ts). +A service is really just a function that takes a callback: The program that +"consumes" the service. Consumption of `withServer` is as easy as +`withServer(server => ...)` and `withServices` is just +`withServices(({server, logger}) => ...)`. + +So let's consume the `withServices` service. + +See the full example in [`./example/index.ts`](./example/index.ts). ```ts const program = withServices(({server, logger}) => pipe( @@ -72,12 +87,16 @@ const program = withServices(({server, logger}) => pipe( )); ``` -And finally, run your program: +The consumption of a service returns an [fp-ts `TaskEither`][], which can itself +be monadically composed with other Tasks, or eventually consumed. This too is +just a function that returns a Promise: ```ts program().then(E.fold(console.error, console.log), console.error); ``` +[fp-ts `TaskEither`]: https://gcanti.github.io/fp-ts/modules/TaskEither.ts.html + ## Types ### Bracket @@ -92,13 +111,17 @@ type Bracket = ( ); ``` +The Bracket type is a drop-in replacement for the Cont type from [fp-ts-cont][], +but specialized in returning `TaskEither`. This solves the problem stipulated at +the end of [application bootstrapping with fp-ts][] by allowing the return type +to be threaded through the program. Furthermore, it makes the `ApplicativePar` +instance possible, which allows for parallel composition of bracketed resources. + The Bracket type aliases the structure that's encountered when using a curried variant of [fp-ts' `TaskEither.bracket` function][]. This curried variant is also exported from the Bracket module as `bracket`. It models a bracketed resource for which the consumption hasn't been specified yet. -[fp-ts' `TaskEither.bracket` function]: https://gcanti.github.io/fp-ts/modules/TaskEither.ts.html#bracket - The Bracket module defines various type class instances for `Bracket` that allow you to compose and combine multiple bracketed resources. From most instances, some derivative functions are exported as well. @@ -112,6 +135,10 @@ some derivative functions are exported as well. - ApplyPar: `apPar`, `apFirstPar`, `apSecondPar`, `apSPar`, `getApplySemigroupPar`, `sequenceTPar`, `sequenceSPar` - ApplicativePar: Pointed ApplyPar +[fp-ts' `TaskEither.bracket` function]: https://gcanti.github.io/fp-ts/modules/TaskEither.ts.html#bracket +[fp-ts-cont]: https://github.com/joshburgess/fp-ts-cont +[application bootstrapping with fp-ts]: https://dev.to/avaq/application-bootstrapping-with-fp-ts-59b5 + ### Service ```ts diff --git a/example/services/server.ts b/example/services/server.ts index 68d2594..f0aa202 100644 --- a/example/services/server.ts +++ b/example/services/server.ts @@ -24,11 +24,14 @@ type Dependencies = { export const withServer: Service.Service = ( ({port, app}) => Bracket.bracket( + // Acquire: () => new Promise(resolve => { const server = HTTP.createServer(app); server.once('error', e => resolve(E.left(e))); server.listen(port, () => resolve(E.right(server))); }), + + // Dispose: server => () => new Promise(resolve => { server.removeAllListeners('error'); server.close((e: unknown) => resolve( diff --git a/package.json b/package.json index 8c420cc..b4cf114 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fp-ts-bootstrap", "version": "0.1.0", - "description": "Application bootstrapping utilities for fp-ts", + "description": "Service orchestration made functional", "main": "lib/index.js", "type": "commonjs", "scripts": {