内部のRBKmoney支払い-マイクロサービス、プロトコル、プラットフォーム構成

こんにちはHabr! RBKmoneyが再び連絡を取り、日曜大工の支払い処理の書き方に関する一連の記事を続けています。













すぐに、ステートマシンとしての支払いビジネスプロセスの実装の説明の詳細を掘り下げ、一連のイベント、実装機能を備えたそのようなマシンの例を示したいと思いました。 サブジェクト領域が大きすぎることが判明しました。 この投稿では、作業のニュアンスと、プラットフォームのマイクロサービス間の相互作用、外部システムとの相互作用、およびビジネス構成の管理方法を明らかにします。







マクロサービス



私たちのシステムは多くのマイクロサービスで構成されており、ビジネスロジックの完成した各部分を実装し、相互にやり取りしてマクロサービスを形成します。 実際、銀行やその他の支払いシステムに接続されたデータセンターに展開されたマクロサービスは、支払い処理です。







マイクロサービステンプレート



書かれている言語にかかわらず、マイクロサービスの開発には統一されたアプローチを使用しています。 各マイクロサービスは、以下を含むDockerコンテナーです。















マイクロサービスを開発する場合、プラットフォームの高可用性とフォールトトレランスの両方の問題を解決するように設計された、特別に考案した制限を使用します。









サードパーティと統合する多くの人、状況に確かに精通しています。 プロトコルに従って金銭を償却する要求に対する第三者からの応答を期待し、どのように解釈するか不明な、仕様に記載されていない完全に異なる答えが来ました。







この状況では、この支払いを処理するステートマシンを強制終了し、外部からのアクションに対して500のエラーを受け取ります。内部で支払いの現在の状態を確認し、マシンの状態を現実に合わせてステートマシンを復活させます。







プロトコル指向開発









この記事の執筆時点では、プラットフォームの機能を保証するサービスについて、636の異なるチェックがサービスディスカバリに登録されていました。 1つのサービスでいくつかのチェックが実行されていること、およびほとんどのステートレスサービスが少なくとも3つのインスタンスで動作していることを考慮しても、すべてのアプリケーションで、何らかの方法で相互に接続でき、失敗しないことが必要ですRPC地獄へ。







スタックには3つの開発言語(Erlang、Java、JS)があり、それらはすべて透過的に通信できる必要があるため、状況は複雑です。







解決する必要がある最初のタスクは、マイクロサービス間でデータを交換するための正しいアーキテクチャを設計することでした。 基礎として、Apache Thriftを使用しました。 すべてのマイクロサービスはtriftバイナリを交換します; HTTPをトランスポートとして使用します。







フォント仕様は、GitHubに個別のリポジトリの形式で配置されるため、それらにアクセスできるすべての開発者が使用できます。 最初は、すべてのプロトコルに共通のリポジトリを1つ使用していましたが、時間が経つにつれて、これは不便であるという結論に至りました-プロトコルに関する共同並行作業は、常に頭痛の種になりました。 さまざまなチームやさまざまな開発者でさえ、変数の名前に同意することを余儀なくされました。名前空間に分割しようとしても、助けにはなりませんでした。







一般的に、プロトコル駆動型の開発があると言えます。 実装を開始する前に、リフト仕様の形で将来のマイクロサービスプロトコルを開発し、7つのレビューサークルを通過して、このマイクロサービスの将来の顧客を引き付け、同時にすべての将来のメソッドを知っており、既にハンドラーを作成できるため、同時に複数のマイクロサービスの開発を開始する機会を得ますオプションでmokiを使用します。







プロトコル開発プロセスの別のステップはセキュリティレビューです。そこでは、開発者が開発中の仕様の微妙な違いを5半期の観点から見ます。







また、チームにおけるプロトコル所有者の別の役割を強調することが適切であると考えました。 タスクは難しく、すべてのマイクロサービスの詳細を念頭に置いておく必要がありますが、大きな順序で成果を上げ、エスカレーションの単一ポイントが存在します。







これらの従業員によるプルリクエストの最終承認がなければ、プロトコルをマスターブランチにマージできません。 githubには、これに非常に便利な機能があります- コード所有者 、私たちは喜んでそれを使用します。







したがって、マイクロサービス間の通信の問題、プラットフォームにどのようなマイクロサービスが登場したか、なぜ必要なのかを誤解する可能性のある問題を解決しました。 このプロトコルのセットは、おそらく、1つのマイクロサービスの実装を比較的簡単に書き換えることができるため、開発のコストと速度に対して無条件に品質を選択するプラットフォームの唯一の部分であり、数十のプロトコルはすでに高価で苦痛です。







その過程で、正確なログ記録は文書化の問題の解決に役立ちます。 メソッドとパラメーターの適切な名前、いくつかのコメント、および自己文書化された仕様により、多くの時間を節約できます!







たとえば、マイクロサービスのいずれかのメソッドの仕様は次のようになり、プラットフォームで発生したイベントのリストを取得できます。







 /**    */ typedef i64 EventID /* Event sink service definitions */ service EventSink { /** *       ,   *    ,  ,  `range`.  *      `0`  `range.limit` . * *   `range.after`    ,   * ,        , *   `EventNotFound`. */ Events GetEvents (1: EventRange range) throws (1: EventNotFound ex1, 2: base.InvalidRequest ex2) /** *         *  . */ base.EventID GetLastEventID () throws (1: NoLastEvent ex1) } /* Events */ typedef list<Event> Events /** * ,    -,  . */ struct Event { /** *  . *    ,     *      (total order). */ 1: required base.EventID id /** *   . */ 2: required base.Timestamp created_at /** *  -,  . */ 3: required EventSource source /** *  ,    ( ) *   -,  . */ 4: required EventPayload payload /** *      . *    . */ 5: optional base.SequenceID sequence } // Exceptions exception EventNotFound {} exception NoLastEvent {} /** * ,       -   */ exception InvalidRequest { /**          */ 1: required list<string> errors }
      
      





Thriftコンソールクライアント



必要なマイクロサービスの特定のメソッドを直接呼び出すタスクに直面する場合があります。たとえば、端末からの手です。 これは、デバッグ、生データセットの取得、またはタスクが非常にまれで個別のユーザーインターフェイスの開発が実用的でない場合に役立ちます。







そのため、 curl



機能を組み合わせたツールを開発しましたが、JSON構造の形式で簡単なリクエストを行うことができます。 それに応じて私たちは彼を呼んだwoorl



。 このユーティリティは汎用性があり、コマンドラインパラメータを使用してリフト仕様の場所を渡すだけで十分であり、残りは自動的に実行されます。 非常に便利なユーティリティで、たとえば端末から直接支払いを開始できます。







これは、アプリケーションの管理(たとえば、ストアの作成など)を担当するプラットフォームマイクロサービスにアピールする方法です。 テストアカウントのデータをリクエストしました。













おそらく、読者はスクリーンショットの1つの機能に気付いたでしょう。 それも好きではありません。 マイクロサービス間でのトリフトコールの承認を固定する必要があります。TLSを適切に接着する必要があります。 しかし、リソースはいつものように十分ではありません。 マイクロサービスの処理が行われる境界の全体のエンクロージャーに限定しました。







外部システムと通信するためのプロトコル



外向きのリフト仕様を公開し、商人にバイナリプロトコルを使用した通信を強制するために、私たちはそれが彼らにとってあまりにも残酷だと考えました。 私たちが便利に統合し、デバッグし、便利に文書化できるように人間が読めるプロトコルを選択する必要がありました。 Swaggerとしても知られるOpen API標準を選択しました。







プロトコルの文書化の問題に戻ると、Swaggerではこの問題を迅速かつ安価に解決できます。 ネットワークには、Swagger仕様の美しいデザインの開発者向けドキュメントの形式で多くの実装があります。 見つけることができるものすべてを見て、最終的にswDoc.jsonを入力として受け入れ、出力でそのような3列のドキュメントを生成するJSライブラリであるReDocを選択しました: https ://developer.rbk.money/api/。







内部Thriftと外部Swaggerの両方のプロトコルの開発へのアプローチは、私たちにとってまったく同じです。 これは開発に時間を追加しますが、長期的には報われます。







また、別の重要な問題を解決する必要がありました-お金の引き出しのリクエストを受け入れるだけでなく、さらにそれらを銀行と支払いシステムに送信します。







リフトを強制的に実装することは、パブリックAPIに送信するよりも実行不可能なタスクです。







そのため、プロトコルアダプタの概念を思いついて実装しました。 プラットフォーム全体で同じ内部リフト仕様を実装するもう1つのマイクロサービスであり、2つ目は特定の銀行または変電所に固有の外部プロトコルです。







サードパーティとやり取りする必要があるときにこのようなアダプターを作成するときに発生する問題は、さまざまなストーリーが豊富なトピックです。 私たちの実務では、フォームの答え、「あなたはもちろん、あなたが与えたプロトコルで説明されているようにこの機能を実装することができますが、私は何の保証もしません。このすべての答え、あなたは彼に確認を求めます。」 また、このような状況は珍しいことではありません。「ここにサーバーからのユーザー名とパスワードがあります。そこに行って、すべて自分で設定してください。」







私たちが支払いパートナーと統合したとき、特に興味深いことに気づきました。支払いパートナーは、以前にプラットフォームと統合し、私たちを通して支払いを成功させました(これは、多くの場合、支払い業界のビジネス仕様です)。 テスト環境に対する当社のリクエストに応えて、パートナーは、テスト環境自体はないが、RBC、つまり当社が関与できるプラットフォームとの統合のためのトラフィックを得ることができると回答しました。 これが、パートナーを通じて、かつて自分自身と統合された方法です。







したがって、さまざまな支払いシステムや他のサードパーティの大規模並列接続を実装する問題を非常に簡単に解決しました。 ほとんどの場合、このためにプラットフォームコードを変更する必要はありません。アダプターを記述し、enumに支払い方法を追加するだけです。







その結果、このような作業スキームが得られました-RBKmoney APIマイクロサービス(これらはCommon API、またはcapi *と呼びます。上の領事で見ました)の外側を見て、公開Swagger仕様に従って入力データを検証し、クライアントを許可し、ブロードキャストしますこれらのメソッドを社内のリフトコールに組み込み、次のマイクロサービスにチェーンでリクエストを送信します。 さらに、これらのサービスはさらに別のプラットフォーム要件を実装し、その技術仕様は次のように定式化されました。「システムには常に猫を飼う機会が必要です。」







外部システムへの呼び出しが必要な場合、内部マイクロサービスは対応するプロトコルアダプターのリフトメソッドを取得し、特定の銀行または支払いシステムの言語に変換して送信します。







プロトコルの後方互換性の問題



プラットフォームは常に進化しており、新しい機能が追加され、古い機能が変更されています。 このような状況では、後方互換性のサポートに投資するか、依存するマイクロサービスを常に更新する必要があります。 そして、必須フィールドがオプションのすべてに変わる状況が単純な場合、何もすることができず、反対の場合、追加のリソースを費やす必要があります。







一連の内部プロトコルを使用すると、物事が簡単になります。 決済業界はめったに変化しないため、根本的に新しい対話方法がいくつか登場します。 たとえば、私たちの一般的なタスクを考えてみましょう-新しいプロバイダーを新しい支払い手段に接続します。 たとえば、カザフスタンのテンゲで支払いを処理できるローカルウォレット処理。 これはプラットフォームの新しいウォレットですが、原則として同じQiwiウォレットと違いはありません。デビット/デビットをキャンセルできる一意の識別子とメソッドが必要です。







したがって、すべてのウォレットプロバイダーのリフト仕様は次のようになります。







 typedef string DigitalWalletID struct DigitalWallet { 1: required DigitalWalletProvider provider 2: required DigitalWalletID id } enum DigitalWalletProvider { qiwi rbkmoney }
      
      





新しいお支払い方法を新しいウォレットの形式で追加すると、enumが補完されます。







 enum DigitalWalletProvider { qiwi rbkmoney newwallet }
      
      





現在、この仕様を使用してすべてのマイクロサービスを強化し、リポジトリウィザードと仕様を同期し、CI / CDを介してそれらを展開しています。







外部プロトコルはより複雑です。 Swagger仕様の各更新は、特に下位互換性がない場合、妥当な期間内に適用することはほとんど不可能です。パートナーがプラットフォームの更新専用の無料の開発者リソースを保持することはほとんどありません。







また、これは単に不可能な場合もあります。「プログラマーが私たちに手紙を書いて去り、ソースコードを彼と一緒に持ち、どのように作業したか、わかりません。







そのため、外部プロトコルの下位互換性のサポートに投資しています。 私たちのアーキテクチャでは、これは少し簡単です-Common APIの特定のバージョンごとに個別のプロトコルアダプターを使用するため、古いcapiマイクロサービスはそのままにしておき、必要に応じてプラットフォーム内の些細な部分のみを変更します。 したがって、マイクロサービスcapi-v1



capi-v2



capi-v3



などが表示され、永遠に残ります。







capi-v33



ときに何が起こるか、おそらく古いバージョンを廃止する必要があります。







この時点で、私は通常、Microsoftなどの企業を非常によく理解し始め、何十年も機能しているソリューションの下位互換性をサポートすることに苦労しています。







システムをカスタマイズする



トピックの終わりに、ビジネス固有のプラットフォーム設定の管理方法を説明します。







支払いを行うことは、見かけほど簡単ではありません。 ビジネス顧客は、支払いごとに膨大な数の条件(手数料から原則として、時刻に応じて実装が成功する可能性まで)を付加したいと考えています。 私たちは、ビジネス顧客が現在および将来に思い付く条件のセット全体をデジタル化するタスクを設定し、このセットを新しく開始された各支払いに適用します。







その結果、独自のDSLを開発することにしました。これに便利な管理ツールを固定して、ビジネスモデルを正しい方法で説明できるようにしました。支払いシステムに固有のその他のもの。







たとえば、マエストロとMSのカードで取得するために1%の手数料を受け取り、システム内のアカウントに分散する場合、次のようにドメインを構成します。







 { "cash_flow": { "decisions": [ { "if_": { "any_of": [ { "condition": { "payment_tool": { "bank_card": { "definition": { "payment_system_is": "maestro" } } } } }, { "condition": { "payment_tool": { "bank_card": { "definition": { "payment_system_is": "mastercard" } } } } } ] }, "then_": { "value": [ { "source": { "system": "settlement" }, "destination": { "provider": "settlement" }, "volume": { "share": { "parts": { "p": 1, "q": 100 }, "of": "operation_amount" } }, "details": "1% processing fee" } ] } } ] } }
      
      





, , . , JSON. , , , . , , . , CVS/SVN-.







" ". , , , 1%, , , . , , . , .







cvs-like , . , — stateless, , . . .







- . , , . , , .







. , 10 , , .







, , , -, woorl-. - JSON- . - JS, , UX:













, , , .







, , .







, , SaltStack.







, !








All Articles