7 个不稳定版本 (3 个重大更改)
0.4.0 | 2019年6月25日 |
---|---|
0.3.3 | 2019年6月14日 |
0.2.0 | 2019年6月13日 |
0.1.0 | 2019年6月6日 |
#28 in #msgpack
每月下载量 53次
145KB
4K SLoC
lavish-compiler
lavish
允许您声明服务,并轻松地从多种语言中实现/消费它们。
具有意见
- 拥有自己的模式语言和编译器(用Rust编写)
- 目前针对Rust、Go和TypeScript
- 自带Rust的RPC运行时
- 设计时考虑到“双向帧MessagePack-RPC over TCP”
状态
lavish仍在开发中,目前不可用
模式
模式可以定义“函数”,它接受参数并返回结果。
server fn log(message: string)
server fn get_version() -> (major: i64, minor: i64, patch: i64)
server fn shutdown()
所有输入参数和输出参数(结果)都有名称。
函数可以有命名空间
namespace utils {
server fn log()
server fn get_version()
}
namespace system {
server fn shutdown()
}
函数可以由服务器或客户端实现
namespace session {
// try to log in. if password authentication is
// enabled, @get_password is called.
server fn login(username: string)
client fn get_password(username: string) -> (password: string)
server fn logout()
}
内置类型(小写)包括:
i8
、u16
、u32
、u64
:无符号整数i8
、i16
、i32
、i64
:有符号整数- 注意JavaScript只有53位精度。
f32
、f64
:浮点数bool
:布尔值string
:UTF-8字符串data
:原始字节数组timestamp
:UTC日期+时间
可以声明自定义类型,那些应该是 CamelCase
enum LoginType {
Anonymous = "anonymous",
Password = "password",
}
struct Session {
login_type: LoginType,
connected_at: timestamp,
}
namespace session {
server fn login() -> (session: Session)
// etc.
}
默认情况下,所有字段都必须指定 - 没有默认值。然而,可以使用 option<T>
将字段设置为可选。
// password can be None in Rust, nil in Go, undefined in TypeScript
server fn login(password: option<string>)
数组使用 array<T>
声明。
server fn login(ciphers: array<Cipher>)
映射使用 map<K, V>
声明。
server fn login(options: map<string, string>)
option
、map
和 array
可以嵌套。
server fn login(options: option<map<string, string>>)
可以导入第三方模式。
import itchio from "github.com/itchio/go-itchio"
namespace fetch {
server fn game(id: i64) -> (game: option<itchio.Game>)
}
(关于导入机制的更多信息将在后面介绍。)
工作区
工作区是一个包含 lavish-rules
文件的目录。
lavish-rules
文件类似于 lavish 的 Makefile
- 它告诉 lavish 编译什么,以及编译哪种语言。
lavish 命令行工具将模式文件编译成 Rust、Go、TypeScript 代码。
每个工作区
- 针对单一语言(Rust、Go、TypeScript)
- 可以构建各种服务
- ...它们共享导入
创建时钟服务
假设我们正在编写一个简单的 Go 服务,该服务返回当前时间。
在运行 lavish 编译器之前,我们的存储库看起来像
- go.mod
- main.go
- services/
- clock.lavish
- lavish-rules
clock.lavish
包含
server fn current_time() -> (time: timestamp)
并且 lavish-rules
包含
target go
build clock from "./clock.lavish"
lavish 编译器接受工作区的路径
lavish build ./services
运行 lavish 编译器后,我们的存储库将看起来像
- go.mod
- main.go
- services/
- clock.lavish
- lavish-rules
- clock/ <-- generated
- clock.go <-- generated
现在我们可以实现时钟服务器,例如
package main
import (
"github.com/fasterthanlime/clock/services/clock"
"time"
)
func Handler() clock.ServerHandler {
var h clock.ServerHandler
h.OnCurrentTime(func () (clock.CurrentTimeResults, error) {
res := clock.CurrentTimeResults{
time: time.Now(),
}
return res, nil
})
return h
}
最后,我们可以在顶级添加一个 lavish-rules
文件,以便我们可以在以后的项目中无缝导入它
export "./services/clock.lavish" as clock
从 Rust 消费时钟服务
假设我们想从 rust 调用我们的时钟服务。
我们的初始 Rust 存储库将看起来像
- Cargo.toml
- src
- main.rs
- services/
- lavish-rules
我们的 lavish-rules
文件将看起来像
target rust
build clock from "github.com/fasterthanlime/clock"
使用以下命令运行编译器
lavish build ./src/services
...将会抱怨 clock
缺失。
运行
lavish fetch ./src/services
将会填充 lavish-vendor
文件夹
- Cargo.toml
- src
- main.rs
- lavish-rules
- lavish-vendor/ <-- new
- clock.lavish <-- new
再次运行编译将生成 rust 代码
- Cargo.toml
- src
- main.rs
- lavish-rules
- lavish-vendor/
- clock.lavish
- clock/ <-- new
- mod.rs <-- new
现在,可以从 Rust 导入 clock
模块并用于消费服务,例如
mod clock;
type Error = Box<dyn std::error::Error + 'static>;
async fn example() -> Result<(), Error> {
// Create router - don't implement any functions from our side.
let r = clock::client::Router::new();
// Connect to server over TCP, with default timeout.
let client = lavish::connect(r, "localhost:5959")?.client();
{
let time = client.call(clock::current_time::Params {})?.time;
println!("Server time: {:#?}", time);
}
// when all handles go out of scope, the connection is closed
}
从 TypeScript 消费时钟服务
初始存储库
- src/
- main.ts
- services/
- lavish-rules
lavish-rules
的内容
target ts
build clock from "github.com/itchio"
lavish fetch src/services
- src/
- main.ts
- services/
- lavish-rules
- lavish-vendor/ <-- new
- clock.lavish <-- new
lavish compile src/services
- src/
- main.ts
- services/
- lavish-rules
- lavish-vendor/
- clock.lavish
- clock <-- new
- index.ts <-- new
然后我们可以从 index.ts
使用它
import clock from "./services/clock"
async function main() {
let socket = new net.Socket();
await new Promise((resolve, reject) => {
socket.on("error", reject);
socket.connect({ host: "localhost", port: 5959 }, resolve);
});
let client = new clock.Client(socket);
console.log(`Server time: `, await client.getTime());
socket.close();
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
那很好,但...(常见问题解答)
为什么使用工作区?
假设你使用两个服务,A
和 B
,并且它们都使用来自模式 C
的类型。
你希望能够将来自 A
的调用结果作为参数传递给 B
的调用。
如果你在同一工作区中构建 A
和 B
,你最终将得到三个目录:A
、B
和 C
。 A
和 B
都将使用来自 C
的类型。
此外
- 传递数百万个命令行选项没有乐趣
- 数百万个环境变量也没有乐趣
- 一个最小的配置语言(
lavish-rules
)实际上并不那么糟糕 - 无论写一个还是两个解析器,差别都不大
如果A和B导入不同的C会发生什么?
那么你无法在同一个工作区中使用A
和B
。不过你可以创建两个工作区!
这似乎是一个任意的限制。这会简化实施过程吗?
会的,非常简化。
为什么每个工作区只能有一个目标?
再次,简化实施。如果你想在单个仓库中为多种语言生成绑定,你可以有
- foobar/
- lavish-rules
- foobar-js/
- lavish-rules
- foobar-go/
- lavish-rules
- foobar-rs/
- lavish-rules
import from
路径的格式是什么?
我对导入语法的想法是,对于本地文件
import foo from "./foo.lavish"
import bar from "../bar.lavish"
对于仓库
import foo from "github.com/user/foo"
import foo from "gitlab.com/user/bar"
它是如何知道要git clone什么内容的?
给定host/user/project
,它尝试
https://host/user/project.git
git@host:user/project.git
所以lavish build
需要网络连接吗?
不,不需要。lavish fetch
需要。
所以lavish fetch
可以算是一个迷你包管理器吗?
是的,可以。我中招了。另一种选择似乎需要复制大量文件或者手动克隆仓库,这在很多方面都很糟糕。
TL;DR: lavish fetch
负责获取依赖,lavish build
可以离线工作。
它与其他项目相比如何?
我非常喜欢JSON-RPC,因为它的简单性。那是我之前用的。Msgpack-RPC非常相似,只是序列化更快,有合适的时间戳类型,并且可以传递原始字节。
Cap'n Proto RPC非常令人敬畏。它不仅速度快,还带来了独特的功能——能力和承诺流水线。我对此感到非常兴奋。
然而,在花了些时间在现有的TypeScript序列化库之上实现capnp-rpc后,我最终承认
- 实现复杂度对我来说太高了。从头开始写另一个实现(针对新语言)需要大量的工作,我不懂Rust实现,如果出了问题,我会很难追踪。
- 能力使其难以在浏览器中使用。这不是巧合,对于JavaScript世界,推荐的实现是仅限node(绑定到C++库)。尽管我设法在纯TypeScript中实现了RPC,但我不得不使用electron和node特定的功能来 hook into GC(以便知道何时释放能力)。浏览器使用很容易泄漏能力,而浏览器出于安全原因并不想暴露GC hooks。
- 它是专门设计的。没有强烈的愿望去推广其采用。它正在内部使用,但开发者没有兴趣让它成为适合所有人的万能工具——这很正常!这正是我在lavish中所做的。
tarpc看起来很棒,但仅限Rust。
grpc显然试图成为适合所有人的万能工具。我希望从用各种语言编写的各种应用程序中消费服务——一个MsgPack序列化库加上TCP套接字是合理的要求。ProtoBufs + HTTP/2不是。
依赖项
~5–15MB
~151K SLoC