Serverless function platform for Lua
- High-performance non-blocking functions.
- Fast edit-save-reload development cycle: No redeployment or rebuilding image is needed.
- Platform agnostic: From a simple PostgreSQL database to a complex Kubernetes clusters with Apache Kafka. Spacer can run on most platforms.
Here's a minimal spacer project.
You write a function:
-- app/hello.lua
local G = function (args)
return "Hello from Spacer!"
end
return G
...and define how to expose it to the internet:
-- app/gateway.lua
local R = {
-- HTTP Method, Path, Function Name
{"GET", "/hello", "hello"}
}
return R
...then start the project.
$ ./bin/dev.sh
Test the function.
$ curl localhost:3000/hello
{"data":"Hello from Spacer!"}
That's it!
The easiest way would be using the provided Dockerfile.
To install from source:
- Install Dependencies
If you're on a Mac, brew install openresty/brew/openresty librdkafka
should get everything you need.
- Install Spacer
Spacer is written in Go. It can be installed via go get
.
$ go get -u github.com/poga/spacer
Create a spacer project:
$ spacer init ~/spacer-hello
Start the development server:
$ cd ~/spacer-hello
$ ./bin/dev.sh
Open http://localhost:3000/hello
and you should see spacer working.
Functions in spacer are written in Lua, a simple dynamic langauge. Here's a hello world function:
-- app/hello.lua
local G = function (args)
return "Hello from Spacer!"
end
return G
Every function takes one argument: args
. For detail, check the Functions section.
Spacer have built-in test framework. Run all tests with command ./bin/test.sh
.
$ ./bin/test.sh
1..1
# Started on Mon Feb 12 17:46:48 2018
# Starting class: testT
ok 1 testT.test_ret
# Ran 1 tests in 0.000 seconds, 1 success, 0 failures
See test/test_hello.lua
for example.
Code in spacer are organized by functions. For now, spacer only support Lua as the programming language.
Functions are the main abstraction in spacer. Functions are composed together to build a complex application.
There are 2 way to invoke other functions from a function. The first is the simplest: just call it like a normal lua function.
-- app/bar.lua
local G = function (args)
return params.val + 42
end
-- app/foo.lua
local bar = require "bar"
local G = function (args)
return 100 + bar({val = 1}) -- returns 143
end
The second way is use flow.call
, which emulate a http request between two function. It's useful when you want to create seperated tracings for two function.
local flow = require "flow"
local G = function (args)
return flow.call("bar", {val = 1}) -- returns 143
end
It's called flow since the primary usage of it is to trace the flow between functions.
Functions are exposed to the public through a gateway. You can define gateway in gateway.lua
.
local _R = {
-- HTTP Method, Path, Function Name
{"GET", "/hello", "hello"},
-- users resources
{"GET", "/users", "controllers/users/get"},
{"PUT", "/users/:id", "controllers/users/put"},
-- teams resources
{"POST", "/teams", "create_teams"},
{"GET", "/teams/:id", "get_team"},
{"PUT", "/teams/:id", "put_team"},
{"DELETE", "/teams/:id", "delete_team"},
-- registration
{"POST", "/register", "controllers/users/create"}
}
return _R
When invoking a function via HTTP request, query params, route params, and post body(json) are all grouped into an args table and passed to the function.
An error is corresponding to HTTP 4xx status code: something went wrong on the caller side. In this case, return the error as the second returned value
local G = function (args)
return nil, "invalid password"
end
If an unexpected exception happepend, use the error()
function to return it. Spacer will return the error with HTTP 500 status code.
local G = function (args)
local conn = db.connect()
if conn == nil then
error("unable to connect to DB")
end
end
Serverless programming is about events. Fuctions are working together through a series of events.
Spacer use Kappa Architecture to provide a unified architecture for both events and data storage.
Events are organized with topics. Topics need to be defined in the config config/application.yml
.
To emit a event, use the built-in topics
library.
local topics = require "topics"
local G = function (args)
topics.append("EVENT_NAME", { [EVENT_KEY] = EVENT_PAYLOAD })
end
return G
Triggers are just functions. You can set a function as a trigger by setting them in the config config/application.yml
.
Events in spacer are permanently stored in the specified storage (by default we use PostgreSQL as storage).
You can require lua module under app/
and lib/
directly.
-- requiring app/foo.lua
local m = require "foo"
Directories can be used to organize your modules.
-- requiring app/models/foo.lua
local m = require "models/foo"
To build spacer from source:
$ git clone git@github.com:poga/spacer.git
$ make
$ go install // if you want to put it into your path
lib/luaunit.lua
: BSD. https://github.com/bluebird75/luaunitlib/uuid.lua
: MIT. https://github.com/thibaultcha/lua-resty-jit-uuidlib/router.lua
: MIT. https://github.com/APItools/router.lualib/resty/hmac.lua
: BSD. https://github.com/jkeys089/lua-resty-hmaclib/resty/eve.lua, lib/resty/jwt-validators.lua, lib/resty/jwt.lua
: Apache License. https://github.com/SkyLothar/lua-resty-jwt- Everything else: MIT