🐷

次世代のサーバー見えなくなる。

2025/02/21に公開

こんにちはStampの村本です。最初の方は難しい横文字が多いように思いますが、内容はスッキリとこれからの最新のServerの情報をまとめていますのでぜひ読んでいってください。

Swift 6.0 から導入された Resolve DistributedActor Protocols により、サーバー内部の具体的な実装は隠蔽され、クライアントはプロトコルで定義されたインターフェースだけを利用してリモート呼び出しが可能になりました。
この新しい設計思想により、従来の複雑な通信プロトコルや内部実装の詳細を意識せず、統一された API 呼び出しだけでシステムを構築できる環境が整えられています。


1. サーバーは見えなくなって、アクターとなる

従来、サーバーは物理的なプロセスやサーバーマシンとして存在し、内部の実装や通信処理が直接利用者に露出していました。
しかし、分散アクターを利用することで、サーバーは「アクター」という抽象単位に置き換えられ、内部ロジックや通信処理が完全に隠蔽されます。

たとえば、単純な「Ping-Pong」通信を例に取ると、クライアントからの ping() 呼び出しに対して、サーバーは常に "pong" を返すだけのシンプルな振る舞いとなります。
この設計により、サーバー内部の実装が見えなくなることで、開発者はバックエンドの複雑さに煩わされず、必要な機能に集中できる環境が実現されます。


2. APIは、全てインターフェースとなる

分散システムにおいては、API をすべてインターフェース(すなわちプロトコル)として定義することが極めて重要です。
このアプローチでは、クライアントはサーバーの具体的なアクター型や実装の詳細を知る必要がなく、プロトコルに記述されたリモートメソッドを単に呼び出すだけで必要な機能を利用できます。

特に、@Resolvable マクロを活用することで、分散プロトコルに対して自動的にスタブ型(例:$PingPong)が生成されます。
クライアントはこのスタブ型を通じてリモート参照を解決し、サーバー内部の実装に依存しない API 呼び出しを行うことが可能となります。

以下に、API モジュール、クライアントモジュール、サーバーモジュールのコードブロックを示し、インターフェースを渡すだけで通信が成立する仕組みを解説します。

API Module – インターフェースの定義

// API Module: インターフェース定義
@available(macOS 13.0, iOS 16.0, *)
@Resolvable
distributed protocol PingPong: DistributedActor where Self.ActorSystem == WebSocketActorSystem {
    // クライアントが ping() を呼び出すと、サーバーは "pong" を返す
    distributed func ping() -> String
}

Client Module – クライアント側の実装例

// Client Module: クライアント側の実装例
import Distributed

// 既に構成済みの ActorSystem を利用して、リモート参照を解決する例
let clusterSystem: ClusterSystem = /* 取得済みの ActorSystem */
let pingPong = try $PingPong.resolve(id: someID, using: clusterSystem)
let response = try await pingPong.ping()  // サーバーから "pong" が返される
print("Received response: \(response)")

Server Module – サーバー側の実装

// Server Module: サーバー側の実装
@available(macOS 13.0, iOS 16.0, *)
distributed actor PingPongServer: PingPong {
    public typealias ActorSystem = WebSocketActorSystem
    
    public distributed func ping() -> String {
        return "pong"
    }
}

このように、クライアントは API モジュールで定義されたインターフェースだけを受け取り、サーバー側の具体的な実装(PingPongServer)の詳細を意識せずに通信が可能です。


3. トランスポートレイヤーが拡張された世界

分散アクターを利用したシステム設計では、実際に通信に用いられるトランスポートレイヤー(例えば、WebSocket や gRPC)の実装の違いは内部で抽象化されます。
そのため、開発者は通信基盤の違いを意識する必要がなく、言語レベルで統一された API 呼び出しだけを記述すればよいのです。

この抽象化により、通信プロトコルの変更や拡張があった場合でも、アプリケーションコードへの影響が最小限に抑えられ、システム全体の柔軟性が向上します。
さらに、DistributedActorSystem プロトコルにはシリアライゼーション要件(例:Codable)を型レベルで保証する仕組みも組み込まれているため、どの通信基盤を採用していても一貫したリモート呼び出しが可能となります。


4. 実際のサーバーコード

以下は、Ping/Pong 通信を例に、分散アクターとしてのサーバー実装とその起動処理を示すコード例です。
サーバーは API モジュールで定義された PingPong プロトコルに準拠し、その実装として PingPongServer を提供します。
エントリーポイントとなる Boot 構造体では、サーバーが起動され、常にリモート呼び出しを受け付ける状態に設定されています。

// Server Module: サーバー側の実装
@available(macOS 13.0, iOS 16.0, *)
distributed actor PingPongServer: PingPong {
    public typealias ActorSystem = WebSocketActorSystem
    
    public distributed func ping() -> String {
        return "pong"
    }
}

// サーバーの起動例(Boot 構造体)
@main
struct Boot {
    static func main() async throws {
        // WebSocketActorSystem を利用してサーバーを起動
        // 通信の詳細は ActorSystem 内部で抽象化されるため、ここでは意識不要
        let system = WebSocketActorSystem(id: .server)
        
        // ローカルアクターとして PingPongServer を生成
        _ = system.makeLocalActor(id: .client) {
            PingPongServer(actorSystem: system)
        }
        
        print("PingPongServer is running. Awaiting ping requests...")
        
        // サーバーは無限ループで動作状態を維持する
        while true {
            try await Task.sleep(nanoseconds: 1_000_000_000)
        }
    }
}
    // サーバーのアドレス
    let address = ServerAddress(
        scheme: .insecure,
        host: "127.0.0.1",
        port: 8888
    )
    
    // 通信システムの構築とクライアントの接続
    let system = WebSocketActorSystem()
    try await system.connectClient(to: address)
    
    // @Resolvable マクロにより生成されたスタブ型
    let session = try $PingPo.resolve(id: .client, using: system)
    try await session.ping()
 

まとめと未来の展望

Swift 6.0 の Resolve DistributedActor Protocols を活用することで、サーバー内部の複雑な実装は抽象化され、クライアントはプロトコルとして定義されたシンプルな API 呼び出しだけでリモート通信が可能となりました。
この設計は、以下の点で開発者に有用なメリットを提供します。

  • サーバー内部の隠蔽
    サーバーは分散アクターとして実装され、物理的なプロセスや内部ロジックは完全に隠蔽されます。これにより、開発者はバックエンドの複雑さに煩わされることなく、必要な機能の実装に専念できます。

  • インターフェースベースの通信
    API はすべてプロトコルというインターフェースで統一され、@Resolvable マクロによって生成されたスタブ型を利用することで、クライアントはサーバーの具体的な実装に依存せずに通信を行うことができます。
    この明確な分離は、将来的な変更や拡張に対しても柔軟な対応を可能にします。

  • トランスポートレイヤーの抽象化
    通信プロトコルの実装は ActorSystem 内部で抽象化されるため、開発者はその詳細を意識する必要がなく、言語レベルで統一された API 呼び出しのみでシステム全体を構築できます。
    これにより、通信基盤のアップデートや変更も容易に対応できる環境が整います。

未来の展望

この新しい設計思想は、サーバーサイド Swift の未来に大きな影響を与えると期待されます。具体的には、次のような展望が考えられます。

  • 柔軟な分散システムの構築
    サーバーとクライアント間のインターフェースが明確に分離されることで、システム全体のモジュール化が進み、各コンポーネントの独立した開発やスケーラビリティが向上します。
    マイクロサービスやサーバーレスアーキテクチャとの連携も一層進むでしょう。

  • 開発者の生産性向上
    複雑な通信プロトコルやサーバー内部の実装を意識せずに済むため、開発者はビジネスロジックの実装に集中でき、生産性が向上します。
    また、統一された API インターフェースにより、チーム間の協力が円滑になり、コードの保守性も改善されます。

  • エコシステムの発展
    分散アクターと @Resolvable マクロを中心とした開発手法により、監査ツール、デバッグ支援ツール、さらには分散システム全体の可観測性を向上させる仕組みなど、周辺ツールやエコシステム全体が発展する可能性があります。

  • クライアント/サーバーの柔軟な立ち回り
    クライアントはサーバーの実装詳細を知らずに済むため、バックエンドのアップデートやマイグレーションが容易になり、システム全体の進化に対して柔軟に対応できるようになります。

総じて、Swift の分散アクターを活用したこの新たなアプローチは、サーバーサイド開発の効率化と柔軟性向上に大きく寄与する可能性を秘めています。
開発者はこの革新的な設計思想を積極的に取り入れ、次世代の分散システム構築における競争力を高めるための重要な手法として活用していくことが期待されます。

ぜひ、このアプローチをプロジェクトに取り入れ、その実用性と将来性を実際に体験してみてください。

それでは、この記事が少しでも勉強になったと思ったらいいね、ブックマークよろしくお願いします!!


おまけ

AIで使われているMCP(Modal Context Protocol)をSwiftで実装した例です。Swift界隈はAIのエコシステムの開発が遅れ気味です。いい言語といい仕組みがあるのでこの仕組みをAIと連携させるように取り組みをしています。

https://github.com/1amageek/swift-context-protocol

記事の内容GPTが書いています。おかしいところがあったら修正お願いします。

Discussion