Sandwich Shop: A containerless, serverless experiment
Published: 2021-11-21 22:33:00

Some jobs in your career will be pleasant, engaging uses of your waking hours. Challenges are welcome, and the pursuit of bettering yourself ahead of the product is the leading reason you show up every day. However, this was not one of those jobs. Instead, the days felt like they dragged on, with the only way of telling them apart was by weekends, forced birthday celebrations, or the occasional company meeting. The kind of meetings that applause for the senior leadership wasn't only expected but warranted a one-on-one with HR if you didn't provide one. An awkward call to the principal's office where your "company loyalties" were called into question.

> HR person: I know we are entirely dissecting your team, putting you in the overflow building on the other side of the city, changing your fundamental job description, and firing a few people you've grown to rely on to do your job. But why weren't you clapping after we announced the half a million-dollar bonus we're giving the CEO instead of the usual 3% raise we offer our warehouse workers every year?

The day began like the rest—the acrid aroma of too cheap coffee brewed for too long in a forever-neglected Black and Decker was my usual welcome. I spent that morning writing serverless functions for a fledgling e-commerce site, a practice that usually involves explaining to the director that the term "serverless" didn't actually mean the absence of a server. Still, I humored him with the burden of explanation. Once completed and with the pitchy coffee cleansed from my mug and bowels, it was time for a lunchtime pilgrimage to Subway.

My co-workers had chosen the topic for the meal—a recount of a game they had recently picked up. Not having much to contribute, I sat quietly and unpacked my sandwich. I'm not sure how long I sat there, not saying anything when one of my co-workers asked what was on my mind.

"Ya know, serverless architecture is a lot like owning a sandwich shop. You put the foundations down, hire the people, stock the kitchen, and put out a menu. Only, with serverless, after the first customer comes in and places an order, we bulldoze the whole thing to the ground and start over when the next customer arrives." I don't believe I have ever seen a table with so many confused faces since my four-year-old nephew tried to tell a joke. At least my nephew got a couple of sympathetic chuckles.

But why do we do it that way? Why do we put our functions inside wrappers or only use specific languages? I want to put my code up somewhere and just use it as-is instead of the extra bits that go with it.

> I didn't stay much longer with that company and was soon on my way to the next set of forced birthday celebrations.

{% github suhay/sandwich-shop no-readme %}

## Platform agnostic and Sandwiches (PaaS)

One of the more complex parts of having an idea is finding a way to use it. Logistics, good intentions, and planning aside, solving a problem for someone separates a good idea from a great one. Like the inventors of the cell phone when just starting off, "Who would use something like that?"

In a previous experiment, The Exothermic Project, we discussed what a platform would look like without a front or backend. This pet rock-like creature that neither eats nor poops would solve many problems for would-be pet owners, but it isn't exciting. And let's face it: there are web problems that can't be solved without a backend. I wanted to address this issue by providing a friend our pet rock could talk to. Stack agnostic website, meet the code agnostic platform. Now we had a place where we could write APIs against a database without exposing our passwords or integrating with 3rd parties without distributing our keys. Sandwich Shop will be the gateway, but having it just do one application's heavy lifting seemed like a waste.

With that in mind, we came up with a tenant architecture. By coming up with, of course, I mean we stole the tenant architecture idea from all the other PaaS (Platform as a Service) providers. The idea was to separate our applications by the tenant name. If a site wants to pull something from a database, it would just call `https://example.com/shops/theSite/getPage`. The webserver would proxy any call for `/shops` to Sandwich Shop, with the next part of the path being the tenant, and the last part being the function to run (or the Order to place).

Each tenant would have its own workspace. It would define an `orders.yml` file containing all the essential parts of placing an Order. It'll provide a path to the script, pick the runtime required to run it, set any custom authentication steps it needs to take and add any environment variables.

```yaml
---
getPage: # order name
  runtime: node14 # shop to run in
  path: getPage.js # path where function lives
  env: # any variables to include
    - NODE_ENV=dev
getNews: # order name
  runtime: go1_17 # shop to run in
  path: news_sync.go # path where function lives
  env: [] # any variables to include

```

Tenants are also able to determine their own access authorization. By default, a tenant can provide a `.key` file within its workspace. An authorization will be provided to requests that have an `Authorization: Bearer whatever-is-in-the-key-file` header. Pretty basic, but it's at least something. Orders can also provide their own header definition, just in case `Authorization` is not used, and a custom `auth` function. These functions will be run just before the Order to check if the request is authorized. For example, something that comes from GitHub webhooks:

```yaml
# orders.yml
---
suhay.dev:
  runtime: python3
  path: parse.py
  auth_header: X-Hub-Signature-256
  auth: auth.py
```

```python
# auth.py
async def auth(header, data):
    token = os.environ.get("API_TOKEN")
    tokenb = bytes(token, 'utf-8')

    signature = 'sha256=' + hmac.new(tokenb, data, hashlib.sha256).hexdigest()
    if hmac.compare_digest(signature, header):
        return True
    return False
```

Since we defined a custom `auth_header` and `auth`, we will skip the default authorization step and immediately send the request to an available Shop. The Shop will then execute the script specified in `auth`, passing in the `auth_header` to be checked. The rest of the execution then waits on whether or not we return true or false from `auth.py`.

## Franchising

Sandwich Shop will define a set of Shops used to broker an Order based upon the Shop's runtimes. A Shop is what actually runs the code associated with the Order and can live anywhere. For this example, we have one defined on localhost and port 4007.

```json
[
  {
    "_id": "ciabatta",
    "name": "Ciabatta",
    "host": "http://localhost",
    "port": 4007,
    "runtimes": [
      "go1_17",
      "node14",
      "node16",
      "python3"
    ]
  }
]
```

We wanted to keep Sandwich Shop and the Shops as separate processes for scaling purposes. You also might want to specialize your Shops to run certain kinds of code. For instance, you can have one Shop that only does Node.js and lives in one place while another handles Python found in another.

## If this, then paywall

I've always loved the idea of connecting my different apps and services together. When I'm researching something or writing an article, grabbing images or stories and sending them to my notes is simply incredible. However, they can be limited without a subscription, and I don't feel I use it enough to justify paying for one.

One of the first things I used Sandwich Shop for was to listen for GitHub webhooks. I can then use a development server to run any build steps and move the readied files to their respective webroots. By eliminating the need to SSH into multiple servers to deploy a change by hand has been lovely.

I also just started opening a few Shops that handle home automation. We can then set up a few Alexa skills and other triggers that you would see in IFTTT or Zapier.

## Containing the excitement

One thought was to wrap the Shops in Docker containers. I decided this would be against the idea of being containerless. We should instead deploy them on different ports and across other machines if we need to scale.

## What's next?

Something the current version of Sandwich Shop is missing is the ability to check on long-running processes. Right now there is really no way to see if there is an error, or even if it's stuck. You place the Order and then just hope you get what you want. 

Standing up another Shop or tenant is something you have to do yourself, today. That's not the best, so allowing an easier way to add either from the command line or even from a web interface would make it a lot easier to scale and add additional projects to the flow.

Although we have added a layer of security to the requests themselves, we need to prevent malicious code from running in a Shop. If this experiment were to move forward to being a PaaS offering, we would want to make sure the code is executed within a `rbash` or even use something like jailing to prevent code from leaking into another tenant's workspace.

Lastly, comparing Sandwich Shop up against other solutions such as AWS would be interesting to see if we got anywhere close to solving a problem or just something fun to play around with.