Emulating the cloud within Booster Framework 💻🌩️
Tags: webdev, node, typescript, devops
Published: 2020-10-29 18:28:20

---
title: Emulating the cloud within Booster Framework 💻🌩️
published: true
description: The cloud is cool and all that jazz, but what's better than developing locally and seeing your changes instantly in an express server?
tags: #webdev #node #typescript #devops 
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/kvir21f35z8jf3myd0kh.png
---

One of the cool things about Booster is that most of it's functionality sits on top of an abstract interface that expects some stuff from the cloud. The framework itself doesn't have a single call to any service from AWS, Azure, or Kubernetes. That's the job of the *provider* packages.

When you are developing your app, you probably don't wanna think about the very little details of each database, cloud service or whatever. Perhaps you, like me, hate even having to learn each and every library or SDK for the technology/service at hand.

Thanks to this abstraction, you just code by using Booster concepts (command, events, etc.) and forget about the rest. But what happens underneath? Let's take a look 👀

![knight removing helmet to reveal another helmet](https://media.tenor.com/images/5bc2aaa60ecec90881ddf7279a346b94/tenor.gif)

# Cloud vs local development

The cloud is cool and all that jazz, but what's better than developing locally and seeing your changes instantly?

Yeah, there are things that emulate the workings of specific services, like DynamoDB, or there are folks who run their entire Kubernetes apps, with all the required processes, like MongoDB, MySQL, Redis, etc. Or even things like Serverless framework that deploy your app relatively quickly, but at the cost of maintaining a huge, messy, YAML file.

Stuff should be simpler, you shouldn't need a beefy computer to develop your app.

Due to many reasons, but along them, the ones I just described, people resolve to coding their app in the simplest way possible, probably an `express` server, or alike.

What if we had an `express` server that behaved as our app in the cloud? That's the idea with a local provider.

# Implementing a Booster provider to work locally

![man dropping packages](https://media.tenor.com/images/8c51c4c46572262b8fd6474d1009a9eb/tenor.gif)

To implement a Booster provider, you'll need to create two `npm` packages:

- `framework-provider-<name of your environment>` - This package is in charge of:
    - Provide the functions to store/retrieve data from your cloud.
    - Transform the specific objects of your cloud into Booster ones, e.g. converting an AWS event into a Booster one.
- `framework-provider-<name of your environment>-infrastructure` - This package is in charge of:
    - Provide a `deploy` function that will set all the required resources in your cloud provider and upload the code correctly, as well as a `nuke` function that deletes everything deployed, **OR**
    - Provide a `start` function that will start a server and all the appropriate processes in order to run the project in a specific environment. *This one is the one that I'll be using for the local provider*.

Given that I'm implementing the local provider, I just named them like:

- `framework-provider-local`
- `framework-provider-local-infrastructure`

To implement the local provider, I'll be using `express` that will act as the endpoints provided by Booster, and [nedb](https://github.com/louischatriot/nedb/), which is a local, filesystem implementation of a NoSQL database, with an API very similar to MongoDB. It would be the equivalent of SQLite but for NoSQL databases.

Let's start implementing the first package.

# The provider interface

Booster's provider interface is a regular TypeScript interface that must have it's methods implemented, an implementation could look like this:

```tsx
export const Provider = {
  events: {
    rawToEnvelopes: ...,
    forEntitySince: ...,
    latestEntitySnapshot: ...,
    store: ...,
  },
  readModels: {
    rawToEnvelopes: ...,
    fetch: ...,
    search: ...,
    store: ...,
    // ...
  },
  graphQL: {
    rawToEnvelope: ...,
    handleResult: ...,
  },
  api: {
    requestSucceeded,
    requestFailed,
  },
  // ...
}
```

To begin implementing the basics, let's start with `rawToEnvelopes` which are functions that convert from the cloud data type to the Booster one.

In the case of the local provider, the data will arrive as it is, as we are in charge of handling it with `express`, so the implementation is pretty simple:

```tsx
export function rawEventsToEnvelopes(rawEvents: Array<unknown>): Array<EventEnvelope> {
  return rawEvents as Array<EventEnvelope>
}

export function rawReadModelEventsToEnvelopes(rawEvents: Array<unknown>): Array<ReadModelEnvelope> {
  return rawEvents as Array<ReadModelEnvelope>
}
```

In the case of the `rawToEnvelope` function for the `graphQL` field, we will have to get some more information from the request, like a request ID, a connection ID, or the event type, which will come in the request, to simplify things, let's ignore them:

```tsx
export async function rawGraphQLRequestToEnvelope(
  request: express.Request
): Promise<GraphQLRequestEnvelope | GraphQLRequestEnvelopeError> {
  return {
    requestID: UUID.generate(),  // UUID.generate() provided by Booster
    eventType: 'MESSAGE',
    connectionID: undefined,
    value: request.body,
  }
}
```

With these functions implemented, we already have our endpoints connected to Booster, now we just have to teach it how to store/retrieve data!

![learning](https://media1.tenor.com/images/5c57cc20451d5f0b438a8e707ff91b60/tenor.gif?itemid=4878723)

# Creating a local database

Given that we'll be using NeDB to store our Booster app data, we will need to initialize it first. We can do it in the same file as the `Provider` implementation:

```tsx
import * as DataStore from 'nedb'
import { ReadModelEnvelope, EventEnvelope } from '@boostercloud/framework-types'

const events: DataStore<EventEnvelope> = new DataStore('events.json')
const readModels: DataStore<ReadModelEnvelope> = new DataStore('read_models.json')
```

NeDB uses a file for each "table", so we create two `DataStore`s to interact with.

Now we have to implement the methods that the providers require, for example `store`:

```tsx
async function storeEvent(event: EventEnvelope): Promise<void> {
  return new Promise((resolve, reject) => {
    events.insert(event, (err) => {
      err ? reject(err) : resolve()
    })
  })
}

async function storeReadModel(readModel: ReadModelEnvelope): Promise<void> {
  return new Promise((resolve, reject) => {
    readModels.insert(readModel, (err) => {
      err ? reject(err) : resolve()
    })
  })
}
```

Sadly, NeDB doesn't provide a `Promise` based API, and doesn't play well with `promisify`, so we have to wrap it manually. The implementation is pretty straightforward.

The rest of the methods are a matter of implementing the proper queries, for example:

```tsx
async function readEntityLatestSnapshot(
  entityID: UUID, 
  entityTypeName: string
): Promise<EventEnvelope> {
  const queryPromise = new Promise((resolve, reject) =>
    this.events
      .find({ entityID, entityTypeName, kind: 'snapshot' })
      .sort({ createdAt: -1 }) // Sort in descending order
      .exec((err, docs) => {
        if (err) reject(err)
        else resolve(docs)
      })
  )
}
```

There are some other methods that can be a bit confusing, but they also act as interaction at some point, like managing HTTP responses:

```tsx
async function requestSucceeded(body?: any): Promise<APIResult> {
  return {
    status: 'success',
    result: body,
  }
}

async function requestFailed(error: Error): Promise<APIResult> {
  const statusCode = httpStatusCodeFor(error)
  return {
    status: 'failure',
    code: statusCode,
    title: toClassTitle(error),
    reason: error.message,
  }
}
```

After implementing all the methods of the `Provider`, we are pretty much done with the first package, and we can hop onto the infrastructure train 🚂

![person in train costume punching someone](https://media.tenor.com/images/fad7174a3a82b4755f7b38b04ea38414/tenor.gif)

# Wiring everything up with an Express server

In the same case as the `Provider` , your `Infrastructure` object must conform to an interface, which in our case is a `start` method that initializes everything. Here we will create an `express` server and wire it into Booster, by calling the functions that the framework core provides.

Let's begin by initializing the `express` server:

```tsx
export const Infrastructure = {
  start: (config: BoosterConfig, port: number): void => {
    const expressServer = express()
    const router = express.Router()
    const userProject: UserApp = require(path.join(process.cwd(), 'dist', 'index.js'))
    router.use('/graphql', graphQLRouter(userProject))
    expressServer.use(express.json())
    expressServer.use(router)
    expressServer.listen(port)
  },
}
```

Here we are importing user's app, in order to gain access to all the public Booster functions (typed in the `UserApp` type).

You can see that the only endpoint at the moment is `/graphql`, and that's what we are gonna configure now:

```tsx
function graphQLRouter(userApp: UserApp) {
  const router = express.Router()
  this.router.post('/', async (req, res) => {
    const response = await userApp.boosterServeGraphQL(req)  // entry point
    res.status(200).json(response.result)
  })
}
```

And that's it, we only have to call `boosterServeGraphQL` on the user's app. 

Because we already provided all the required methods in the Provider package, Booster has access to all the infrastructure capabilities, and it will use all of them as they need to be, no need to write more code! 🚀

# That's all folks!

![kid falling from skate](https://media.tenor.com/images/0052e6fbc2acd0d17efc6036df4048cf/tenor.gif)

I'm gonna keep working on improving the local provider, like adding nice logging messages, tests, and more goodies 😉, but you can always check out the complete code in the following folders of the [Booster repo]([https://github.com/boostercloud/booster](https://github.com/boostercloud/booster)):

- `packages/framework-provider-local`
- `packages/framework-provider-local-infrastructure`

Thanks for reading all of this! Have an awesome day,

Nick