Rust製軽量マイクロサービスフレームワークfusen-rsの紹介:DubboおよびSpringCloudとの相互運用性

概要

fusen-rsは、Rustのマクロを活用してコンパイル時の「リフレクション」を実現する軽量かつ高性能なマイクロサービスフレームワークです。従来のRPCフレームワークで一般的だったコード生成スクリプトやスキャフォールディングツールへの依存を排除し、Javaのリフレクションプロキシに近い使い勝手を提供しつつ、ゼロコスト抽象化による高いパフォーマンスを維持します。さらに、Dubbo3やSpringCloudといった既存のマイクロサービスエコシステムとの相互運用性を備えており、Javaプロジェクトとのシームレスなサービスディスカバリと通信が可能です。

クイックスタート

インターフェース定義

#[derive(Serialize, Deserialize, Default, Debug)]
pub struct InputPayload {
    pub message: String,
}

#[derive(Serialize, Deserialize, Default, Debug)]
pub struct OutputPayload {
    pub message: String,
}

#[fusen_trait(package = "org.apache.dubbo.springboot.demo")]
#[asset(spring_cloud = "service-provider")]
pub trait GreetingService {
    async fn greet_user(&self, name: String) -> String;

    #[asset(path="/greet-v2-http", method = POST)]
    async fn greet_user_v2(&self, payload: InputPayload) -> OutputPayload;

    #[asset(path="/compute-ratio", method = GET)]
    async fn calculate_ratio(&self, numerator: i32, denominator: i32) -> String;
}

サーバー実装

#[derive(Clone, Debug)]
struct GreetingHandler {
    data_source: String,
}

#[fusen_server(package = "org.apache.dubbo.springboot.demo")]
impl GreetingService for GreetingHandler {
    async fn greet_user(&self, name: String) -> FusenResult<String> {
        info!("Received: {:?}", name);
        Ok(format!("Hi, {}", name))
    }

    #[asset(path="/greet-v2-http", method = POST)]
    async fn greet_user_v2(&self, payload: InputPayload) -> FusenResult<OutputPayload> {
        info!("Received: {:?}", payload);
        Ok(OutputPayload {
            message: format!("Hi, {} V2", payload.message),
        })
    }

    #[asset(path="/compute-ratio", method = GET)]
    async fn calculate_ratio(&self, numerator: i32, denominator: i32) -> FusenResult<String> {
        info!("Computed: numerator={}, denominator={}", numerator, denominator);
        Ok((numerator + denominator).to_string())
    }
}

#[tokio::main(worker_threads = 512)]
async fn main() {
    fusen_common::logs::init_log();
    let handler = GreetingHandler {
        data_source: "DatabaseConnection".to_string(),
    };

    FusenServer::build()
        .add_register_builder(
            NacosConfig::builder()
                .server_addr("127.0.0.1:8848".to_owned())
                .app_name(Some("fusen-service".to_owned()))
                .server_type(fusen_rs::register::Type::Fusen)
                .build()
                .boxed(),
        )
        .add_register_builder(
            NacosConfig::builder()
                .server_addr("127.0.0.1:8848".to_owned())
                .app_name(Some("service-provider".to_owned()))
                .server_type(fusen_rs::register::Type::SpringCloud)
                .build()
                .boxed(),
        )
        .add_protocol(Protocol::HTTP("8081".to_owned()))
        .add_protocol(Protocol::HTTP2("8082".to_owned()))
        .add_fusen_server(Box::new(handler))
        .run()
        .await;
}

クライアント実装

lazy_static! {
    static ref FUSEN_CLI: FusenClient = FusenClient::build(
        NacosConfig::builder()
            .server_addr("127.0.0.1:8848".to_owned())
            .app_name(Some("fusen-client".to_owned()))
            .server_type(fusen_rs::register::Type::Fusen)
            .build()
            .boxed()
    );
    static ref DUBBO_CLI: FusenClient = FusenClient::build(
        NacosConfig::builder()
            .server_addr("127.0.0.1:8848".to_owned())
            .app_name(Some("dubbo-client".to_owned()))
            .server_type(fusen_rs::register::Type::Dubbo)
            .build()
            .boxed()
    );
    static ref SPRING_CLI: FusenClient = FusenClient::build(
        NacosConfig::builder()
            .server_addr("127.0.0.1:8848".to_owned())
            .app_name(Some("springcloud-client".to_owned()))
            .server_type(fusen_rs::register::Type::SpringCloud)
            .build()
            .boxed()
    );
}

#[tokio::main(worker_threads = 512)]
async fn main() {
    fusen_common::logs::init_log();

    // Fusenプロトコルによる呼び出し (HTTP2 + JSON)
    let fusen_client = GreetingServiceClient::new(&FUSEN_CLI);
    let fusen_res = fusen_client.greet_user_v2(InputPayload { message: "Rust".to_string() }).await;
    info!("Fusen response: {:?}", fusen_res);

    // Dubbo3プロトコルによる呼び出し (HTTP2 + GRPC)
    let dubbo_client = GreetingServiceClient::new(&DUBBO_CLI);
    let dubbo_res = dubbo_client.greet_user("Rust".to_string()).await;
    info!("Dubbo3 response: {:?}", dubbo_res);

    // SpringCloudプロトコルによる呼び出し (HTTP1 + JSON)
    let spring_client = GreetingServiceClient::new(&SPRING_CLI);
    let spring_res = spring_client.calculate_ratio(10, 5).await;
    info!("SpringCloud response: {:?}", spring_res);
}

Dubbo3との連携

本フレームワークはDubbo3プロトコルとの互換性があり、JavaのDubbo3プロジェクトとインターフェース経由でサービスディスカバリや相互呼び出しをシームレスに行えます。Rust側のServerおよびClientコードは上記の実装から変更する必要はありません。

Java側のDubbo3プロジェクトでは、コードの大幅な改修は不要ですが、Dubbo3のデフォルトシリアライズがfastjson2のバイナリ形式であるため、JSONシリアライズを有効にするためにfastjson1の依存関係と設定を追加する必要があります。

Apache公式のdubbo-samples-spring-bootプロジェクトを例にします。ServerとClientのpom.xmlに以下のMaven依存関係を追加します。

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-serialization-fastjson</artifactId>
    <version>2.7.23</version>
</dependency>
<!-- registry dependency -->
<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-client</artifactId>
    <version>2.2.0</version>
</dependency>

Javaサーバー実装

@DubboService
public class GreetingHandlerImpl implements GreetingService {

    @Override
    public String greetUser(String name) {
        return "Hello " + name;
    }
}

Javaサーバー application.yml

dubbo:
  application:
    name: dubbo-springboot-demo-provider
  protocol:
    name: tri
    port: 50052
    prefer-serialization: fastjson
  registry:
    address: nacos://${nacos.address:127.0.0.1}:8848

Javaクライアント実装

@Component
public class ExecutionTask implements CommandLineRunner {
    @DubboReference
    private GreetingService greetingService;

    @Override
    public void run(String... args) throws Exception {
        String output = greetingService.greetUser("world");
        System.out.println("Received output ======> " + output);

        new Thread(()-> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println(new Date() + " Received output ======> " + greetingService.greetUser("world"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
    }
}

Javaクライアント application.yml

dubbo:
  application:
    name: dubbo-springboot-demo-consumer
  registry:
    address: nacos://${nacos.address:127.0.0.1}:8848

SpringCloudとの連携

fusen-rsはHTTPインターフェースの拡張によりWebサーバーフレームワークとしても機能し、SpringCloudのサービスディスカバリにも対応しています。ユーザーは暴露するプロトコルを柔軟に選択・切り替え可能で、複数プロトコルの同時暴露もサポートしています。

ここではspring-cloud-alibabaプロジェクトを使用してデモを行います。Rust側のServer/Clientコードは上記のままで変更不要であり、Java側のServer/Clientコードも特別な変更なしでそのまま起動して連携できます。

Javaサーバー実装

Provider起動クラス: com.alibaba.cloud.examples.ProviderApplication

@RestController
public class EchoEndpoint {
...
    @GetMapping("/compute-ratio")
    public String computeRatio(@RequestParam Integer numerator, @RequestParam Integer denominator) {
        if (denominator == 0) {
            return String.valueOf(0);
        } else {
            return String.valueOf(numerator / denominator);
        }
    }
...
}

Javaクライアント実装

Consumer起動クラス: com.alibaba.cloud.examples.ConsumerApplication

@RestController
public class TestEndpoint {
...
    @GetMapping("/compute-ratio-feign")
    public String computeRatio(@RequestParam Integer numerator, @RequestParam Integer denominator) {
        return echoClient.computeRatio(numerator, denominator);
    }
...
}

動作確認

SpringCloud経由でのリクエストテスト:

curl "http://127.0.0.1:18083/compute-ratio-feign?numerator=1&denominator=2"

ログ出力:

2024-04-10T06:52:32.737307Z  INFO ThreadId(07) server: 33: Computed: numerator=1,denominator=2

fusen-rsサーバーへの直接リクエストテスト:

curl "http://127.0.0.1:8081/compute-ratio?numerator=2&denominator=3"

ログ出力:

2024-04-10T06:54:26.436416Z  INFO ThreadId(512) server: 33: Computed: numerator=2,denominator=3

POSTリクエストのテスト:

curl --location --request POST 'http://127.0.0.1:8081/greet-v2-http' \
--header 'Content-Type: application/json' \
--header 'Connection: keep-alive' \
--data-raw '{
    "message" : "World"
}'

ログ出力:

2024-04-10T07:02:50.138057Z  INFO ThreadId(03) server: 26: Received: InputPayload { message: "World" }

タグ: rust fusen-rs Dubbo3 SpringCloud Microservices

5月19日 04:08 投稿