支払いプロセスのリファクタリングYa.Money-パワーの目覚め

画像の代替テキスト







長い歴史を持つプロジェクトの場合、ある日、コードが独自の生活を送り始める時が来ます。ロジックと接続に精通している人はいないだけです。 新しい機能を追加すると、ランダムにショットのように見えることがあります。ターゲットに到達するか、視聴者に到達する可能性があります。







そして、彼は来て、支払いプロセスをリファクタリングします。 しかし、リファクタリングにIDEF-0のアイデアを追加することで、プロセスをさらに面白くすることにしました。







これはすべて一時的なもので、その後変更します



Yandex.Moneyの支払いプロセスは2002年から開発されており、そのフロントエンドは長年にわたって、多くの世代の開発者の作業結果を超えて成長してきました。 転送を送信する前にユーザーのバランスをチェックするためのアルゴリズムを変更することでさえ、トラップを使用したクリアリングの旅になりました-ユーザーには見えないが、フードの下で魅力的な旅。 この記事では、フロントエンドのサーバー部分に触れます。







サポートの難しさに加えて、新しい開発者を最新の状態にすることは困難でした。これは、エンジニアがプロジェクト間を定期的に移行する会社にとって大きなマイナスです。 したがって、コードの深いリファクタリングを実施することが決定されました。 作業量を考えると、これはプロセスを書き直すことを意味しました。







ゼロから始める場合は、認識された方法論(有限状態マシンの理論とIDEF-0 )を使用して徹底的に行います 。 この標準に従ってビジネスプロセスを記述する原則は、大学のベンチからエンジニアと管理者の両方によく知られています。このため、共通言語を見つけなければなりませんでした。 同時に、技術者の青い夢は、経営陣が非常に愛しているプロセス図の自動構築について実現します。 たとえば、そのようなスキームは、Yandex.Moneyオフィスで豊富に掛けられている統計情報とともにディスプレイの1つに表示されます。







コードをとかすだけでは十分ではありません-賢明に行う必要があります



すべての古いコードを新しいトラックに変換すると、すべての基本的なメソッドプロセスを説明するNode.jsモジュールのセットが表示されました。 さらに、それらは一連の手順だけでなく、IDEF-0の考え方に従って説明されています。機能ブロック、入力データと出力データ、プロセス接続があります。







一般に、IDEF-0は開発中に簡略化できる多くのことを記述しているため、標準からトレースペーパーを作成せず、アイデアと関連するすべての原則を借用しました。







機能ブロック



IDEF-0では、ファンクションブロックは単にシステムの独立した機能であり、グラフィカルに長方形として描かれています。 Yandex.Moneyの支払いプロセスでは、機能ブロックには特定のプロセスのビジネスロジックの一部が含まれています。







画像の代替テキスト







機能ブロックの4つの側面にはそれぞれ独自の役割があります。







  1. 上部は管理を担当します。







  2. 左-入力データ(操作対象、転送量など);







  3. 右側に結果が表示されます。







  4. 一番下はプロセスで使用されるリソースを指す「メカニズム」です。


Yandex.Money支払いフロントエンドでは、機能ブロックの2つの側面( 入力出力 )のみが使用されます。ビジネスロジックを実行するためデータセットは入力に送信され、出力ではシステムがこのロジックの結果を期待します。







コードでは次のようになります。







/** *       * @param {Object} $flow  ,   ,        * @param {Object} inputData   * @param {Object} inputData.userName   */ const checkUserName($flow, inputData) { if (inputData.userName) { //      const outputData = { userName: inputData.userName isUserNameValid: true }; $flow.transition('doSomethingElse', outputData); return; } $flow.transition('checkFailed', inputData); }
      
      





この関数は、引数として2つのパラメーターを取ります。







  1. $ flow-サービスオブジェクト、現在のプロセスのインスタンス;







  2. inputData-機能ブロックの入力データを持つオブジェクト。 機能ブロックと通常の機能の違いは、外部コードに制御を移す方法にあります。 機能ブロックは、このために別の移行方法を使用します。


機能ブロックを開発する場合、共有責任原則を覚えておくことが重要です。そうしないと、新しいビジネスロジックを追加するときに適切な柔軟性が得られません。







インターフェースアーク



インターフェイスアークは、単に機能ブロックの矢印です。これは、データ転送または機能ブロックへの影響を意味すると予想されます。







新しい支払いプロセスでは、インターフェイスarcの役割は、APIプロセスを提供するインスタンスである$ flowオブジェクトのTransition関数によって実行されます。







分解



大規模で複雑なものを多くの単純で理解可能な部分に分割するという有名な原則。 コードでは、これは関数の単純化と統合を意味します。







IDEF-0では、分解は次のとおりです。







画像の代替テキスト







分解は支払いプロセスのあらゆる場所で使用されましたが、ユーザープロパティをチェックするプロセスの例を見てみましょう。







画像の代替テキスト







ユーザープロパティの確認は、5つの機能ブロックと、分解可能なプロセスからの2つの出力(青色でマーク)で構成されます。 たとえば、電話番号の確認はユーザーだけに適用されるわけではなく、他のプロセスで役立つ場合があります。 このアクションを別のプロセスに分けると、コードはよりシンプルでわかりやすくなります。







画像の代替テキスト







ユーザープロパティの検証の分解後、機能ブロックの一部は電話番号をチェックする新しいプロセスに移動します。 BitBucketを使用すると、違いがより明確に表示されます。ユーザーの電話を確認する機能ブロックは3つあります。







  1. prepareToCheckPhone —データ準備







  2. requestBackendForCheckPhone-バックエンドへのリクエスト。







  3. checkUserPhone-結果の分析。


すべてを外部に移動する前に、これらのすべてのブロックはユーザープロパティをチェックするロジックをオーバーロードし、非常に若い開発者でもプロセスがはるかに単純で理解しやすくなりました。







好奇心の強い人のために、ソースコードをスポイラーの下に置いておき、ロジックの過負荷を個別に評価できるようにします。
 // check-phone.js module.exports = new ProcessFlow({ initialStage: 'prepareInputData', finalStages: [ 'phoneValid', 'phoneInvalid' ], stages: { /** *         * @param {Object} $flow  ,    * @param {Object} inputData   */ prepareInputData($flow, inputData) { /** *       ,    , *    ,     . *              , *   ,         */ $flow.transition('checkPhone', { phone: inputData }); }, /** *      * @param {Object} $flow  ,    * @param {Object} inputData   */ checkPhone($flow, inputData) { const someBackend = require('some-backend-module'); someBackend.checkPhone(inputData.phone) .then((result) => { $flow.transition('processCheckResult', result); }) .catch((err) => { $flow.transition('phoneInvalid', { err: err }); }); }, /** *      * @param {Object} $flow  ,    * @param {Object} inputData   */ processCheckResult($flow, inputData) { if (inputData.isPhoneValid) { $flow.transition('phoneValid'); return; } $flow.transition('phoneInvalid'); } } }); // check-user.js const checkPhoneProcess = require('./check-phone'); module.exports = new ProcessFlow({ // ,         initialStage: 'checkUserName', //     finalStages: [ 'userCheckedSuccessful', 'userCheckFailed' ], stages: { /** *       * @param {Object} $flow  ,    * @param {Object} inputData   */ checkUserName($flow, inputData) { if (inputData.userName) { $flow.transition('checkUserBalance', inputData); return; } $flow.transition('userCheckFailed', { reason: 'invalid-user-name' }); }, /** *       * @param {Object} $flow  ,    * @param {Object} inputData   */ checkUserBalance($flow, inputData) { if (inputData.balance > 0) { $flow.transition('checkUserPhone', inputData); return; } $flow.transition('userCheckFailed', { reason: 'invalid-user-balance' }); }, /** *      * @param {Object} $flow  ,    * @param {Object} inputData   */ checkUserPhone($flow, inputData) { const phone = inputData.operatorCode + inputData.number; checkPhoneProcess.start(phone, { //         phoneValid() { $flow.transition('userCheckedSuccessful'); }, phoneInvalid() { $flow.transition('userCheckFailed', { reason: 'invalid-user-phone' }); } }); } } });
      
      





各Yandex.Money支払いプロセスは、プロセス制御APIを提供するProcessFlowクラスのインスタンスです。 initialStageで説明されている機能ブロックを呼び出すstartメソッドがあります。 引数として、startメソッドは入力ハンドラーと出力ハンドラーを受け入れます。







難易度の原則



通常、プロセスには複雑なビジネスロジックが含まれているため、コードでは、IDEF-0の推奨事項に従って複雑さを制限する必要があります。









既におなじみの「方法」プロセスの図では、7つの機能ブロックが表示されているため、階層に煩わされることなく、すべてをフラットに記述する誘惑が高まります。







画像の代替テキスト







次のセクションでは、ロジックを単純化した後、最終的なプロセスがどのように見えるかを示します。







イースターエッグ:自動描画スキーム



大企業では、ビジネスプロセスがアナリストが描くよりも早く陳腐化することがあります。 悲しいかな、私たちはこの点で例外ではないので、私はより速く描くことを学ばなければなりませんでした。







IDEF-0とコードでプロセスを記述するための厳格なルールのおかげで、静的コード分析を使用して、機能ブロックとプロセスの相互関係の図を構築できます。 たとえば、 Esprima製品が適しています。 コード分​​析の結果、このツールはすべての機能ブロックと遷移を持つオブジェクトを形成し、 GoJSライブラリを使用してブラウザーで視覚化が行われます。







画像の代替テキスト







この図は、依存関係を示すcheck-userおよびcheck-phoneプロセスを示しています。 それらをデプロイすると、次のものが得られます。







画像の代替テキスト







最初の機能ブロックは図にはっきりと表示され、プロセスの出力には色が付いています。 たとえば、この図から、 userCheckFailedの結果は、電話番号を確認する段階だけでなく、名前を確認する時点でも取得できることが明らかです。 以前はとんでもないほど明白ではありませんでした。







ろうそくに値するゲームだった



支払いプロセスのリファクタリングの結果は、データ準備のプロセスを記述するためのプラットフォーム全体になりました。 リファクタリングに費やす時間の主な利点は、新しいプロセスのロジックを形成する際に厳格なルールを順守する開発者の正しい思考の列です。 これは、将来的にはリファクタリングが少なくなることを意味します。







さらに、新規参入者はプロセスの本質をすばやく把握できるようになりました。 これにより、ブリーフィングの時間を大幅に節約でき、すべてがバラバラになることを恐れずに新しいチップを導入できます。







副作用があります-ビジネスアナリストが静的なチャートを描画する必要がなくなったため、ココアを使用したコーヒーと紅茶の消費量が劇的に増加しました。








All Articles