kabusapiへのリクエストをまとめて受け付けるgRPCサーバを作る
Go + gRPCでリクエストを受けて、そこからkabusapiを叩くものを作ろうと思います。
基本思想
基本的にはkabusapiへのリクエストをそのまま投げてもらうことになります。
が、一部簡略できるものや、中間サーバがあることでできることも追加したいと思います。
例えば
アクセストークンを管理し、クライアント側のツールではトークンを意識しない
本番環境か開発環境課とパスワードは中間サーバの起動時に設定してクライアントツールでは意識しない
登録銘柄をキャッシュし、登録銘柄一覧をいつでも取得できるようにする
リクエストによってレスポンスが変わるような命令を違う命令に分ける
時価配信はwebsocketではなくgRPCのStreamに乗せる
などなど。
使い心地がちょっとkabusapiと変わるかもしれないけど、protocol bufferにenumなどを明記するから、それをドキュメントとすればいいかなーくらいな感覚です。
アーキテクチャどうしよう
まずつながりはこんな感じ。
https://gyazo.com/a1066b501d47ba49e4fae562c26f8ea0
中間サーバを構成する要素はこんな感じ。
gRPC Server
Protocol Bufferから変換して出されたサーバinterfaceを満たす実装
変換のタイミングでenumやmessageのstructが出力される
kabusapi Library
Goで書かれたライブラリで、REST APIを叩くためのjsonへの変換とかを内部でやってくれる
Converter
gRPCのメッセージとRESTのリクエストレスポンスを変換する機能
Store
トークンや登録銘柄情報などをキャッシュしておく
https://gyazo.com/af439879351577f15c04bc0707fbb8f5
僕は普段からClean ArchitectureとDDDの使い勝手のいいところだけを取り込んだオレオレアーキテクチャで開発してて、
今回もそれに則ってやろーっと思ってたんですが大きな問題が。
腐敗防止層を作るのがめんどくさい!!
腐敗防止層は作るものが外部のものに汚染されるのを防ぐための層。
だいたいは外部のシステムから来る情報を内部用の構造体に置き換えるコンバータとしての役割が強い。
それを今回の中間サーバに適用した場合、
gRPCの構造体を中間サーバ独自の構造体に変換し、中間サーバの構造体をkabusapiの構造体に変換してライブラリに渡す。
逆の流れでは、kabusapiの構造体を中間サーバの構造体に変換し、中間サーバの構造体をgRPCの構造体に変換して返す。
これ絶対めんどくさい。特に何の意味もない中間サーバの構造体。これは良くない。
とはいえ、gRPCかkabusapiの構造体に依存してしまうと、DIPに反してしまう。
悩みに悩んだ挙句、gRPCの構造体をvalue objectだと脳内変換して使うことにしました。
こうすれば、腐敗防止層 = コンバータにでき、gRPCのメッセージとkabusapi libraryの変換をするだけで済みます。
中間サーバだけで使うvalue objectもあると思うので、違うパッケージに属しているのに違和感はありますが、
少しすっきりした設計にできるのではないかと思います。
gRPCやGoogleのAPI命名規則とGoの命名規則が一部一致しない?
下記を参考にすると、微妙にGoの命名規則と一致しないところがある。ように思う。
アッパースネークケースっていうんかな? UPPER_SNAKE_CASE みたいなの。
こういうの、Goではあまり見ないかなーって。だいたいUpperCamelCaselやと思う。
あとは整数値には単位を含めるのが推奨されてたり、ね。
今回はprotocol buffer側はAPI命名規則に従って、gRPCに変換されたものはそのまま使う。
Goで作ってるものはGoっぽいルールに従ってやる。
とはいえ、ほとんどのオブジェクトがprotocol bufferから生成されるから、Goっぽいルールに従おうとしてるところは少ない。
特に悩んだのが、enum。
code:sample.proto
enum Hoge {
HOGE_UNSPECIFIED = 0; // 未指定
}
をGoに変換すると
code:sample.pb.go
Hoge_HOGE_UNSPECIFIED = Hoge(0)
となる。
Hoge_HOGEつらい!
とはいえ、HOGE_をなくすと名前被りで他のenumでUNSPECIFIEDを使えなくなる。
messageに埋め込む方法もあるけど、共通で使うパーツを埋め込んじゃうのは良くない。
ってことで、泣く泣くHoge_HOGEにしてます。
もともと1つのものを分けるかどうか、複数のものをまとめるか
kabusapiでは1つのエンドピントであるものを複数のエンドポイントに分けるかどうか。
例えば、注文情報というエンドポイントがあるけど、このなかで現物も信用も先物もオプションもまとめて返されるため、
現物注文には関係ない項目があったり、先物注文には関係ない項目があったりして、
あまり見通しの良くない構造になっている。
そういったのを避けるために、gRPCサーバでは種別ごとに違うエンドポイントを作ろうかなーと考えたりする。
似たような問題として、kabusapiのコール/プットには文字型と数値型の2種類がある。
P/Cと1/2で、エンドポイント毎に使い分ける必要がある。
が、gRPCサーバでは同じように扱っちゃう。
いつwebsocketをつなぎ、何を配信するか
streamingの開始はいつにするか、いつ止めるかというのを考えなくてはいけない。
Streamのリクエストは検知できるからそこで初めてつなぎ、Streamの接続がなくなったところで切断でもいいかも。
もしくは30s毎などのような定期的に接続しようとし、接続が出来ていれば接続しないことにより、
購読があろうがなかろうがkabusのwebsocketを購読することも可能ではある。
常時つないでおくことのデメリットとしては常にリソースを使い続けることになること。
kabusが動く時点で懸念事項にすべきほどじゃないとは思うけど、一応コストではある。
また、gRPCサーバのストリームを得ることはいつでもできるけど、
kabusapiのpushはいつでもうけられるわけではない。
kabusが起動していることも当然ながら、loginできている必要がある。
もし繋げない場合はgRPCのstreamもエラーで閉じておきたい。
ということは、やっぱり初めのリクエストが来たときに接続し、kabusのpushとの接続に失敗したらエラーを返すのが良さそうかなー。
全てのクライアントに全ての価格情報を流すのでもいいけど、出来れば必要なところに必要なものを流すのがいい。
クライアントから受けたRegisterSymbolを保持しておき、どのクライアントにどの価格情報を流すかを判断する。
できた!
ってことで、ここまで考えながらやったことができましたー。
protobufファイルはこんな感じ
分かってたけど、長い……。
現時点で1700行弱!!!
全部丁寧に定義してコメント書くと長くなるのは仕方ないけど……
で、そこから出したドキュメントはこんな感じ
これからまだ内容増やすつもりやからまだまだ増えちゃうなー。
更新履歴