概要
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" }