レガシーな決済機能からの脱却を目指して

2023/09/27に公開

こんにちは、DevOpsグループでレガシー移行やパフォーマンス改善、雑用をしている 橋本 です。
今回はレガシーな決済の移行という爆弾処理についてゆるゆるお話したいと思います。
あまり細かい話が書ける状態にはないため、ふわっとした内容になってしまうことはご了承ください。

レガシー💎

レガシーコード改善ガイドにしたがえば、レガシーコードとはテストのないコードのことです。
ここでは、加えて古の遺産という意味も包含したものと考えていただけるとよいかと思います。

環境🖥️

現行 移行先
言語 PHP Ruby
フレームワーク CakePHP Ruby on Rails

背景🖼️

ココナラのバックエンドには複数のプログラミング言語、フレームワークが利用されています。
そのなかには CakePHP で構成されたレガシーな機能群があり、設計・実装において次のような課題を抱えています。

  • 機能設計がレガシー機能に引きずられる
  • コードが入り組んでおり、開発速度が制限される
  • 各所に地雷が埋まっており、ちょっとした修正が障害を引き起こしかねない

悪いことに、レガシー機能群は実質的に自動テストの追加ができず、安全なリファクタリングが難しい状態となっています。
そのため、特に移行したい機能(複雑な機能や重要度の高い機能)ほど移行が進まないという状況がありました。

とはいえ、移行しなければ未来はありません。
そこで、目の上のたんこぶである決済機能を移行することに決め、2022年の秋頃に着手しました。

進捗どうですか?😀

🙉
…………
一部機能の移行は完了しており、リリース後は特に問題無く動作しています🎉
移行のリリースに関連した不具合は一度も発生していないため、ご迷惑をかけずに済んでいることは幸いです。
ですが、なかなか触れないタイミングもあり、元々の計画からはだいぶ遅れてしまっています。
「一部リリース」「遅れている」と書いてもどれくらいかまったく伝わらないと思いますが、具体的に何%とは書きづらいところがあり、ご容赦いただければと思います。
今後の進捗にご期待ください。

決済移行、どう進めてるの?🗓️

ここでは、ざっくりとした移行の流れや各ステップの詳細を説明していきます。

1. 移行対象の決定👈

万が一問題が起こった場合の影響とリカバリを考慮し、取引規模の小さいものから少しずつ移行します。
DBやログを調査し、対象端末(PC/SP/アプリなど)や決済手段を優先順位付けしていきました。

一例として、以下の画像はサービス購入時に選択できる決済手段の一覧です。
サービス購入時の決済手段

見えているだけで5種 + 2種(ポイント、コイン)がありますね。携帯キャリアは3種あるため、さらに増えます。
購入するものによって使える/使えないが変わってくるため単純計算はできませんが、決済の組み合わせは優に100を超えます。

2. 影響範囲の調査🔎

既存のコードは経路毎に必要なものと不要なものが入り混じった状態になっていました。
不要なコードはできるだけ移行したくないため、慎重に影響範囲を調査し、必要な部分とそうでない部分を切り分けます。

3. 設計🤔

初期段階では全体の流れを設計し、決済経路ごとの移行においてはそれを少しずつ拡張するかたちで進めています。

現行の環境から移行後の処理を呼び出す

最終的にはCakePHPを通さないようにしたいのですが、いきなりできることではありません。
CakePHPの処理を通りつつ、決済処理の主要部分を移行していく方針としました。

既存処理からの大きな設計変更は避ける

レガシーコードに対してユニットテストを実装することが難しい事情があり、大胆な設計変更はハイリスク・ローリターン、今やるべきではないと判断しました。

移行対象以外の決済経路に対する影響を最小限にする

特定経路だけをパスするようなフィルタを用意し、それ以外の共通処理には手を加えないことで、他の経路には影響を与えないようにしました。
他の経路がフィルタをパスしないこと は手動テストにて担保することとなりましたが、比較的安全に移行できるようになりました。

遠回りで分かりづらい実装や過剰な密結合をやめる

基本的な処理の流れは踏襲するものの、理解に時間を要するコードや無用に結合度の高いコードなどは可能な範囲で修正することとしました。

4. 実装🚧

余計なことはあまり考えず、ベースとなる部分から少しずつ実装していきました。

実装イメージ

実際のコードとは異なりますが、イメージとしては次のような感じになります。

# ------------
# 1. 全体の流れ
# ------------

# 全体的なフローの記述と空実装の用意
def execute
  before_validate
  validate
  before_payment
  payment
  after_payment
end

def before_payment; end
# ----------
# 2. 共通処理
# ----------

# 共通部分の実装を埋める
def before_payment
  @payment_executer = PaymentExecutor::Creator.create(payment_type)
end

def payment
  @payment_executor.payment(@params)
end
# ------------------
# 3. 決済手段特有の処理
# ------------------
module PaymentExecutor
  class CreditCard
    def payment(params)
      # 決済代行会社への通信など
    end
  end
end

ユニットテスト

移行先ではユニットテストが書けるため、血反吐を吐きながらテストコードを書いていきます。楽しいです。🤤
通常privateメソッドに対するテストは書きませんが、安心を得るために小さなprivateメソッドに対するテストも用意します。
どの決済手段でも共通で使われる処理のうち、末端部分から少しずつ、足場を固めながら積み上げていきました。

5. 手動テスト🧪

丹精込めて作り上げたテストケースに基づき、結合テスト、リグレッションテストを実施します。
決済に限った話ではないですが、やらかすと非常に後が大変なので、慎重に行います。

テストケース

6. リリース🚀

リリーススケジュールの決定

大型リリースやQAチームの都合が悪いタイミングに重ならないよう、リリーススケジュールを調整します。
QAチームのスケジュールを意識するのは、リリース後の確認をお願いするためです。

本番動作確認

リリース時、QAチームの方にご協力をお願いし、本番の環境でも正しく動作することを確認していただきます。
基本的には開発環境での確認で十分なはずですが、外部に依存している部分があるため、どうしても本番での確認は避けられません。

モニタリング

決済の失敗が発生していないか、データの不整合が発生していないか、パフォーマンスが著しく劣化していないか、などリリースによる悪影響が無いかモニタリングをしていきます。

リリースに問題が無ければ次のリリースへ向かいます。果てしない移行坂です。

おまけ:Xとの遭遇👽

おまけとして、調査や実装の過程で遭遇した愉快なXたちをご紹介します。
疲れた日々に一服の清涼剤となれば幸いです。

夢破れていったものたち🪦

実は自分が着手するより前に、決済の移行をしようとしていたことがあったようです。
Railsに実装しようとした痕跡が残っており、そっと供養( git rm )するのでした。

地雷原💣

  • 絶対に通らないが通ったら盛大にエラーが発生する古のコードブロック
  • 多くの人が知らない「第2のポイント」
  • 六親等くらいの遠くで正しく動くように設定された、おかしな挙動をしそうでギリギリしない紙一重の実装
  • 明らかにis-a関係ではないのに多段階で継承されているクラス

バグ🐞

さすがに決済金額がずれたり多重決済が走ったりするようなやばいやつではないですが、いくつか既存の不具合を発見しました。
面白おかしく笑い飛ばせるものであれば記載したかったのですが、それはちょっと難しいので、ご想像におまかせすることにしたいと思います。
注意深くコードを読んでみると、発見はあるものです😇

熱の伝わるコミットログ🔥

ふと疑問を感じてログをたどると、当時の熱さが伝わってくる。そんなこともありました。
大変だっただろうなぁ🍵

これから

慎重に進めてきたおかげか、ここまでは大きなミスなく移行できています。
とはいえ進捗がよいとは言えないため、今後は着実に進めていきたいところです。
レガシーコードからの脱却、実現したいですね!

終わりに

ココナラではレガシーコードと戦ってくれるエンジニアを絶賛募集しています!
気になった方は採用ページをご覧いただければと思います。🙏

https://coconala.co.jp/recruit/engineer/

Discussion