RAKSUL TechBlog

ラクスルグループのエンジニアが技術トピックを発信するブログです

ラクスル全体の90%のデータチェックを基盤サービスで処理できるようにした話

こんにちは!ラクスル事業本部DTPスクラム所属の堀です。ラクスルには今年の3月に入社しました。

ラクスルではECの本体から入稿データチェックに関する機能を切り出してサービス分割を行っております。DTPスクラムチームでは主に データチェック基盤 に対する開発を行っています。

※データチェック:入稿データを印刷に適した形かチェックし、適した形式にすること

歴史あるプロダクトなのでどうしてもEC本体に残ってしまっているレガシーなコードが存在しています。段階的にサービスへ切り出していく過程のうち、今回はデータチェックの一部機能の移行を紹介します。 規模が大きくなったシステム改修の難しさと学びについて少しでも共有できればと思います。

最初のデータチェック基盤構想についての記事も参考にご覧ください。

背景

ラクスル本体は初期から存在するPHPのコードと新しいRubyのコードがあります。ラクスル本体に残っているPHPで書かれたレガシーな管理画面が「Admin」と呼ばれています。その巨大なコードの保守がつらい、いわゆる技術的負債になってしまっている側面があるので「Admin」を撲滅しよう、ということで、社内ではこの活動を「Admin撲滅」という名前で呼んでいます。

「Admin撲滅」のDTPパートとして、注文管理画面のDTPオペレーターによるデータチェックに関する機能をデータチェック基盤へ連携し、DTPオペレーターの関心事を切り出した話をご紹介します。

データチェック基盤とは

データチェック基盤はラクスルの基盤を作る大型プロジェクトの流れで作られました。現在、ラクスル本体のECやその他のECと接続し、ラクスルのデータチェックを担うサービスとなっています。

元々はラクスル本体のコードにモノリスな機能として存在していましたが、現在はサービス分割され、各サービスからのリクエストを受け付ける基盤となっています。

今回は本体に残ってしまっているオペレーターのチェックの管理機能を、オペレーションを見直しつつ、移行しました。

やったこと

Admin撲滅(DTPパート)前の世界

注文の管理画面を見てデータチェックのオペレーターはデータチェックに関する箇所を判断し、業務を行っていました。

Admin撲滅(DTPパート)後の世界

DTP業務を行うオペレーターはそのデータチェックに関する業務のみが表示される管理画面を利用することができるようになりました。注文情報はこれまで通りEC側の管理画面を利用します。

システム構成

データチェック基盤と各サービスとのやりとりにはRPCを利用しています。 下記は今回のプロジェクトでの連携方法を簡単な図として抜粋したものです。

全体から見ると管理画面に関連した一部のアーキテクチャであるという前提での話になってしまうのですが、 raksul.comのサーバーサイドは主にAdminで使われているSymfony(PHP)と現在コアとなっているRuby on Railsで構成されています。 また、Datacheck基盤のサーバサイドはRuby on Railsで構築されています。

データチェック基盤で定義したProtocol Buffersを元にしてTwirpにてクライアントを生成し、RPCによるServer to ServerのInternalなAPIでECとやり取りをしています。

今後のことも考えて、Server to ServerでのやりとりをPHPからでなく、新しいコードであるRubyからデータチェック基盤にリクエストしてほしかったため、ECのRuby on RailsへラッパーとなるAPI実装しました。現状はデータチェック関連だけ切り出したのでPHPからそのラッパーとなるAPIを叩いてデータチェック基盤とのやりとりをしています。

とはいえ、いずれ完全にレガシーコードがなくなるその日には、管理画面もこのアーキテクチャではなくなるでしょう。

結果どうなったか

  • 巨大なPHPのデータチェックに関するコードを保守しなくてよくなった
  • 商品情報の変更・追加などの保守がやりやすくなった
  • オペレーションの効率化がしやすくなった
    • データチェック基盤に分離されているため、DTPオペレーターのための機能拡張が可能に!
  • オペレーターのための画面がデータチェック基盤に集約され、UIも使いやすくなった
    • 例えばデータチェック待ちの一覧のソートと色分けにより、DTPオペレーターは画面の上から順番にチェックをやっていけばいい世界になった

データチェック基盤の管理画面(データチェック一覧サンプル)

当初はシステム的な拡張性・保守性の側面を目的としていたため、古い管理画面の機能をそのまま移植(いわゆるリプレース)しようとしていました。しかし進めていく中で、そのまま移行するよりそもそもオペレーションを整えてから移行した方が、移行の工数も少ないし、オペレーター的にも嬉しいという気付きがありました。

そのためオペレーションのフロー改善 + システム移行を一緒にやることでDX的な流れになりました。 一例を挙げると、DTPオペレーターが自分で判断する箇所をデータチェック領域に集中させるための工夫があります。どのデータをチェックするべきかオペレーター各自で判断するのではなく、パーソナルにフィルタリングされた一覧を上から順番に拾っていけば良いような状態をシステムで実現しました。

ただし、作り込みすぎないことは意識していて、開発時もそのバランスは常に気をつけていたと思います。

リリース時に気をつけたこと

  • データチェック基盤はラクスル本体以外にもダイレクトメールやポスティング、エンタープライズなど分割された各ECのサービスからリクエストを受け付けるため、既存影響、後方互換性の維持を意識しました。
  • 移行後のシステムに問題が発生するリスクを考慮して、旧管理画面でのデータチェックも行えるようにして進めました。リプレース案件とかだとよくやる手法だと思います。並行稼動ですね。
  • オペレーション業務と調整し、段階的に移行できるようデータチェックの流量を調整しました。
    • オペレーターが行うデータチェックも商品によって難しい・簡単や多い・少ないといった違いがあるので、影響が少ないものからリリースしていきました。

おわりに

サービスごとにチームが別れていると、越境した開発で難しい側面もありますので、チーム間での連携を意識して開発を進めました。私は入社して間もないのでこのプロジェクトに参加できたことで業務ドメインへの解像度が上がりました。

規模が大きくなっていくプロダクトと組織的課題はそれだけでブログの記事が何本か書けそうですね。

「Admin撲滅」もまだ完全移行できていなかったり、DTPパートだけしかできていなかったりと課題は残っているので引き続き改善していきます! 課題があるからこそ面白みもありますよね。

エンジニアがROIに向き合ってみたら起こったこと

この記事はラクスルの2022年アドベントカレンダー8日目です。今日はエンジニアがROIを考えるようになった結果起こった変化や、私のチームでのROIの計算方法についてご紹介したいと思います。

自分と所属しているチームの紹介

改めまして、ノバセル株式会社でデータエンジニアをしているyamnakuと申します。最近は登山やキャンプなどアウトドア活動にはまっている新卒2年目です。twitterもぜひフォローしていただけると嬉しいです。Snowflakeに関する話題が多めです。

さて、ノバセルの開発組織は3チームに分かれているのですが、私はCoreDevelopmentチーム(以降、CoreDev)に所属しています。普段の開発業務では、

  • Snowflakeを利用したデータ基盤の開発
  • ノバセルのプロダクト群で共通して利用する基盤システムの開発
  • エンジニアの生産性向上のためのDevOpsの導入・改善

などを行っています。

ROIを考えることになった背景

表題の通り、最近CoreDevではROIを考えるようにしています。最初に、ROIを考えるようになった背景を軽くご紹介します。

CoreDevは、「ノバセルの開発チーム全体の生産性を向上させる」ことをミッションとしたチームであり、前述したような業務に加え、他の開発チームから開発依頼も受け付けています。他チームで困っていることのサポートを担うことで、開発チーム全体の生産性を改善できると考えているためです。たとえば、他チームで行なっていた定常作業が開発時間を圧迫しているということであれば、その作業の自動化やアウトソーシングを進めることで開発時間を確保する、という取り組みも行っています。

CoreDevが今年8月に発足して以降、そうした依頼が多く、前述したようなCoreDevが本来やろうとしていたことになかなか着手できない状況が続いていました。 また、抱えているタスクが多くなり、どのタスクをまず着手するべきかも不明瞭になっていきました。

そうした状況を解決するために、タスクのROIを計算していくことで、より明確な優先順位をつけていくことを試みました。

ROIを考えることで解決しようとした課題

改めて、ROIを考えることで解決しようとした課題は以下の通りです。

  • 他開発チームからの依頼を過剰に受け過ぎており、CoreDevで本来やるべき仕事が出来ていないという懸念
  • 他開発チームからの依頼を含め、タスクに共通の基準がないため、優先順位が付けられない

取ったアクション

まず、ROIを計算するべく、チーム内のエンジニアにそれぞれ担当タスクを割り当て、そのタスクのROIを計算しました。また、ROIの計算方法については各エンジニアがバラバラな方法で計算しないよう、あらかじめフォーマットを作っておき、そのフォーマットに数字を当て込み計算するようにしました。

CoreDevで使っているROIの計算方法については後述するとして、主に開発コストと得られるリターンの見積もりが必要です。そのため、主に以下のようなポイントを調査しました。

  • そのタスクを終わらせるのにどれくらいの開発工数がかかるか、それをお金に換算するといくらか
  • そのタスクを終わらせると誰がどれくらい喜ぶのか、それをお金で換算するといくらか

必要に応じて、他チームのエンジニア・PdM・BizDevとディスカッションしながら明確化していきました。

CoreDevで作成したROIの計算用ドキュメント
CoreDevで作成したROIの計算用ドキュメント.

得られた効果

具体的な計算方法は最後に解説するとして、先にどのような効果があったかをご紹介します。

タスクの優先順位が明確になり、納得感を持つことが出来た

当初の課題であった優先順位づけの課題についてはROIを算出することで一定解消させることができました。特に、他チームからの依頼についてもROIという共通の基準で比較することで、CoreDevでやりたい仕事を後回しにしてでも行うべきか否かを判断することができるようになりました。

また、他チームとの調整もやりやすくなりました。新しい依頼が来たときにもその依頼のROIを計算してもらい、今抱えている他のタスクと比べることで自動的に優先順位がわかり、CoreDevでいつ頃受け入れ可能かがすぐに判断できるためです。CoreDevで受け入れられない場合、依頼者は依頼内容を自分たちでこなすか、依頼内容を見直してROIをより高くするかの二択になるので交渉はシンプルなものになります。

また、このようにROIを元に優先順位を決めてタスクを行うことで、チームメンバーの仕事に対する納得感も高まりました。

ROIの算出を通じてタスクへの解像度が上がる

面白いことに、ROIを計算した結果決まった優先順位と、ROIを計算する前に感覚的に考えていた優先順位はほとんど一致していました。つまり、ROIを計算する前に頭の中で考えていたざっくりとした見積もりはおおよそ間違っていないということです。

しかし、感覚値に比べて、実際に出てきたROIがかなり低いタスクもいくつかありました。これらのタスクは、そのタスクによって得られる利益が曖昧である、という共通点がありました。つまり、役立ちそうなことはわかっているが、実際に言語化しようとすると何に役立つかはまだはっきりしていない、というものでした。

例を挙げると、「新しいBIツールの導入」がそのうちの一つでした。「新しいBIツールを導入すると何が変わるのか?」「現状のビジネスサイドの分析業務をどう改善できるのか?」というところへの我々の解像度が足りていないために、得られる利益について十分に計算出来ていないのではないか、と感じました。

解像度が低い状態でタスクを進めてもあまり成果は得られないことが多いため、これらのタスクについては、まず解像度を高め、ROIを正確に試算できるようにする、という結論に至りました。

普段の業務の中でビジネス感度が上がった

ROIを計算するようになってから、各メンバーの「ビジネス感度」も上がったように思われます。毎週行っている振り返りの中でも、今週やった仕事のROIはどうだったか、ビジネス上のインパクトはどれくらい生み出せたのかという視点で会話することが増え、よりビジネスへどう貢献するかという観点で仕事を進めるようになりました。チームメンバー間で、ROIという共通の物差しを持って会話することが出来るため、議論もスムーズに進むようになったと感じます。

CoreDevで利用しているROIの算出方法

では、CoreDevで利用しているROIの計算方法を紹介します。

参考にしたのが、Google Cloudが出していた変革の成果を数値化: DevOps 変革で収益を獲得する方法です。当初、DevOps関連のタスクのROIから算出しており、その際にこの資料を用いたのがきっかけです。この資料をベースに、以下のような項目を利用してROIを計算することにしました。

  • 利益
    • そのシステムにより減る不必要な仕事
    • 上記で浮いた時間を再投資に回した場合の得られる利益
    • そのシステムによって得られる利益
  • 投資
    • 開発するのにかかる人件費
    • 運用するのにかかるインフラ費
    • 技術的な訓練にかかる費用, 新しい技術の習得費用

各項目についてより詳しく説明していきます。

利益の算出

利益は、以下の3つの要素を合計したものになります。

そのシステムにより減る不必要な仕事

新たに開発するシステムが何かしらの効率化を目指している場合、それによって誰かの仕事が減るということになります。それはそのままそのシステムを開発した際の得られる利益になると考えられます。これが、「そのシステムにより減る不必要な仕事」を算出すべき理由です。この項目は、以下のような点がわかれば計算できます。

  • 年間で削減できる仕事の時間(一人当たり)
  • 仕事が減る人数
  • 一人当たりの平均給与

なお、会社が実際に支払っている給与は社会保険料・年金などが上乗せされているので、我々は平均給与に1.3という係数をかけて算出しています。

上記で浮いた時間を再投資に回した場合の得られる利益

不必要な仕事が減った場合には、その時間を再投資できるわけなので、その場合の利益も考慮する必要があります。この利益を計算する上では色々な要素が絡んでくるため、注意して考える必要があります。CoreDevでは、前述のGoogleの資料を参考に以下の要素に分解して算出しています。

  • 上記で浮いた時間が年間に占める割合
  • 年間の開発リリース数
    • 50(1週間に1,2のリリースができると仮定)
  • 年間売上
  • アイデアが成功する確率
    • 1/3に固定(Googleの資料を元に仮定)
  • アイデアが成功したときに何%売上を向上させるか
    • 1%(Googleの資料を元に仮定)

基本的な考え方としては、年間のリリース数=試すことができるアイデア数、と捉え、そのアイデア数をどれくらい増やすことが出来るか? という観点で考えます。アイデアが成功すれば売上が上がるはずなので、アイデアの成功率と売り上げへの貢献度を加味します。

なお、上記の計算方法は、「エンジニアの開発時間を浮かせることができた場合」の計算方法です。エンジニア以外の人の仕事時間が削減される場合には、「開発リリース数」の部分を適宜変更する必要があります。たとえば、営業の業務時間を削減できるシステムであるならば、「年間に行ける営業数」に読み替えてみる必要があるでしょう。

そのシステムによって得られる利益

効率化ではなく付加価値を生み出すタスクの場合、このセクションの利益が重要となります。このセクションの計算方法はタスクに依存するため、適宜タスク内容を吟味して算出する必要があります。たとえば、DevOpsの改善タスクの場合、「システムのダウンタイム短縮」という利点があるので、それを利益として計上できます。つまり、ダウンタイム時に失った売上を失わずに済む、という観点で利益を計算します。また、プロダクトの機能開発の場合、その機能によって顧客数・顧客一人当たりのLTV・チャーン率などに影響を及ぼすはずなので、その増加分を利益として計上します。

投資

投資については、開発にかかるコストを計算すればよく、基本的には以下のような項目について検討しています。

  • 開発するのにかかる人件費
  • 運用するのにかかるインフラ費
  • 技術的な訓練にかかる費用, 新しい技術の習得費用(ダウンタイム)

特に、新しい技術を導入する際には、その技術を利用することでのパフォーマンス低下やその技術を他の人にも覚えてもらうコストがかかってくるため、その点も考慮します。

このように、利益と投資が計算できたら、ROIを算出することが可能です。CoreDevでは、今後3年間に得られる利益と、必要な投資額を算出してROIを計算しています。

最後に

ここまで読んでいただきありがとうございました。

この記事では、ROIをエンジニアが計算した結果起こったこと、また計算方法についてご紹介しました。ROIに囚われすぎてしまうことの弊害ももちろんあり、全てをROIに基づいて決めるべきだとは考えていません。しかしながら、ROIを計算することは、タスクへの解像度が向上したり、業務への納得感を持つことが出来るという点でも有用であると思います。もし、我々と同じような課題感を持つチームがあれば、業務の中にもROIを取り入れてみてはいかがでしょうか?

ECS Fargate による Web アプリケーションを AWS CDK で構築し、GitHub Actions でデプロイしたときに悩んだ箇所を紹介

はじめに

ECS Fargate による Web アプリケーションを AWS CDK で構築し、GitHub Actions でデプロイするシステムを作る際、悩んだ箇所とその解決策を紹介します。

構成概要

AWS CDK で各種 AWS リソースを作成し、resources 配下の Dockerfile を build し、デプロイをするという構成となっています。

下記はディレクトリ構成です。

├── resources/
    ├── workflows/
        └── deploy_prod.yml
        └── deploy_qa.yml
├── cdk/
└── resources/
    └── lnginx/
    └── laravel/
    └── Dockerfile-laravel
    └── Dockerfile-nginx

デプロイは GitHub Actions で行なっています。

GitHub Actions に CDK の実行権限を付与するために OIDC を利用しています。

CDK 内で OIDC を付与したロールを作成し、そのロールを GitHub Actions のワークフローで使用しています。 そのため、初回の CDK デプロイはローカル環境で行なっています。

悩んだところ

DB Migration の実行順序

課題

アプリケーションの DB は AWS Aurora を使用しています。 AWS VPC 内からしか DB にアクセスできないため、GitHub Actions 上で Migration はできません。

そのため、ECS スタンドアロンタスクで Migration を行うことにしました。 スタンドアロンタスクとは ECS Service を使用せずに実行する ECS Task のことでバッチ処理などに使われます。 ECS Task は VPC 内にあるため DB に接続することができます。

最初は CDK で Migration 用のタスクを実行しようとしました。

// まず DB Migration の実行
const runTaskAtOnce = new RunTask(stack, 'RunDemoTaskOnce', { migrationTaskDef });

// 次に Service の更新
const service = new ecs.FargateService(this, 'id', {cluster, serviceTaskDef});

※ スタンドアロンタスクの実行にはこちらのライブラリを使用

https://github.com/pahud/cdk-fargate-run-task

しかし、上手くいきませんでした。DB Migration の完了後に Service を更新(ソースコードの反映)して欲しかったのですが、Service の更新が開始されたあとに DB Migration 用のスタンドアロンタスクが立ち上がってしまいました。

CDK でスタンドアロンタスクを立ち上げるためには AWS CloudFormation のカスタムリソースを使用する必要があり、上記ライブラリでもそのようにしています。

CDK をデプロイした際に、CDK 上のコードではスタンドアロンタスクの記述が先にあるのでカスタムリソースの実行が Service の更新よりも先に開始はされるのですが、カスタムリソースによるスタンドアロンタスクのプロビジョニングは非同期で行われるため、Service の更新が先に完了したという状況でした。

CDK には EcsRunTask というクラスがあり、いかにもスタンドアロンタスクを実行してくれそうではあるのですが、このクラスは ECS Task を実行する AWS Step Functionsを作成してくれるだけで、ECS Task の実行まではしてくれません。

EcsRunTask で作成された Step Functions を実行するためにはやはりカスタムリソースを使用するしかなく、上述した同じ問題に直面します。

解決策

CDK でのデプロイをやめて GitHub Actions でスタンドアロンタスクを実行することにしました。 GitHub Actions は step 内の処理の完了後に次 step に進むため、それぞれの step で DB Migration と Service の更新をすることで、順番を担保しました。

step Run ECS db migration task の内部では AWS CLI の ecs run task を使用してタスクを実行しています。

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Run ECS db migration task
        uses: yyoshiki41/ecs-run-task-action@v0.0.7
        with:
          task-definition: ./db-migration-task-definition.json
          command: '["sh", "-c", "migrate"]'

      - name: ECS Service Deploy
        run: |
          npm run cdk:deploy

※ GitHub Actions 上でのスタンドアロンタスクの実行にはこちらのライブラリを使用 https://github.com/yyoshiki41/ecs-run-task-action

その後の課題

GitHub Actions で DB Migration を実行することで課題も出てきました。それはECS Task の設定値の記載が重複することです。

ECS Task を実行するためにはタスク定義の情報が必要であり、DB Migration 用のタスク定義情報は json ファイルとして保持し、ECS Service 用のタスク定義情報は CDK コード内に記載があります。

そのため、Migration タスクと Service タスクで設定値に変わりがない環境変数とシークレットの記載が重複しています。 これにより環境変数を追加するときに全く同じ記載を CDK のコードと json ファイル、両方にしなくてはなりません。

このままでも環境変数は env file を使用することで共通化することはできますが、シークレットには env file のような仕組みがないため json ファイルと CDK コードでは共通化する方法がありません。

そのため、GitHub Actions 上でのスタンドアロンタスクの実行をやめて ECS Service の反映とは別の DB Migration を実行するスタックを作成し、そのスタックを ECS Service より前の GitHub Actions step で実行する改修を入れる予定です。

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Run ECS db migration task
        run: |
          npm run cdk:deploy:migration

      - name: ECS Service Deploy
        run: |
          npm run cdk:deploy:service

このようにすることでシークレットを CDK 内のプログラミングコードのモジュールとして共通化できるためです。

デメリットとしては2つのスタック間での AWS リソースの共有がしにくいことですが、今回のケースではVPCやサブネット、セキュリティグループは既存の同一のものを利用しているため問題ありません。

スタック内で作成したものではない既存の ALB のリスナー設定を使用して ECS Service を作成する

課題

CDK で ALB を使用して ECS Service を作成するときには ECS Patterns という便利なモジュールが用意されています。

const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
  cluster,
  memoryLimitMiB: 1024,
  cpu: 512,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
    command: ['command'],
    entryPoint: ['entry', 'point'],
  },
});

loadBalancedFargateService.targetGroup.configureHealthCheck({
  path: "/custom-health-path",
});

このモジュールを利用すると ALBやターゲットグループ、タスク定義をまとめて作成してくれます。

しかし今回の要件ではターゲットグループは既存のALBリソースを使用するがターゲットグループは CDK で作成するというものでした。 またリスナーも新規作成せずに既存の物を使います。

既存のリスナーを取得しそれにターゲットグループを紐付けようとしても、スタック内で作成したALBのリスナーしか設定変更ができないため失敗してしまいました。

const listener = ApplicationListener.fromLookup()
listener.addTargets('id', {port: 8080,targets: [tg]});

解決策

リスナーとCDKで作成されたタッゲートグループの紐付けは手動で行い、ECS Service にターゲットグループを紐付けることで実現できました。

service.attachToApplicationTargetGroup(targetGroup)

attachToApplicationTargetGroup メソッドには「Don't call this function directly」と記載もあるため、できる限りALBはスタック内で作成するのが CDK のベストプラクティスには合っていそうです。

タスクに登録されている別コンテナのデータにアクセスしたい

課題

今回タスクには n=Nginx と Laravel のコンテナを登録しました。 Nginx の設定上、Laravel のイメージ内で build した javascript のファイルにアクセスできるようにする必要がありました。

Laravel で使用する Vuejs の設定の関係上、javascript ファイルを build するためには npm のインストールだけではなく、PHP、composer のインストールも必要であり、Nginx のイメージでも Laravel のイメージと同じように下記のようなインストールを行ってしまいました。

Nginx Dockerfile 例

RUN apt-get update \
&& apt install -y lsb-release ca-certificates apt-transport-https software-properties-common gnupg2
RUN echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/sury-php.list
RUN curl -fsSL  https://packages.sury.org/php/apt.gpg| gpg --dearmor -o /etc/apt/trusted.gpg.d/sury-keyring.gpg
RUN apt update && apt install -y php8.1 php8.1-dom php8.1-mbstring php8.1-curl php8.1-zip php8.1-gd

RUN composer update && composer install --optimize-autoloader --no-dev \
&& yarn install && yarn build

EXPOSE 80

解決策

Laravel コンテナのファイルをボリュームとして共有することで、Nginx イメージでは PHP のインストールや javascript の build をする必要がなくなりました。

Laravel Dockerfile

VOLUME ["/application/public"]

CDK

nginxContainer.addVolumesFrom({sourceContainer: laravelContainer.containerName, readOnly: true})

おわりに

参考になれば幸いです。

Docker Composeを用いてPyCharm上でデバック

ノバセル株式会社CoreDevelopmentチームの田村です。今回はDocker Composeを用いてPyCharm上でデバック実行できるようにするためのTIPSを紹介します!

現在チーム内では、SnowflakeでPythonベースのプロシージャを作る機会があります。(Snowpark for Pythonを利用しています) その際のローカル開発環境はDockerで動かしているため、普段使っているIDEでデバッグ実行が簡単にできたらと思いやってみました!

前提

今回TIPSを紹介するにあたり使用するディレクトリの構成を共有します。

.
├── app
│   └── run.py
├── dev.Dockerfile
└── docker-compose-local.yml

1 directory, 3 files

また、docker-compose.ymlは以下の通りです。

version: "3"

services:
  app:
    build:
      context: .
      dockerfile: dev.Dockerfile
    container_name: app
    tty: true
    command: bash
    volumes:
    - ./app:/app

※ 今回は簡略化のため立ち上げるコンテナを1つにしています。

具体的な方法

1.PyCharmのPreferences...を選択

2.Python Interpreterの選択

3.Docker Composeの“選択

4.環境を設定をする

各設定の説明は以下の通りです。

  • Server...実行するDocker環境の設定(既存のものがない場合はCreate new...を選択)
  • Configuration files...読み込ませたいdocker-compose.ymlファイルを指定
  • Service...実際に開発するコンテナのサービス名
  • Environment Variable...追加の環境変数の設定(docker-compose側に既にenvを指定している場合は不要)

終わったらNextを押しましょう。

5.System Interpreterを選択してCreateする

6.設定されていることを確認

Dockerのイメージ内で指定したバージョンがインタプリタに設定されていれば準備完了です!(こちらの環境の場合は3.8系をイメージに指定しています)

実際に試してみる

デバックしたい箇所に対してブレークポイントを指定します。

Debugを選択し実行します。

Debuggerを開くとスタックトレースが確認できます!

Consoleを開くと変数の操作もできます!

注意

docker-compose.ymlのサービス名を途中で変えた場合は、PyCharmを再起動してPython Interpreterを再設定しましょう。

PyCharmを再起動しないと上手くdocker-compose.ymlの情報が反映されないためです。

Jestをv28にバージョンアップした話

はじめに

はじめまして、ラクスル事業部 PBU(Printing Business Unit)開発チームでフロントエンドエンジニアをしている沖です。

ラクスルに参画して半年たちましたが、まだまだフレッシュな気持ちでいます。

さてRAKSUL Advent Calendar 2022の12月5日の記事では、

今年の8月に対応した「Jestをv28にバージョンアップした作業」について話していきたいと思います。

経緯

PBUで扱っているrepositoryではフロントエンドの単体テストとしてJestを使用しておりますが、開発環境のメンテナンスの一環として24.8.0から28へバージョンアップ対応を行いました。

アップデートする前の開発環境

  • Node.js: v14.19.2
  • Jest: 24.8.0
  • Typescript: 3.8.3

どのように対応したか

まずJestのv25からv28までにある破壊的変更について調査することからはじめました。

調査方法

調査してわかったこと

調査してざっくり下記のことがわかりました。

  • jest-environment-jsdomパッケージを個別にインストールする必要がある
  • ts-jestのversionもv28に上げる必要がある
  • babel-jestでのversionもv28に上げる必要がある
  • TypeScriptのバージョンを4.3以上にする必要がある

実際に手を動かしてみた

調査する限り大規模な変更はなさそうということで、実際にバージョンアップ作業を行いました。

対応したことを順番に記述していきます。

  1. Jestのv28.1.3をインストール
npm install --save-dev jest@28.1.3
  1. jest-environment-jsdomパッケージを個別にインストール

アップグレードガイドより、v28にバージョンアップする際はjest-environment-jsdomを個別にインストールする必要があるようです

npm install --save-dev jest-environment-jsdom
  1. Jestの設定ファイルにtestEnvironmentを追記
"testEnvironment": "jsdom
  1. テストを実行するとts-jestのエラーが発生したので、Jestのバージョンにあわせてts-jestをアップデート
Error: ● Invalid transformer module:
  "~/node_modules/ts-jest/dist/index.js" specified in the "transform" object of Jest configuration
  must export a `process` or `processAsync` or `createTransformer` function.
  Code Transformation Documentation:
  https://jestjs.io/docs/code-transformation
npm install --save-dev ts-jest@28.0.2
  1. テストを実行するとbabel-jestのエラーが発生したので、Jestのバージョンにあわせてbabel-Jestをアップデート
TypeError: Cannot destructure property 'config' of 'undefined' as it is undefined.

      at Object.getCacheKey (node_modules/babel-jest/build/index.js:151:8)
npm install --save-dev babel-jest@28.1.3
  1. テストを実行すると下記エラーが発生。
Cannot read property 'createNodeArray' of undefined

こちらのissueによるとTypescriptを上げる必要があるとのことでした。

  1. Typescript3.8.3から4.3.4へバージョンアップ
npm install typescript@4.3.4

4.3.4のバージョンアップに伴い以下の修正を行いました。

  • importしているファイルの拡張子.d.tsを削除
  • Typescriptのバージョンアップの影響で下記エラーが発生していたので、こちらのissueを参考にtypescript-eslint5.40.0へバージョンアップしてエラーを解消

      error Parsing error: Cannot read property 'map' of undefined
    
  • typescript-eslintの他にEslintts-loaderなど関連するパッケージをバージョンアップ

  • Eslint系パッケージのバージョンアップの影響によりeslint-loaderで下記エラーが発生していたので、子コンポーネントでprops のプロパティを直接を書き換えていた箇所を親コンポーネントで書き換えるように修正

      error  Unexpected mutation of "hoge" prop
    

    ※補足) Eslint系のパッケージは下記のバージョンにアップデートしました

    • eslint: 7.22.0
    • eslint-loader: 4.0.2
    • eslint-plugin-prettier: 3.3.1
    • eslint-plugin-vue: 7.7.0
  • テストを実行するとエラーは解消されましたが、下記のように警告が出ていました。

Option "testURL" was replaced by passing the URL via "testEnvironmentOptions.url".

警告の文言通りにJestの設定ファイル内にあるtestURLtestEnvironmentOptionsに変更

"testURL": "http://localhost/"

"testEnvironmentOptions": {
    "url": "http://localhost/"
}
  1. ローカルのエラーと警告は解消されましたが、次はCircleCIのテストでタイムアウトしてしまいました。
Too long with no output (exceeded 10m0s): context deadline exceeded

CircleCiの公式ドキュメントを見てみると下記の記述がありました。

Jest テストの実行時には、--runInBand  フラグを使用してください。 このフラグがない場合、Jest はジョブを実行している仮想マシン全体に CPU リソースを割り当てようとします。 --runInBand  を使用すると、Jest は仮想マシン内の仮想化されたビルド環境のみを使用するようになります。

  1. runInBandのオプションを追加して無事CircleCiのタイムアウトエラーも解消されました
"test:ci": "jest --runInBand"

感想

npmパッケージのバージョンアップってついつい後回しにしてしまいがちですよね。

ラクスルに参画するまでは受託開発の現場が多く、腰を据えてバージョンアップ作業に取り組むのはほとんど初めての経験でした。

バージョンアップ作業はトライ&エラーの繰り返しという地道な作業ではありますが、個人的にはパズルを解いているようで苦労しながらも楽しんで作業ができたように感じます。新しい自分こんにちは。

また、npm auditしてみると脆弱性のあるパッケージがちらほらあったのでそれも気持ちが熱いうちに対応していきたいと思います。

最後に

自分が楽しみつつ作業ができたのは、ひとえに記事を上げてくださった先人たちの苦労があってこそであります。

この記事もどこかでバージョンアップに苦戦しているエンジニアの一助になれば幸いです。

ラクスルではエンジニアを募集しています!どしどしご応募お待ちしております。