最近在看一些有趣的flutter项目时,又看到一个Appfolowy的项目,是notion的开源替代版本,主要想解决数据在notion云而不能自己管理的隐私问题。

  1. 整个项目采用多进程架构设计,分为前端(flutter or tauri)和后端(rust),进程间通信,其中进程交换请求和响应使用Protobuf进行数据的序列化。
  2. 消息传递是比共享内存或直接函数访问更安全的技术,因为接收者可以自由地拒绝或丢弃其认为合适的请求。例如,如果 Flowy Core 确定请求无效,它只会丢弃该请求,并且从不执​​行相应的函数。分为事件通知.

EVENT(事件)

AppFlowy的后端定义了所有事件并生成事件的。目前,AppFlowy支持Dart和TS事件调用。 事件在前端发出并在后端处理。每个事件在后端都有自己的处理程序。每个事件都可以携带使用 protobuf 序列化的有效负载。该有效负载将在后端使用相应的 protobuf 结构进行反序列化。 关于event的细节在官方文当有说明查看 image 例如,使用UserEventSignIn后端传入参数来触发事件。

1
2
3
4
5
6
7
8
9
async function sendSignInEvent() {
    let make_payload = () =>
        SignInPayloadPB.fromObject({
            email: nanoid(4) + "@gmail.com",
            password: "A!@123abc",
            name: "abc",
        });
    await UserEventSignIn(make_payload());
}

Notification(通知)

最适合传达生命周期事件和状态更改的通知单向消息。通知在后端触发并在前端接收。每个通知都可以携带使用 protobuf 序列化的有效负载。该有效负载将在前端使用相应的 protobuf 结构进行反序列化。 image 例如,用于UserNotificationListener在新用户登录时接收通知。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 let listener = await new UserNotificationListener({
    onUserSignIn: (userProfile) => {
        console.log(userProfile);
    }, 
    onProfileUpdate(userProfile) {
        console.log(userProfile);
        // stop listening the changes
        // listener.stop();
    }}
);
listener.start();

当了解到跨进程通过protobuf通讯时,想看下proto文件,发现居然在vscode搜索不到,只能硬着头皮从编译脚本入手去看。发现它的整个流程还是有挺多直接借鉴的地方。

  1. 相对于传统客户端,采用多进程跨语言的设计,让合适的语言干合适的事情,使得ui和逻辑解耦同时还能有非常好的性能。
  2. 通过rust代码和模板生成代码(shared-lib/flowy-codegen)包括:proto文件、dart的protobuf序列化和反序列化代码、event调用代码、Notification调用代码,代码生成流程如下:
  3. 按功能划分模块,将frontend/rust-lib 下的rust代码通过rust注解生成proto文件,再生成event调用代码。 入口在frontend/rust-lib/flowy-user/build.rs
  4. 在自动化方面做了不少工作,因此CI/CD比较完善,能够持续快速迭代。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
  let crate_name = env!("CARGO_PKG_NAME");
  flowy_codegen::protobuf_file::gen(crate_name);

  #[cfg(feature = "dart")]
  flowy_codegen::dart_event::gen(crate_name);

  #[cfg(feature = "ts")]
  flowy_codegen::ts_event::gen(crate_name);
}

亲自实践了一把https://github.com/csbzy/gof

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
该main.go文件是一个Go语言源代码文件,属于一个程序的入口文件。它包含了一些导入的包和一些函数定义。

该文件中导入了以下包:
- "C":该包是用于与C语言进行交互的包。
- "unsafe":该包提供了一些与指针相关的不安全操作。

该文件中定义了以下变量:
- isInitialized:一个布尔类型变量,用于判断是否已经初始化。
- isInitializedLock:一个互斥锁变量,用于对isInitialized变量进行保护。

该文件中定义了以下函数:
- main函数:程序的入口函数,它是一个空的函数,没有具体逻辑。
- initialize函数:一个导出函数,用于初始化程序。它接受一个指针参数,并在isInitialized变量为false时进行初始化。
- receiveTime函数:一个导出函数,用于接收时间数据。它接受一个整数参数、一个字节指针参数和一个整数参数,并在新的goroutine中执行一些逻辑操作。具体操作包括根据文件路径打开badger数据库,定期获取序列值并发送到指定端口。

该文件的主要功能是初始化程序和接收时间数据,执行相应的逻辑操作。