記事をご覧頂きありがとうございます!BBSakuraにてOCXの開発をリードしている川畑です。
OCXを支える技術の連載2記事目です。
連載記事一覧は、この記事についているタグ #OCXを支える技術
からご覧いただけます。
制御システム・ソフトウェアの構成
前回の記事では代表的なOCXのネットワーク接続の機能について紹介しましたが、ここではそのネットワークを制御する心臓部にあたる部分についてお話します。
システムは大きく フロントエンド/認証
と バックエンド
の2つに分けられます。前者はポータルのログイン機能やリソースの購入・設定などのUIを具備したもの、後者はお客様構成情報を管理し、課金を行ったり商用網のネットワーク機器を入力通り間違いなく制御するためのプログラムです。
図をご覧ください。クライアント(お客様のブラウザ)から操作され、ネットワーク機器に設定が投入される一連のフローが記されています。
これを見ると、特にバックエンドについては2つの制御機構があることが分かります。内部の呼称としてそれぞれ API
と Worker
と呼んでいます。
APIが担っている主な機能は、以下の通りです。
- システム全体のマスターデータ(物理ポートやVLANなどの在庫となる情報)の管理
- お客様からのリソース購入・変更・削除の要求を受け付け、適切に精査し、内容に異常が無ければ必要に応じてネットワークへの設定指示情報を生成すること
- 購入した履歴を管理し、課金データを作りお客様に請求を行うこと
Workerが担っている主な機能は、以下の通りです。
- データベースに登録された設定指示情報に基づき、担当するネットワーク機器へのconfigを適切に行うこと
- 同様に、お客様のリソースの開通・閉塞時のデータベース上の状態遷移を取り扱い、適切に情報を更新すること
ネットワーク機器のconfigにはある一定の時間がかかることや、クラウド化されたサービスにおいてリクエスト受付時のリアルタイム性や待ち行列問題を考えたときに、 APIではリクエストを受理・精査した上で「作成や変更・削除要求を問題なく受け付けた」旨のレスポンスは即座に返し、非同期に設定を行うアーキテクチャを考案しました。 マイクロサービス的な構成で行こうという考えも私個人の開発者思想としてありましたが、「同期的にやった場合の平均待ち時間のストレス度合い」についての議論した結果がそれを後押しし、結果的にAPI/Workerで役割を完全に棲み分けた形になりました。
苦労するポイント
同期・非同期に関わらず、受け取るパラメータはお客様による入力値です。最終的にパラメータを受け取るのはサービス網に接続されているサーバやネットワーク機器、はたまたクラウド事業者のAPIであり、受け入れられるパラメータには制限があります。 つまり、お客様の入力値が最終的に投入する機器・APIにおいて受け入れられる値かどうか精査する必要があります。精査には2種類あり、1つ目はパラメータが受け入れられる範囲を超えていないかをチェックすること。2つ目は、既に設定済みの値や同じ値やその範囲に含まれるパラメータを2個以上持つことができない場合のチェックです。開発においては意図的に前者をバリデーション、後者をコンシステンシー(整合性)チェックと呼んでいます。
これら2つの分かりやすい例を紹介します
- バリデーション
- 入力されたIPアドレスが
192.168.0.256
の場合(IPアドレスとしてフォーマットが異常値) - 入力された帯域の値が
0Gbps
の場合(設定できる帯域値として0は許可していない) - 入力が必須項目なのに、入力されていない場合(パラメータが無いと機器の設定が受け付けられない)
- オプションの入力項目について、入力が中途半端な場合(オプションは全て入力するか、しないかの2択の場合がある)
- 入力されたIPアドレスが
- コンシステンシーチェック
- 既に
192.168.0.1/24
のIPアドレスが設定されているルータに、新たに別のインタフェースに192.168.0.10/24
を設定した場合にはエラーを返す(同じセグメントに属するIPアドレスを、複数のインタフェースに設定できない) - ルータのインタフェースに
192.168.0.0/24
のIPアドレスを設定した場合にはエラーを返す(IPアドレスとしてのフォーマットは正しいが、ネットワークアドレスはインタフェースに設定できない)
- 既に
OCXではお客様の入力値をポータル画面を介して受け付けているため、まずはここで入力値のバリデーションを行います。事前にUIチームに「この範囲から外れた値は異常値なので受け付けないで欲しい」という値を共有し、入力画面での異常値を受け付けないように開発してもらいます。 しかし開発途中で受け入れる値が変わることもあり、共有が漏れると異常値がそのままAPIへ渡ることがあります。
加えて、パラメータの中には「組み合わせ」でバリデーションを行うものもあり、単体で見ると正常値なのに組み合わせると異常値であるという場合もあり、バリデーションも一筋縄ではいきません。 更にコンシステンシーチェックまでやるとコード量が膨大になるため、入力画面はプレチェックとして捉え、本格的なチェックはAPIが担当しています。
さて、ここにきて非同期アーキテクチャを採用した最大の難関が待ち受けます。 フロントのバリデーションをすり抜けた値はAPI側で95%程度は救うことができるのですが、稀に5%くらいは考慮漏れ等によって機器まで到達してしまうことがあります(これをエッジケースと呼んでいます) こうなった場合が凶悪で、「機器に入らなかった設定値が、設定されたことになっている」という現象が発生します。設定されたことになっているというのは、ポータル画面で異常な入力値が確定値として表示されてしまっている状態です。 これが「APIは正常に処理をして設定指示情報を生成し、ユーザにも成功の値を返したが、Workerからネットワーク機器に設定を投入するときに失敗した・ネットワーク機器が受け付けなかった」という非同期処理特有の難しさです。
つまり、バリデーション及びコンシステンシーチェックは、投入するネットワーク機器と同じ機構・要領で値を精査しなければならないということを意味します。 普段何気なくネットワーク機器をコンソールで操作しているとき、間違った値を入れると即座ににエラーが返ってきますが、あれが本当にありがたいものだと痛感します。 同期的にやればネットワーク機器のエラーをそのままパラメータ投入結果のエラーとして返せばいいものの、応答の快適性を重視した結果、この問題が常に付きまとうことになりました。 実際、OCX-Router(v1)開発におけるコード量のうち半分以上はバリデーション及びコンシステンシーチェックに費やしました。
おわりに
このように、サービスの開発においてはシステムでの涙ぐましい工夫やサービスの提供仕様・運用・様々な観点でデメリットを許容できる範囲まで落としてチーム一丸で開発を進めています。
そんな工夫を支えるソフトウェア技術としては、フロントエンドはNext.js、バックエンドはGolangを採用しており、モダンなウェブアプリケーションに仕上がっています。 それぞれの技術の詳細については連載にて開発者が赤裸々にお話しますので、楽しみにお待ち下さい。