Timee Product Team Blog

タイミー開発者ブログ

DREが考えるデータ基盤の生産性とfourkeysによる可視化

DREグループの石井です。

先日といってももうしばらく前ですが、Techmeeというイベントで生産性についてトイルの計測をしてそれを一定に抑える取り組みをしているという話をさせて頂きました 。

https://timeedev.connpass.com/event/275750/

www.youtube.com

これはDREグループ内の生産性を維持する=一定以上のアウトプットが出せる状態を作り出すための取り組みでした。 しかし、生産性といえば、古くはコード量で計測されていたような(最も、コード量は良い指標ではありませんが)ものや今で言えばfourkeysといったようなもので、どれだけアウトプットに繋がっているかが大切になります。

私の所属するDREグループでは、主に色々なデータソースからデータを抽出し、便利に分析しやすい状態にすることに責任を持つと同時に、データ基盤の運用も担当しています。

そのため、私達のグループはもとより、他の分析者の生産性を上げることに責任を持っていかないといけません。

今回はDREグループで始まったfourkeysによる生産性の計測と今後の展望について書かせていただこうと思います。


前提としてfourkeys

今回の計測に当たってはfourkeysをベースとして考えることにしました。 これについては書籍や色々な発表資料などを漁り、グループ内で勉強会などを通してある程度共通理解を得て我々の計りたいものに対してまずはこれでいってみよう、という合意が得られたためです。 我々が考える生産性については後述しますが、前提としてのfourkeysは以下の本や資料などを読んでいただけると理解が進むかと思います。

State of DevOps 2021

https://services.google.com/fh/files/misc/state-of-devops-2021.pdf

(2022もありますが、フォーム入力が必要なためパブリックに公開されているこちらを貼っています) 

エリート DevOps チームであることを Four Keys プロジェクトで確認する

https://cloud.google.com/blog/ja/products/gcp/using-the-four-keys-to-measure-your-devops-performance

継続的デリバリーのソフトウェア工学

https://bookplus.nikkei.com/atcl/catalog/22/12/01/00531/

DREグループの考える生産性の定義

我々DREグループは社内向けのデータ基盤を提供するグループです。 つまり、生産性計測の対象が1つのチームに閉じるとは限らず、何なら複数グループの活動が全て終わって1つの機能提供が完了するというケースも多くあります。

例えば、新規データをつなぎ込みモデリングした上でダッシュボードを提供しようとするケースでは、DREグループだけでなく、モデリングを担当するBIグループが絡んできます。このとき、DREグループが担当するステージング処理までが完了したとしても価値が提供できていると言えるでしょうか?そうではなく、データの取り込み、ステージング処理、モデリングダッシュボード化のすべてが完了して価値を提供できていると言えると考えています。

そのため、開発はもちろんですが、その2グループの連携がスムーズに流れることも重要な要素であり、その点も改善の対象となってくるはずです。例えば、もしドキュメントが足りなくてBIグループがモデリングに入れないのであればそれをスムーズに提供するべきだし、依頼内容が曖昧でヒアリングに時間がかかりすぎるならそこも改善対象かもしれません。

つまり、スループットとしては絵にすると以下の全体で計測・改善を行う必要があると考えています。

また、単に速度だけを追求してしまうと障害を起こしまくる仕組みになってしまいかねません。 そのため、同時に安定性の指標として障害も計測する必要があるのですが、データ基盤の場合シンプルにデプロイによる失敗を計測すればよいかというとそういうことでもなく、どうしてもデータソース側の変更などにより「何もしてないのに壊れた」ということが発生することも多くあります。 そのあたりを考慮して実装を考えていく必要があります。

実装

実装は以下の様になりました。

基本的にDORAが公開してくれている実装を使い、Github上の営みをBigQuery上に収集しています。

デプロイ周りについては、

  • ほとんどのリポジトリはmain merge時にデプロイ自動化されているので、マージをデプロイの代替指標として採用する
  • 障害は現在Notion上のポストモーテムDBで管理できているのでそこから引っ張ってくる

ということにしています。

現時点では通常のfourkeysを取得できるようにしていますが、今後は上述した通りのリードタイムをNotion上にあるバックログなどと連携させて取得していく予定です。

考察と今後について

現時点ではほぼ素のfourkeysが取得できた状態です。 最初に計りたかったものからするとやや乖離がある状態ですが、それでもいくつかの示唆を得ることができました。 例えば、dbtのリポジトリでDREグループのリリース速度は十分早いのですが、データのモデリングを行うBIグループはかなりの時間がかかっておりなにか課題がありそうなことが特定できました。

これについてはヒアリングを行っており、実際にメンバー間でモデリングについての習熟度や前提条件の理解などの差があるために、レビューで大きな時間がかかっていることがわかりました。

また、当たり前ですがDREグループだとしても意図して大きいPRを出すとマージまでの時間が圧倒的に伸びていることがわかります。

どちらも担当ベースでは理解のあることと思いますが、具体的な数値として見えてくると議論の俎上にも上がりやすく、解決に向けて動きやすくなるのでシンプルなfour keysでも意味のある可視化なのではないかと思います。

ただ、やはりこれでは最初に書いたような課題は見えてこない部分はあるので、少しずつアップデートしていき、エンドツーエンドのfourkeysを測れるようにしてより生産性の高いデータ基盤を実現する礎にしていければと思っており、そのあたりはまたブログ等で発表できればと思っています。

おわりに

我々DREグループはデータ基盤を提供する裏方の部門ではありますが、データを使った業務の生産性を最大化するために業務に取り組んでいます。

こういった話に興味がある、少しでも気になった方はお気軽にカジュアル面談に申し込みいただけると嬉しいです。

Rails edgeでCIを回し始めました 〜設定方法編〜

こんにちは、マッチング領域でバックエンドエンジニアをしているぽこひで ( @pokohide ) です。

冷やし中華はじめました的なタイトルですね。分かります。

今回はタイミーが本番運用しているRailsアプリケーションに対してRails edgeでCIを回すようになった話を紹介します。翌週には「〜見つけたエラー編(仮)〜」と題して、実際に弊社で見つけたエラーの例を紹介していきます。記事公開時点(2023年7月)のバージョンは下記の通りです。

$ ruby -v
ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [aarch64-linux]

$ rails -v
Rails 7.0.6

弊社ではRubyRailsも積極的に最新バージョンにあげる活動をしています。今回の記事はRailsに関してですが、Rubyのアップグレードも同様に行っています。過去にはRuby3.2にし、YJITを有効化にした記事を公開しているので興味があれば、ご一読ください。

tech.timee.co.jp

Rails edgeとは?

ChatGPTに聞いて楽をしてみました。

この記事ではChatGPTと同様に rails/rails のmainブランチを指すこととします。

Rails edgeは、安定版ではなく開発版なので以下のような特徴があります。

  • 将来のリリースで利用可能になるかもしれない新機能が含まれている
  • 正式リリースされていない既知のバグ修正が含まれている

安定版ではなく、まだ評価されていない機能やバグが含まれている可能性があるため、本番環境で使用する際には注意が必要です。GitHubでは、毎週本番環境で使うRailsをedgeにアップデートしているそうです*1

なぜRails edgeでCIを回すのか

タイミーでは、RubyRailsのコミュニティの進化に継続的に追随することで、高速化や機能追加などの恩恵を受け、ユーザーに最大限の価値を提供していきたいと考えています。

Rails edgeでは、将来のリリースで利用可能になる新機能を事前に検査できるため、潜在的な影響を確認することができます。また、バグや互換性の問題を早期に発見し、解決策を見つけることも可能です。その他にも、最新の機能や修正に関して何かあれば、安定版リリース前に異議申し立てをしやすいといったメリットもあります。

これらの利点を考慮し、Rails edgeでCIを回すことにしました。

Rails edgeでCIを回す

rails以外のgemはそのまま利用したいため、既存のGemfileを読み込み、railsのみを rails/rails に上書きすることでRails edge用のGemfileを用意します。

# frozen_string_literal: true

# 既存のGemfileを読み込む
eval_gemfile File.expand_path('../Gemfile', __dir__)

# 上書きしたい依存関係を削除する
dependencies.delete_if { |d| d.name == 'rails' }

# rails/railsのメインブランチに上書きする
gem 'rails', branch: 'main', github: 'rails/rails'

使用するgemのバージョンが変更されたので専用のlockファイルを用意する必要があります。BUNDLE_GEMFILE を指定する事で指定したGemfileを使用できます。

$ BUNDLE_GEMFILE=gemfiles/rails_edge.gemfile bundle install

これによりRails edgeと他のGemとの依存関係を定義したファイルの用意が完了します。次にこれらのファイルを利用してCIでテストを実行する準備をしていきます。実際の設定ファイルを簡略化して紹介しています。なお、弊社ではCIツールとしてCircleCIを利用しています。

version: 2.1
orbs:
  ruby: circleci/ruby@2.0.1
jobs:
  test:
    parameters:
      gemfile:
        type: string
        default: Gemfile
    environment:
      BUNDLE_GEMFILE: << parameters.gemfile >>
      BUNDLE_PATH_RELATIVE_TO_CWD: true
    docker:
      - image: cimg/ruby:3.2.2
    steps:
      - checkout
      - ruby/install-deps:
          key: gems-<< parameters.gemfile >>
          gemfile: << parameters.gemfile >>
      - run:
          name: Test
          command: bundle exec rspec
workflows:
  version: 2
  workflow:
    jobs:
      - test:
          filters:
            branches:
              only:
                - master
                - /^rails_edge.*$/
          matrix:
            parameters:
              gemfile:
                - "gemfiles/rails_edge.gemfile"

今回はmasterブランチと rails_edge.* ブランチでのみ実行するようにしました。 Orb - circleci/ruby で定義されるコマンドである install-deps ではgemfileパラメータを渡すと指定したGemfileを利用してbundle installを実行してくれるため、その機構を利用しています。

また、BUNDLE_PATH_RELATIVE_TO_CWD を利用することで、Gemfileではなくカレントディレクトリに対して相対的にパスを指定するのでGemの競合を回避でき、キャッシュを有効活用できます。

Rails edgeで落ちるテストに印をつける

後出しですが今回の目的は「Rails edgeでCIを回す」ことであり、全てのテストを修正することではありませんでした。そこで、今回は落ちたテストをpendingすることにしました。

skipではなくpendingを採用したのは、mainブランチの追従によりテストが成功する可能性があるためです。skipはテストの成功・失敗に限らずテストを実行しませんが、pendingはテストの失敗時のみ保留にするため修正に気づくことができます。

便宜上、これから出るであろうRails7.1で動かないこととそれまでに直すぞという意味合いも込めて以下のようなメッセージを表示するようにしています。ここは適当に変えていただくのが良いかと思います。

# frozen_string_literal: true

module PendingIfRailsEdge
  def pending_if_rails_edge
    'PENDING: Rails 7.1で動くように直す' if Rails::VERSION::STRING.start_with?('7.1')
  end
end

RSpec.configure do |config|
  config.extend PendingIfRailsEdge
end

以下のように使えます。こうすることでテストコードをgrepした時に落ちているコードをすぐに見つけることができるの便利でもあります。

context 'foo', pending: pending_if_rails_edge do
  ...
end

it 'bar', pending: pending_if_rails_edge do
  ...
end

これでRails edgeでCIを回すことができるようになりました。

Rails edge用のlockファイルを追従させる

masterブランチと rails_edge.* ブランチでRails edgeでCIを回す運用を開始してから、featureブランチでGemの追加や削除を行ったが rails_edge.gemfile の更新を忘れてmasterブランチにマージしたため、masterブランチでRails edgeでのCIがfailするケースが発生しました。

本番環境に影響がないとはいえmasterブランチのCIがfailしているのはモヤモヤすることや、せっかく導入した仕組みが形骸化してしまうため、masterブランチマージ前に気づける仕組みを導入しました。

具体的には BUNDLE_GEMFILE=gemfiles/rails_edge.gemfile bundle install を実行して差分が発生すれば失敗するステップを追加し、featureブランチでも実行するようにしました。

jobs: 
  check_outdated_gemfile:
    parameters:
      gemfile:
        type: string
    docker:
      - image: cimg/ruby:3.2.2
    steps:
      - checkout
      - ruby/install-deps:
          key: gems-<< parameters.gemfile >>
          gemfile: << parameters.gemfile >>
      - run:
          name: re-bundle install
          command: bundle install
      - run:
          name: check file changes
          command: |
            if [ `git diff --name-only << parameters.gemfile >>.lock` ]; then
              echo 'Please run `BUNDLE_GEMFILE=<< parameters.gemfile >> bundle install`'
              exit 1
            else
              exit 0
            fi

最後に

今回はRails edgeでCIを回し始めた背景や導入する方法について紹介しました。

今回の取り組みを通して、将来のRailsアップグレードにおいて遭遇するであろうバグに早期に気づき、迅速な対応ができるようになりました。まだGitHubのように *1Rails edgeを毎週取り込める状態ではないですが、引き続き頑張っていこうと思います。

次回は、Rails edgeでCI回し始めたことで見つけた問題を紹介していきます。記事公開時には公式Twitter ( @TimeeDev ) でアナウンスしていくのでフォローしていただけると嬉しいです。

また、タイミーでは一緒にサービスを成長させていく方を募集しています。もし少しでも興味を持っていただけたら、カジュアル面談受け付けておりますので是非お話ししましょう!

devenable.timee.co.jp

データサイエンスグループが心からおすすめする本をまとめてみた

こんにちは、タイミーのデータ統括部データサイエンス(以下DS)グループ所属の小栗です。

今回は、DSグループのメンバーにおすすめの本を聞いてみたのでご紹介します!*1

おすすめ本を通して、DSグループの雰囲気や、業務で活用するスキル・知識について、みなさんに伝わればいいなと考えています。

*1:先日、データ統括部メンバーのおすすめ本もnoteで紹介しましたので、興味があればご一読ください。

続きを読む

DREのMissionを合宿で決めた話

はじめに

初めまして、タイミーのDREチーム(Data Reliability Engineering Team)でエンジニアをしてます、筑紫です。

今回DREチームで実施した合宿ついてご紹介させて頂こうと思います。

DREのカルチャーを少しでも知って頂けたら嬉しいです。

DREチームについて紹介

DREチームでは、社内の様々データを集約し、クレンジングを行い、社内外で利活用できる形で提供するためのデータ基盤プロダクトの開発・運用を行なっております。

データ基盤の詳細については、ProductOwner(以降POと記載)の土川の記事をご参照ください。

tech.timee.co.jp


今年の4月に私を含め2人入社したことでメンバーの入れ変えもあり、データ基盤の開発体制が新しくなりました。

メンバーが大きく変わったこともあり、開発を進める上で今までのデータ基盤の歴史的背景や方向性の理解にメンバー間で差分があることが課題になっていました。

DREチームのMissionと合宿の目的

DREではチームとしてのMissionを定めています。

ここでいうMissionとは、ビジネスシーンでよく用いられる組織やチームのMVV(Mission・VisionValue)のMissionで、組織やチームの存在意義、果たすべき使命を指します。

Missionを定めて、チームとしての明確な目標をチームの共通言語にすることで、チームの役割や責任範囲を明確化し、成果の向上や成功につながるデータ基盤プロダクトの開発を効率的に進めることができます。

また、Missionは外部とのコミュニケーションをスムーズにし、チームのモチベーションを高める要素ともなります。

元々のMissionは以下の通りです。

『信頼性の高いデータ基盤を整備し、活用のための環境を提供する』

ただ、Missionを定義した頃から時間も経っていること、またチーム構成が大きく変わったこともあり、アップデートしたい機運が高まっていました。

今のチームでMissionを再検討することで、上述の課題も含め解消できるのではという話になり、DREチームのMissionを決めるワークショップを開催することになりました。

合宿の内容

普段はリモートワークが多いチームですが、合宿は1日だけ東京オフィスに集まり、会議室を貸し切って実施することになりました。

Missionを決めるにあたって、まずは、DREチームの存在意義についてメンバーそれぞれポストイットで意見を出し合い、それを議論しながらクラスタリングしました。

その結果を基に、Missionの方向性を導き出す形で進めました。

ただ、ポストイットの結果からMissionという形で、抽象度高いフレーズを抽出することに苦戦しました。

重要視する要素については、メンバー間で認識が概ね揃っていたものの最終的にMissionという形でどう表現するかに難儀しました。

議論中で、元々のMissionに入っていた、”信頼性”というワードを採用することになりました。

タイミーのDREチームにおける“信頼性”とは、スピード、品質、安定性、ユーザビリティを総合的に表現したものであり、ユーザがデータを利活用する上で、DREチームではこれらの要素を特に重視しています。

特にスピードについては、データが生成されてから活用されるまでの時間を短縮していきたいというPOの思いがあり、また、これからリアルタイム性を求められる要求に対応していくためにも温度感の高い指標になってきています。

また、高い”信頼性”を達成するための手段として、DataOpsという観点を導入することになりました。

DataOpsは、ガートナー社が提唱している概念で、データパイプラインの構築、自動化、監視、デプロイメントの迅速化、データ品質の向上などを重視することで、組織のデータ管理者と利用者の間の連携促進し、データの収集、処理、分析、展開のプロセスを効率化するためのプラクティスです。

このプラクティスを用いて、”信頼性”あるデータ基盤を構築していくことをMissionとすることになりました。

その後議論が進み、最終的に以下のMissionに決まりました。

『DataOpsを実現し、信頼性の高いデータ基盤プロダクトを提供する』

まとめ

日頃このようなチームの方向性などを深く話を機会が少ないので、とても貴重な時間を過ごせました。

チーム内でMissionを定めることができ、同じ方向を向いてプロダクト開発を進めていけそうで、メンバー間での満足度も高く、良かったと思います。

また、それ以上にチームで議論しながら、データ基盤プロダクトの構想や方向性をPO+開発メンバー間で共有できたという、その過程にとても価値がある会だったと思います。

今回定めたMissionを持ってデータ基盤プロダクトの成長を加速させていきたいと思ってます。


最後に、タイミーではエンジニア・データサイエンティストを初め、様々な職種のメンバーを募集してます!

カジュアル面談からでも対応できますので、少しでも気になった方は申し込み頂けると嬉しいです!

Ruby 3.2+YJIT 本番運用カンパニーになりました #rubykaigi

こんにちは。2023年1月に株式会社タイミーに入社したバックエンドエンジニアの id:euglena1215 です。

RubyKaigi 2023 がとうとう明日に迫ってきました。楽しみですね。
タイミーは RubyKaigi で初めてブース出展を行います。至らぬ点もあるかと思いますが、RubyKaigi を一緒に盛り上げていければと思っています!どうぞよろしくお願いします。

productpr.timee.co.jp

今回はタイミーが本番運用している Rails アプリケーションに対して Ruby 3.2.2 へのアップデートと YJIT の有効化を行い、パフォーマンスが大きく改善したことを紹介します。

前提

タイミーを支えるバックエンドの Web API は多くのケースで Ruby の実行よりも DB がボトルネックの一般的な Rails アプリケーションです。JSON への serialize は active_model_serializers を利用しています。

今回の集計では API リクエストへのパフォーマンス影響のみを集計し、Sidekiq, Rake タスクといった非同期で実行される処理は集計の対象外としています。

今回は Ruby 3.1.2 から Ruby 3.2.2 へのアップデートと YJIT 有効化を同時に行いパフォーマンスの変化を確認しました。そのため、パフォーマンスの変化には Ruby のバージョンアップによる最適化と YJIT 有効化による最適化の両方の影響があると考えられますがご容赦ください。

結果

以下のグラフは API リクエスト全体のレスポンスタイムの 50-percentile です。アップデート前後でレスポンスタイムが約10%高速化されていることが確認できました。

API リクエスト全体のレスポンスタイムの 50-percentile

リクエスト全体としては大きく高速化されていることが確認できました。それでは、レスポンスが遅く、時間当たりのリクエスト数が多いアプリケーションの負荷の多く占めるエンドポイントではどうでしょうか?

そこで、タイミーの Web API のうち2番目に合計の処理時間*1が長いエンドポイントへのパフォーマンス影響を確認しました。*2

以下のグラフは2番目に合計の処理時間が長いエンドポイントのレスポンスタイムの 50-percentile です。Ruby アップデートの数日前に GW 中の負荷対策のためのスケールアウトを実施したことで変化が少し分かりにくくなっていますが、Ruby 3.1.2 から Ruby 3.2.2+YJIT にしたことによって10%以上高速化されていることが確認できました。

2番目に合計の処理時間が長いエンドポイントのレスポンスタイムの 50-percentile

元々十分に高速なエンドポイントだけでなく、アプリケーション負荷の多くを占めていたエンドポイントのパフォーマンスも改善されていることが分かります。Ruby 3.2 アップデート+YJIT 有効化はパフォーマンスチューニングへの十分実用的な打ち手と言えるのではないでしょうか。

まとめ

Ruby 3.1.2 から Ruby 3.2.2 へのアップデートと YJIT を有効にしたことでリクエスト全体のレスポンスタイムの 50-percentile が約10%高速化されました。また、アプリケーション負荷の多くを占めていたエンドポイントも同様に10%以上高速化されていることを確認できました。

Ruby 3.2’s YJIT is Production-Ready でも YJIT によって Shopify が 5~10% 高速化されたと記されていることから、「Ruby 3.2 の YJIT は一般的な Rails アプリケーションを 10%程度高速化させる」と考えて良いのではないかと思っています。

これほどの高速化に尽力していただいた Ruby コミッターのみなさん、本当にありがとうございました。

余談ではありますが、自社で YJIT を有効化したことによって YJIT という技術がより自分ごとになり、内部で何が行われているのかをきちんと理解したいと思うようになりました。RubyKaigi 2023 で理解を深めようと思います。

宣伝

冒頭で説明したように、タイミーは RubyKaigi 2023 でブース出展を行います。今回の記事ではパフォーマンス改善の結果のみ紹介しましたが、タイミーでのこういった技術改善における取り組み方など話したいことは色々あるので、ぜひブースでお話しさせてください!

productpr.timee.co.jp

また、RubyKaigi 後 5/16(火)にはスポンサー振り返り会を Qiita さん、Wantedly さんと実施予定です。 「自分の会社も RubyKaigi スポンサーをしてほしいと思っているエンジニア」をターゲットにしたちょっとニッチな会ですが、もしかして自分ターゲットかも…?と思う方はぜひご参加ください!

wantedly.connpass.com

*1:合計の処理時間 = 平均レスポンスタイム x hits数

*2:最も合計の処理時間が長いエンドポイントはGWの繁閑の影響でレスポンスタイムに変化が生じ、比較が困難であったため除外しています。

YAML Programming in GitHub Actions

こんにちは。2022年11月に株式会社タイミーに入社した sinsoku です。

最近はGitHub ActionsのYAMLを書く機会が多く、YAMLも複雑化してきました。
しかし、日常的にYAMLを触っている職人以外にはパッと読めないことも多いので、社内の方々が読めるようにGitHub ActionsのYAMLの書き方をまとめたいと思います。

目次

三項演算子

GitHub Actions には三項演算子がないため、代わりに論理演算子を使います。

- steps:
  - run: echo "${{ (github.ref == 'refs/heads/main' && 'production') || 'staging' }}"

参考: Expressions

環境変数(env)

環境変数を使いたい場合は env で定義します。

env:
  TIMEE_CEO: ryo
  TIMEE_CTO: kameike

jobs:
  job-env:
    runs-on: ubuntu-latest
    steps:
      - run: echo $TIMEE_CEO

      # この実装だと置換後の文字列 `echo kameike` を実行する
      - run: echo ${{ env.TIMEE_CTO }}

ただし、 env で env の値を指定するとエラーになるケースがあるので注意してください。

env:
  DEPLOY_ENV: ${{ (github.ref == 'refs/heads/main' && 'production') || 'staging' }}
  # Unrecognized named-value: 'env'. Located at position 1 within expression: env.DEPLOY_ENV == 'production'
  IS_PROD: ${{ env.DEPLOY_ENV == 'production' }}

jobs.<job_id>.env でも同様のエラーが出ます。

jobs:
  job-error:
    runs-on: ubuntu-latest

    # Unrecognized named-value: 'env'. Located at position 1 within expression: env.DEPLOY_ENV == 'production'
    IS_PROD: ${{ env.DEPLOY_ENV == 'production' }}

jobs.<job_id>.steps[*].env であればエラーになりませんが、同じ階層の env の値は参照できません。

env:
  TIMEE_CTO: kameike

jobs:
  job-env:
    runs-on: ubuntu-latest

  steps:
    # この実装だと `echo "true, foo, -bar"` を実行する
    - run: echo "${{ env.IS_KAMEIKE }}, ${{ env.FOO }}, ${{ env.BAR }}"
      env:
        IS_KAMEIKE: ${{ env.TIMEE_CTO == 'kameike' }}
        FOO: foo
        BAR: ${{ env.FOO }}-bar

参考: Workflow syntax for GitHub Actions

変数(outputs)

汎用的な名前の環境変数を定義すると、何かのCLIコマンドに影響する可能性があります。
これを避けるために、outputs で変数を定義することもできます。

steps:
  - id: var
    run: |
      echo "x=foo" >> "$GITHUB_OUTPUT"
      echo "y=bar" >> "$GITHUB_OUTPUT"

  # この実装だと `echo "foo, bar"` を実行する
  - run: echo "${{ steps.var.outputs.x }}, ${{ steps.var.outputs.y }}"

outputs は env と違い、 bash の処理結果を変数に定義することができます。

steps:
  - uses: actions/checkout@v3

  - id: var
    run: |
      echo "terraform-version=`cat .terraform-version`" >> "$GITHUB_OUTPUT"

  # この実装だと `echo "1.4.4"` を実行する
  - run: echo "${{ steps.var.outputs.terraform-version }}"

参考: Defining outputs for jobs

関数(workflow_call)

ワークフローの一部を別のファイルに定義し、関数のように呼び出すことができます。

# .github/workflows/_say.yml
name: say

on:
  workflow_call:
    inputs:
      name:
        required: true
        type: string

jobs:
  hello:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, ${{ inputs.name }}."

  bye:
    needs: hello
    runs-on: ubuntu-latest
    steps:
      - run: echo "Bye, ${{ inputs.name }}."

使い方は以下の通りです。

jobs:
  job-say:
    uses: ./.github/workflows/_say.yml
    with:
      name: kameike

参考: Reusing workflows

関数 + 配列(dynamic matrix)

workflows_call の入力には真偽値、数字、文字列しか使えません。

The value of this parameter is a string specifying the data type of the input. This must be one of: boolean, number, or string.
引用: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_callinputsinput_idtype

しかし、少し工夫することで配列を扱うことができます。

# .github/workflows/_say_multi.yml
name: say_multi

on:
  workflow_call:
    inputs:
      names:
        required: true
        type: string

jobs:
  matrix:
    runs-on: ubuntu-latest
    outputs:
      names: ${{ steps.set-matrix.outputs.names }}
    steps:
      - id: set-matrix
        run: |
          names=`echo '${{ inputs.names }}' | jq -csR 'split("\\n") | map(select(. != ""))'`
          echo "names=$names" >> $GITHUB_OUTPUT

  hello:
    needs: matrix
    runs-on: ubuntu-latest
    strategy:
      matrix:
        name: ${{ fromJSON(needs.matrix.outputs.names) }}
    steps:
      - run: echo "Hello, ${{ matrix.name }}."

使い方は以下の通りです。

job-say-multi:
  uses: ./.github/workflows/_say_multi.yml
  with:
    names: |
      ryo
      kameike

GitHub CLIの活用

GitHub APIを使うことで、チェックアウトせずにファイル名を取得することができます。

job-gh-matrix:
  runs-on: ubuntu-latest
  outputs:
    files: ${{ steps.set-matrix.outputs.files }}
  steps:
    - id: set-matrix
      run: echo "files=`gh api '/repos/{owner}/{repo}/contents/.github/workflows?ref=${{ github.sha }}' --jq '[.[].name]'`" >> $GITHUB_OUTPUT
      env:
        GH_REPO: ${{ github.repository }}
        GH_TOKEN: ${{ github.token }}

job-gh-echo:
  needs: job-gh-matrix
  runs-on: ubuntu-latest
  strategy:
    matrix:
      file: ${{ fromJSON(needs.job-gh-matrix.outputs.files) }}
  steps:
    - run: echo "${{ matrix.file }}"

画像のようにファイル名の一覧で並列にJobを実行できます。

まとめ

YAMLむずかしいですね。

この記事が読んだ方の参考になれば幸いです。
また、弊社のGitHub Actionsやデプロイフローについて気になることがあれば、フッターの採用ページのURLから面談の申し込みをどうぞ!

フロントエンドの単体テストについて勉強会を開催しました

こんにちは、フロントエンドエンジニアの樫福です。

タイミーのフロントエンドの開発に関わるエンジニアの人数が増えてきました。大人数で開発しながら品質を高い状態に保つには、品質に対する共通認識を作ることが大切です。

このたび、チームでフロイントエンドの単体テストについての勉強会を開催しました。 testing-library というフロントエンドのテストに使うライブラリを例に挙げ、具体的な手法よりも、テストを実装する前に抑えておきたい思想についてフォーカスしました。

  • フロントエンドでテストしたい項目
  • フロントエンドの単体テストを難しくする要因
  • testing-library を使って壊れにくいテストを作る方法
    • 要素を見つけるクエリ
    • ユーザの動作をシミュレーションする user-event
    • 要素の状態を検査する jest-dom
  • 実際にテストを書いてみる
    • テストが書けないケース
  • 運用するときの注意点
  • おわりに
続きを読む