Abstracting away async with fp-ts
Tags: functional, typescript
Published: 2022-11-22 12:32:31

**Business logic is usually quite simple. It is the plumbing that is hard. You can use ap to isolate your business logic from your plumbing**

Consider the e-commerce store Widgi-tech:

![Widgets for $3 each, buying 3 plus shipping](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmatxb8ho4y3tc7w3jyt.png)

The business logic for calculating the price of an order is usually quite simple. In the above example, our widgets are being sold for $3 each, we are buying three of them and we add shipping on top of that. The complexity usually comes from mixing our business logic with concerns like error handling and asynchronicity.

To begin, lets write our getTotal function.
``` typescript
const getTotal = (price:number) => (qty:number) => (shipping:number) => price * qty + shipping;
```

Job done right? Well no, at Widgi-tech, each one of those arguments is the responsibility of a different team. The pricing team sets prices today based on the prices based on how many of an item you order. Oh and sometimes prices aren't available for a given SKU yet. Qty is controlled by the inventory team which tracks how many widgets they have in their warehouse and you can't order more of a widget than they have and shipping costs depend on the location and total weight of the order. Oh and you can't send items to addresses outside their delivery zone.

The problem then starts to look a bit more like this:

```typescript
import * as E from "fp-ts/Either"

type SKU = string;
type Address = string;

type getPrice = (sku:SKU, qty:number) => E.Either<string, number>
//just ignore that we are representing money with a number type for now

type getQty = (sku:SKU, qty:number) => E.Either<string, number>

type getShipping = (sku:SKU, qty:number, address: Address) => E.Either<string, number>
```

Let's break this down.

We have a function signature for `getPrice` that takes in the SKU and the qty of an item and returns back either an error message (string) or the per unit price of the item (number).

We have a function signature for `getQty` that takes in the SKU and the quantity of items and returns back either an error message when they can't fulfill the item or just the quantity back.

We have a function signature for `getShipping` that takes in the SKU, the quantity of items and the address. We either get back an error message when they can't send the items there or we get back the shipping cost.

Now we are at a conundrum, we started with our beautiful and simple `getTotal` function but now we have to care about error messages and the ugliness of error messages and reality. How do we make the `getTotal` function fit?


![Square peg, round hole](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/387w25hjrsr948rkwq4i.png)

The secret is that Either is an instance of an applicative functor. We can lift our simple `getTotal` function into an Either and then apply each of our arguments sequentially.

``` typescript
/** We lift getTotal into E.Either and apply each argument sequentially */
const price: E.Either<string, number> = pipe(
  E.of(getTotal),
  E.ap(getPrice(sku, qty)),
  E.ap(getQty(sku, qty)),
  E.ap(getShipping(sku, qty, address))
);
```
https://codesandbox.io/s/practical-lewin-yqsthc?file=/src/index.ts

Let's take a closer look at how ap works by examining the type signature of different parts:
- `E.of(getTotal)` This lifts our getTotal function into an Either and the type signature of this is `E.Either<never, (price: number) => (qty: number) => (shipping: number) => number>`
- `getPrice(sku, qty)` This is an `E.Either<string, number>`
- `E.ap` This is the Either ap function, in this case, it has a signature of `<string, number>(fa: E.Either<string, number>) => <B>(fab: E.Either<string, (a: number) => B>) => E.Either<string, B>`
- `pipe(E.of(getTotal), E.ap(getPrice(sku,qty))` This has a type signature of ` E.Either<string, (qty: number) => (shipping: number) => number>` Notice the difference in this signature to the one of `E.of(getTotal)`. **We have partially applied our price in our getTotal function**

Phew. That was a lot but let's take a step back and admire what we have just done. By using the power of applicative, we have isolated the business logic inside our getTotal function and deferred all the error handling to our `Either`s.

So why is this isolation useful? Because business logic changes all the time. Let's say there's a promotion where orders get free shipping if the total order is over $100, what that look like?

``` typescript
const getTotal = (price:number) => (qty:number) => (shipping:number) => {
  const exShipping = price * qty;
  const shippingFee = exShipping < 100 ? shipping : 0;
  return exShipping + shippingFee;
}
```

Notice the lack of any change to the plumbing code or any of the wiring. Think also about how easy it is to test this function in isolation.

Now at this point, you the reader will probably be thinking. Hang on Derp, our different departments would have APIs that are asynchronous. How do we handle that?

To answer that question, we'll need to reach for another applicative. `Either`s encapsulate the concept of an error state, a `Task` encapsulates the concept of asynchronicity. The applicative we need is a `TaskEither` which encapsulates both.

So our changes are:
```typescript
/** Note: all that chnages is going from E.Either to TE.TaskEither */
type GetPrice = (sku: SKU, qty: number) => TE.TaskEither<string, number>;
type GetQty = (sku: SKU, qty: number) => TE.TaskEither<string, number>;
type GetShipping = (sku: SKU, qty: number, address: Address) => TE.TaskEither<string, number>;
```

```typescript
/** We lift getTotal into TE.TaskEither and apply each argument sequentially */
const total: TE.TaskEither<string, number> = pipe(
  TE.of(getTotal),
  TE.ap(getPrice(sku, qty)),
  TE.ap(getQty(sku, qty)),
  TE.ap(getShipping(sku, qty, address))
);

const totalP = pipe(
  total,
  TE.fold(
    (err) => () => Promise.reject(err),
    (total) => () => Promise.resolve(total)
  )
);

totalP().then((total) => console.log(`total is ${total}`), console.error);
```

Notice our simple, beautiful `getTotal` function has remained completely unchanged. It cares not for the messy, asynchronous and error prone state of the world. We have successfully abstracted all of this away from it with the power of fp-ts and functional programming.

https://codesandbox.io/s/dreamy-hofstadter-4vuzou?file=/src/index.ts