metrue / fx

A Function as a Service tool makes a function as a container-based service in seconds.
MIT License
2.17k stars 154 forks source link

How do I use fx to build giki.app #470

Open metrue opened 4 years ago

metrue commented 4 years ago

I found AWS lambda in July 2015 and became a big fan of Function as a Service (FaaS) since then, Three years ago I built my own FaaS framework fx at a Go Hackathon, then I published it on Hacker News, it quickly became one of Github trending repositories and pull in 700+ stars in one day.

While I was a little bloated, I struggled to find real-world scenario for it. This week I decided to become my own user of it, so I built an Web App with fx, and fx is amazingly handy in building APIs with FaaS way.

Hello World

fx is a simple tool I build to simplify the API development, let's take a look at how easy we build an API with fx.

You define an API in func.js , looks like this.

module.exports = (ctx) => {
  ctx.body = 'hello world'
}

Then you can deploy your function to be a service with fx with one command.

$ fx up --name helloworld --port 3000 func.js

Let's test it with curl.

$ curl -v 127.0.0.1:8080

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 11
Content-Type: text/plain; charset=utf-8
Date: Tue, 06 Aug 2019 15:58:41 GMT

hello world

fx in fleself.com

fleself.com is a website (UI and APIs) are totally written with JavaScript/Node, all the APIs are built with fx.

Architecture

This's overall APIs code structure of fleself.com,

services
├── Makefile
├── db-migrations
├── tweets
│   ├── create
│   │   └── fx.js
│   ├── delete
│   │   └── fx.js
│   ├── query
│      └── fx.js
│   
└── users
    ├── oauth
    │   └── fx.js
    ├── sign
    │   └── fx.js
    └── update
        └── fx.js

And each source code of an API is just a function, take /api/tweets/delete as example,

const { Client } = require('pg')
const jwt = require('jsonwebtoken')

const k = 'key_xxxxxxxx'
const create = async (ctx) => {
  const { id } = ctx.request.body

  if (!ctx.headers.authorization) {
    ctx.throw(403, 'token required')
    return
  }

  let user = null
  try {
    const token = ctx.headers.authorization.split(' ')[1]
    user = jwt.verify(token, Buffer.from(k, 'base64'))
  } catch (e) {
    ctx.throw(e.status || 403, e.text)
    return
  }
  try {
    const client = new Client()
    await client.connect()
    const res = await client.query({
      text: `DELETE  FROM tweets WHERE id=$1 AND user_id=$2`,
      values: [id, user.id],
    })
    await client.end()
    ctx.body = 'deleted'
  } catch (e) {
    console.warn(e)
    await client.end()
    ctx.status = 500
  }
}
module.exports = create

GitHub Actions Workflows

The whole development process is managed on GitHub, and I use GitHub Actions to do CI/CD, its GitHub Actions workflow looks like this.

name: api
on:
  push:
    paths:
      - '.github/**'
      - 'services/**'
    branches:
      - master
jobs:
  api:
    runs-on: ubuntu-latest
    steps:
      - name: check out
        uses: actions/checkout@master
      - name: use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - name: install SSH key
        uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.SSH_KEY }}
          name: id_rsa
          known_hosts: ${{ secrets.KNOWN_HOSTS }}
      - name: install fx
        run: |
          curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
          fx -v
      - name: add fx host
        run: |
          fx infra create --name <node_name> --type docker --host root@<xxx.xxx.xxx.xxx>
          fx use <node_name>
      - name: deploy tweets_create
        run: |
          fx up -n tweetscreate -p 6000 services/tweets/create/fx.js --force

Caddy as Services Proxy

And I use Caddy as the frontend of the service, the config looks like this,

fleself.com {
  tls h.minghe@gmail.com
  gzip
  log ./fleself.com.log

  proxy /api/users/update 127.0.0.1:5000
  proxy /api/users/login  127.0.0.1:5010
  proxy /api/users/oauth 127.0.0.1:5020
  proxy /api/tweets/create 127.0.0.1:6000
  proxy /api/tweets/query 127.0.0.1:6010
  proxy /api/tweets/update 127.0.0.1:6020
  proxy /api/tweets/delete 127.0.0.1:6030
  proxy /api/tweets/sync 127.0.0.1:7000

  root ./fleself.com
  rewrite / {
    if {path} not_match ^/api
    to {path} /
  }
}
lu-zen commented 4 years ago

Just found this project. Every lambda fx runs is a docker container?

metrue commented 4 years ago

@lu-zen Yes, it's.