はじめに
この記事は Wano Group Advent Calendar 2025 の 20 日目の記事となります。
概要
弊社では Video Kicks という、カラオケ配信に加え、世界 100 カ国以上のストアでミュージックビデオを配信できるサービスを開発・運営しています。
今回は、弊サービスのうち Perl で記述された箇所を Vue・Ts・Go に移行したので、その流れを紹介します。
移行の経緯と現状
弊サービスの移行前に利用されている主な言語は以下のとおりです。
ミュージックビデオ配信登録・・・Perl、JavaScript
カラオケ配信登録・・・Vue.js 3、TypeScript、Go
カラオケ配信の方が新しく開発されたサービスのため、ミュージックビデオ配信もこれに合わせて移行しました。
また、ミュージックビデオとカラオケでは、リポジトリが別となっていました。
そのため、サービス全体に関わる機能追加を実施する際は、2 つのリポジトリに対して似たような変更を異なる言語でする必要があります。
今回の移行により、この問題が解消されます。
※ミュージックビデオ配信登録のリポジトリの最初のコミットは2016-10-24でした。移行までの間 9 年ほどここを開発・保守してきた歴史があります。
移行したのはどんなアプリか
今回移行した中でもメインの画面となる、ミュージックビデオ配信の登録画面は 3 セクションに分かれているフォームです。
動画ファイル設定
ここでは、動画ファイルのアップロードや、サムネイル・プレビューに関する設定を行います。
図: 動画ファイル設定セクション
ビデオ配信ストア設定
このミュージックビデオを配信するストアを選択します。
図: ビデオ配信ストア設定セクション
動画情報設定
タイトルやアーティスト名、ジャケット画像など、メタ情報を設定します。
図: 動画情報設定セクション(メタ情報の入力)
何からやるか
移行は以下の流れで進めました。
- 1. 移行方針を決める V1
- 2. 移行方針を決める V2
- 3. フロントエンドモックの作成
- 4. 実装予定 API 一覧ごとにタスクを分担し、実装を進める
- 5. フロントエンドの粗を修正
- 6. API とフロントエンドの連携
- 7. E2E テスト(V1)とバグ修正
- 8. E2E テスト(V2)とバグ修正
- 9. 本番環境での最終確認
- 10. リリース
1. 移行方針を決める V1
ここでは大まかに、以下のことを実施・仮策定しました。
- 状態管理の方法
- 互いに影響し合う項目をフロントと API どちらで変更するか
- 起こりうる保存アクションの一覧化
- 既存画面の動作検証
- API をざっくり策定(エンドポイントは決めない)
- 使用や実装における相談事項
また、今回移行するミュージックビデオ配信登録は、カラオケ配信登録と画面構成や登録ステップが似ています。
そのため、カラオケ配信登録サービスの開発時の反省点を生かした設計を心がけました。
カラオケ配信サービス開発時の最も大きな反省点として、
- 登録情報を管理するコンポーネントが分散しており、副作用を追うのに苦労する
ことが挙げられるため、これを改善した設計を最優先事項としました。
状態管理の方法
まずは、状態管理の方法から決めていきます。
ただし、ここでいう状態管理は、
- この画面で使用する登録情報
- バリデーション結果
- 各種モーダルの開閉状態
です。
今回はリアクティブな変数を、provide inject 経由で利用することとしました。
※ provide inject は React で言うと Context です。
Vuex や Pinia 等を使わない理由は、Vue.js3 の機能(provie inject)で十分・剥がすのが大変・処理が追いにくいなどが挙げられます。
また、状態を分散させず、それぞれ1箇所で保持することで副作用を追いやすくします。
以下のようなイメージ
type MusicVideoAllInfo = {
mvAssetStateStore: Ref<MusicVideoAsset>; // 動画ファイル設定
mvMetaStoreStateStore: Ref<MusicVideoMetaStore>; // ビデオ配信ストア設定
mvMetaStateStore: Ref<MusicVideoMeta>; // 動画情報設定
validateAllState: Ref<MusicVideoValidateAllResult>; // バリデーション情報
// 各種状態の更新メソッド
updateMvAsset: (newMvAsset: MusicVideoAsset) => void;
updateMvMetaStore: (newMvMetaStore: MusicVideoMetaStore) => void;
updateMvMeta: (newMvMMeta: MusicVideoMeta) => void;
updateAll: (newMvAllInfo: MusicVideoAllInfo) => void;
};
保存系 API
pages からの通信とする
このサービスの根幹となる保存系 API は 3 つ。移行したのはどんなアプリかで示した 3 セクションそれぞれを更新する API です。
これらの保存系 API の通信は pages コンポーネントに限定することで、登録情報の更新を 1 箇所にまとめ、処理を追いやすく、読みやすくします。
各種末端のコンポーネントから emit によりイベントを伝播して、pages コンポーネントから各種保存系 API を叩きます。
レスポンスを getAll にする
また、保存系 API のレスポンスを getAll とします。
これは、可能な限り副作用を API 側で完結させて、フロントエンドのロジックを簡素化することを目的としています。
本サービスはある項目の設定が別の項目に影響するような条件が複雑に絡み合います。
これは、ミュージックビデオ配信ストア毎に仕様が異っていることが理由です。
例えば、あるストア X(仮) には SD(解像度が小さい動画)が配信できないのでアップロードした動画がSD画質の場合、ストアXの設定を外すのような、セクションを跨いだ他の設定への副作用が発生します。
フロントエンド側でこの仕様に対応すると、条件によって複数回 API を叩いたり、別コンポーネントにイベント伝播したりと実装が複雑になってしまいます。
そこで、全ての副作用を保存系 API 内で完結させてレスポンスを getAll とすることで、フロントエンド側の実装はレスポンス内容で状態を更新するだけになります。
この決定は大成功で、実装終盤まで実装に修正を加えやすくなりました。
図: コンポーネント間の通信フローと状態管理の構成イメージ
API の仮策定
移行前の画面を操作し、ユーザーによる UI 操作と各種アクションを洗い出しました。
それを元に、必要な API をざっくりとリストアップします。
ここでは次項で行うメンバーへのイメージ共有のため、メソッド名を列挙しています。
一覧のイメージ
- 画面 A
- GET API
- getXXX
- getYYY
- getZZZ
- POST API
- postAAA
- uploadBBB
- submitCCC
- PUT API
- putDDD
- putEEE
- putFFF
- GET API
メンバーによるレビュー
上記で策定した内容をメンバーに共有し、フィードバックをいただきます。
また、仕様の不明点確認や API の分け方などを相談し、反映します。
2. 移行方針を決める V2
レビューへのフィードバックをもとに、さらに以下が決定しました。
各ページへのルーティングを新しく作成する
Perl 実装のページと、Vue 実装のページでは URL の形式が異なっていたため、この機会に統一しました。
これは、ページの URL に使用する ID をすべて統一することで、
下の例のようにメンバー間でのコミュニケーションをしやすくすることが目的です。
- 変更のイメージ(URLはサンプルです)
旧URL
// サービスによって使っているidが異なり、コミュニケーション時に今言ったIDは何IDか?と毎度確認が必要
/page/new/video/:video_id // ミュージックビデオ
/page/new/karaoke/release/:release_id // カラオケ
新URL
// メンバー間のコミュニケーションがrelease_idに統一されてgood!
/page/new/music_video/release/:release_id // ミュージックビデオ
/page/new/karaoke/release/:release_id // カラオケ
main ブランチにどんどんマージする
開発内容に大きな変更がある場合の手法として release ブランチを作成しておき、
最終的な機能リリース時に release ブランチを main ブランチにマージし、本番反映することがあります。
一方で、今回は開発したものをどんどん main ブランチにマージしていく手法を採用しました。理由は、コンフリクトやブランチが腐ることを防ぐためです。
この移行はタスクの優先度としては低めで、他の機能追加などが入ればそちらを優先するという位置付けです。
したがって、メンバーの手が空いた場合に少しずつ開発を進めることになります。
開発期間は伸びるので、いつまでもマージできない release ブランチが残ります。コンフリクトを避けるには定期的に main ブランチを取り込む必要があります。
上記の問題を防ぐため、新規のルーティングは開発・stage 環境でのみ切り替え可能な状態にして、main ブランチにどんどんマージすることとしました。
結果的に、最終的なリリース時に大きな問題は起こりませんでした。
API 一覧の策定
API の仮策定で洗い出した内容をもとに、より詳細な API 一覧を作成しました。
この一覧では、エンドポイントや開発状況、担当者などを管理し、チーム全体で進捗を把握できるようにしています。
最終的には 25 エンドポイント程度になりました。
- 一覧の例(※ エンドポイントなど各項目はサンプルです)
| エンドポイント | 開発状況 | 開発担当者 | 画面 | メソッド | 既存流用が可能か | 目的 | 備考 |
|---|---|---|---|---|---|---|---|
| /api/v1/music_video/asset | 完了 | 社員 A | 動画ファイル設定 | PUT | × | 動画ファイル設定の保存 | getAll 形式でレスポンス |
| /api/v1/music_video/meta_store | 開発中 | 社員 B | ビデオ配信ストア設定 | PUT | △(一部流用) | ストア設定の保存 | カラオケ側の実装を参考 |
| /api/v1/music_video/meta | API 連携待ち | 社員 C | 動画情報設定 | PUT | × | メタ情報の保存 | バリデーション実装後に対応 |
| /api/v1/music_video/all | レビュー待ち | 社員 A | 共通 | GET | × | 全情報の取得 | 初期表示時に使用 |
| /api/v1/music_video/asset/upload | 開発中 | 社員 D | 動画ファイル設定 | POST | ○ | 動画ファイルのアップロード | 既存のアップロード API を流用 |
| /api/v1/music_video/thumbnail | 未着手 | 未定 | 動画ファイル設定 | POST | × | サムネイル画像の生成 | 動画アップロード完了後に実行 |
| /api/v1/music_video/submit | 未着手 | 未定 | 共通 | POST | △(一部流用) | 配信申請の実行 | 最終確認後に実行 |
このように一覧化することで、以下のメリットがありました。
- 進捗の可視化: 誰がどの API を担当しているか、どこまで進んでいるかが一目でわかる
- 既存資産の活用: 流用可能な API を明確にすることで、開発工数を削減できる
3. フロントエンドモックの作成
ここからは実際の開発に入ります。
まずは API との通信を行わず、フロントエンドのみで動作するモックを作成しました。
見た目や動作のイメージを共有することや、必要と思われるコンポーネントの準備などが目的です。
4. 実装予定 API 一覧ごとにタスクを分担し、実装を進める
各種 API の実装を進めます。
メンバーごとに担当を割り振り、他のタスクの量によって適宜引き継ぎを行うなど柔軟に進めました。
5. フロントエンドの粗を修正
API の実装がある程度進むと、フロントエンドから API を叩けるように API 連携部分の実装が必要となります。
ただし、この時点ではフロントエンドモックはあくまでも動きのみ模倣しているため、実装作業の変更範囲が大きくなってしまいました。
そのため、複数メンバーが並行して作業する時、他メンバーとのコンフリクト多発が問題となりました。
そこで、まずフロントエンドで不足している箇所を可能な限り本実装に近付けました。
ここでの実装はイベント伝播やモデル・インターフェースの定義などを含みます。
6. API とフロントエンドの連携
前項の続きとして、フロントエンドにて API 連携部分を実装します。
モデルやインターフェースが定義済みとなったので、ここでのタスクは、API クライアント部分を少し修正 + 動作確認 で済む状態になりました。
7. E2E テスト(V1)とバグ修正
エンジニアによって、stage 環境でのテストを実施しました。
発見したバグや改善点は以下のように一覧化し、対応状況を管理しました。
- 一覧の例(※ 各項目はサンプルです)
| バグ詳細 | 発生した画面 | 種別 | ステータス | 対応担当者 | 発見者 |
|---|---|---|---|---|---|
| サムネイル生成後に画像が表示されない | 動画ファイル設定 | バグ | 対応完了 | 社員 A | 社員 B |
| ストア選択時のチェックボックスの挙動が旧画面と異なる | ビデオ配信ストア設定 | バグ | 対応中 | 社員 D | 社員 C |
| 保存ボタン押下後のローディング表示がない | 動画情報設定 | 改善 | 対応完了 | 社員 B | 社員 A |
| SD 動画アップロード時にストア X が自動で外れない | 動画ファイル設定 | バグ | 未着手 | 未定 | 社員 D |
| エラーメッセージの文言が旧画面と統一されていない | 共通 | 改善 | 対応完了 | 社員 C | 社員 B |
8. E2E テスト(V2)とバグ修正
前項のバグ修正後、次はエンジニアに加え、運用メンバーによるテストを実施しました。
運用メンバーによる操作視点で、新たなバグが見つかったため、再度解消します。
9. 本番環境での最終確認
移行後の画面はルーティングが既存のものと異なっています。
エンジニアのみ新規画面、エンドユーザーは旧画面に振り分けられるよう設定して、最終確認を実施しました。
10. リリース
ついにリリース!
途中で別の機能追加が入るなど、しばらく移行が進まない期間もありましたが、エンジニアが 3 名のチームで他タスクと並行しながら、ゆるゆると 1 年ほどで移行を完了させることができました!
良かった点
API 一覧の策定
API 一覧の策定を実施したことで、機能単位の分担や引き継ぎがしやすくなりました。
開発期間が長引いても、どの API がどこまで進んでいるかが明確なため、途中から作業を再開しやすい状況を維持できました。
保存系 API のレスポンスを getAll に統一
保存系 API のレスポンスをgetAllにしたことで、フロントエンド側の状態管理が簡素になりました。
複雑な副作用を API 側で完結させることで、バグ修正や機能追加がしやすい状況を保てました。
苦労した点
getAll やバリデーションへの負担集中
getAll やバリデーションのタスク規模が大きくなり、特定メンバーへの負担が偏ってしまいました。
また、この API が完成しないと着手しづらい別 API が複数あり、結果的に開発終盤に作業が集中しました。
フロントエンド側の実装が簡素にできるというメリットはありますが、API の実装が難しくなるのでどちらを優先するかは判断が難しいところです。
フロントエンドモックから本実装への移行
フロントエンドモックはあくまで動きの模倣だったため、API 連携に向けた本実装では作業規模が想定以上に大きくなりました。
複数メンバーが同時に作業することでコンフリクトが多発するため、API 連携前にイベント伝播やモデル定義を整備する対応が必要でした。
旧画面との挙動合わせ
E2E テストの段階で、旧画面との細かな挙動の違いが多数発見されました。
チェックボックスの動作やエラーメッセージの文言など、ユーザー視点では重要な差異を一つずつ解消する必要がありました。
まとめ
9 年間 Perl で運用してきたミュージックビデオ配信登録画面を、Go + Vue.js 3 へ移行しました。
移行を通じて得られた主な学びは以下のとおりです。
- 設計段階での決定が重要: 保存系 API のレスポンスを getAll にする設計は、フロントエンドの実装を大幅に簡素化し、開発終盤までの修正容易性を保つことができました
- 一覧化による進捗管理: API 一覧を策定し、担当者・ステータスを可視化することで、長期間のプロジェクトでも分担や引き継ぎがスムーズに行えました
- 段階的なリリース戦略: main ブランチへの継続的なマージと、環境ごとのルーティング切り替えにより、リスクを抑えたリリースが実現できました
移行後は、カラオケ配信とミュージックビデオ配信のコードベースが統一され、今後の機能追加や保守がしやすくなりました。
この記事が、同様のレガシーシステム移行を検討している方の参考になれば幸いです。
弊社グループでは一緒に働くメンバーを募集中です、ご応募お待ちしています!