跳到正文

手动搭建单体进程

MonolithLauncher 在同一进程内运行 Gateway 与 World,并直接在内存中分发 命令。它保留类型化 Handler 和公共客户端协议,但不需要内部令牌、World 监听端口或服务发现。

适用于早期开发、小规模部署,以及无需独立扩缩 Gateway/World 的测试。

1. 创建 Package

bash
mkdir -p my-game/src/bin my-game/config
cd my-game
toml
[package]
name = "my-game"
version = "0.1.0"
edition = "2024"
rust-version = "1.97"
publish = false

[dependencies]
prost = "0.14"
elura = "0.1.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }

2. 编写应用

组合根加载一个 MonolithLaunchConfig,注册 World Handler 后启动组合运行时:

rust
use std::{env, fs};

use elura::prelude::*;
use prost::Message;
use serde::Deserialize;

#[derive(Clone, PartialEq, Message)]
struct HelloRequest {
    #[prost(string, tag = "1")]
    name: String,
}

#[derive(Clone, PartialEq, Message)]
struct HelloResponse {
    #[prost(string, tag = "1")]
    message: String,
}

struct Hello;

impl Route for Hello {
    const ID: u32 = 100;
    const NAME: &'static str = "example.hello";

    type Request = HelloRequest;
    type Response = HelloResponse;
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct AppConfig {
    runtime: MonolithLaunchConfig,
}

impl AppConfig {
    fn load() -> elura::Result<Self> {
        let path = env::var("APP_MONOLITH_CONFIG")
            .unwrap_or_else(|_| "config/monolith.json".into());
        let mut config: Self = serde_json::from_slice(&fs::read(path)?)?;
        config.runtime.ticket.key = required_env("APP_TICKET_KEY")?;
        config.runtime.admin.token = optional_env("APP_ADMIN_TOKEN");
        Ok(config)
    }
}

#[tokio::main]
async fn main() -> elura::Result<()> {
    let app = AppConfig::load()?;
    MonolithLauncher::new(app.runtime)?
        .configure_world(|builder| {
            builder.register(
                Hello,
                |_context, request| async move {
                    Ok(HelloResponse {
                        message: format!("Hello, {}!", request.name),
                    })
                },
            )?;
            Ok(())
        })?
        .run()
        .await
}

fn required_env(name: &str) -> elura::Result<String> {
    env::var(name).map_err(|_| elura::Error::InvalidConfig(format!("{name} is required")))
}

fn optional_env(name: &str) -> Option<String> {
    env::var(name).ok().filter(|value| !value.trim().is_empty())
}

3. 配置并运行

json
{
  "runtime": {
    "gateway": {
      "listen": "127.0.0.1:17000"
    },
    "world": {
      "handler_timeout": { "secs": 5, "nanos": 0 },
      "idempotency_ttl": { "secs": 30, "nanos": 0 },
      "idempotency_capacity": 10000
    },
    "ticket": {
      "issuer": "game-login",
      "audience": "game-gateway"
    },
    "admin": {
      "listen": "127.0.0.1:17001",
      "component": "monolith",
      "instance_id": "monolith-local"
    }
  }
}

生成票据密钥并启动进程:

bash
export APP_TICKET_KEY="$(openssl rand -hex 32)"
cargo run --bin monolith

在另一个终端验证:

bash
curl -i http://127.0.0.1:17001/healthz
curl -i http://127.0.0.1:17001/readyz

两个端点都返回 204 No Content。命令在进程内分发,因此没有 World 网络端口。

扩展到多进程

迁移到拆分拓扑时,Handler 代码可以保持不变。将组合根替换为独立的 GatewayLauncherWorldLauncher 进程,加入内部令牌并显式选择服务发现。

基于 MIT 许可证发布。