Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

プロダクト本部の誕生と組織変更の狙い

みなさん、こんにちは。プロダクト本部 本部長のid:tetsuro-ito です。Classiでは、8月に組織体制を再編して新たな組織体制に移行しましたので、今日はその内容と狙いを説明します。 以前のブログでも組織に関する話を紹介しており、その継続的な話になるため、あらかじめ参考記事として紹介しておきます。

なぜ組織変更をしたか

昨年の記事でチームトポロジーを参考に組織編成を考えた話を紹介しました。当時も念頭においていたのはプロダクトの開発をよりスムーズに行えるようにし、事業目標を達成しにいくことが狙いとしてありました。2022年度のClassi社内の認識として、2020年のインシデント・高負荷障害を乗り越え、サービスもきちんと安定稼働してきたことから、いわゆるサバイバルモードを抜け出し、ラーニングモードの中で事業目標を達成しにいくつもりでした。

しかし、半年間そうした組織で運用して行った結果、狙いとは裏腹にあまりうまくいきませんでした。ClassiではICT教育の環境変化に伴って、学習系のサービスに注力をしていく戦略を取っています。2023年度にはこれまでの学習機能を刷新して、新たに学習トレーニングという機能をリリースしていく想定でしたが、その開発に黄色信号が灯ってしまう状態でした。そのため方針を転換し、まずはきちんとこの学習トレーニングを開発し、お客様に届けられるような体制へシフトしました。その甲斐もあって、今期は無事に機能のリリースができています。

また、以前よりClassiではマトリクス組織という組織形態を採用していました。エンジニアやデザイナー、マーケターなどの職能を束ねる組織とClassiの各機能を担当するファンクションチームの縦横の組織でした。職能ごとに切り分けた結果、デザイナーやデータ系の組織などを誕生させることができ、メリットもあった一方で指揮系統が2箇所あることで、事業目標に向けた動きが難しかったり、情報連携にも課題があるなど、いくつかの課題もありました。

どのように組織を変更したか

組織変更のbefore/after

今回はClassiのプロダクトに関わる組織を大きく再編しました。上記の図の通り、これまでディレクターやデザイナーの職能を統括していたプロダクトデザイン本部(プロデザ本部)とエンジニアやデータサイエンティストを統括していた開発本部、プロダクトマーケティングに携わっていたプロダクトマーケティング部(PMM部)を一つの本部に統合し、それをプロダクト本部と名付けました。プロダクト本部は3つの部署からなる本部で学習PMF部、Growth部、プラットフォーム部の3つの部署で構成されています。

学習PMF部

まず、学習PMF部についてです。PMFとはProduct Market Fitの略語で、プロダクト開発やマーケティングの分野で用いられる用語です。前述したようにClassiでは、新たな学習体験を顧客へ届けるために学習トレーニング機能をリリースしています。この学習トレーニング機能をきちんと開発し、市場に受け入れられ、活用していくことが主なミッションとなります。この部に所属する開発グループとして、学習トレーニング機能を開発するチームや、学習コンテンツを搭載する機能を開発するチーム、コンテンツを作成し搭載するチーム、個別最適化学習のレコメンドエンジンを開発するチームなどが所属しています。

Growth部

次はGrowth部についてです。こちらの由来もグロースハックなどのグロースで既存のサービスを育てていく意味合いがあります。こちらは主にClassiの既存機能を担当する開発グループが所属しています。コミュニケーション機能やコーチング機能、サービスの利用開始の設定登録や認証の機能など、さまざまなものがあります。こちらの部署では、既存の機能を改善し、顧客によりよく利用してもらうことを主なミッションとしています。

プラットフォーム部

最後にプラットフォーム部です。こちらはその名の通り、サービスの基盤のようなものを担う組織です。主な機能としてインフラ/SREやQA(Quality Assurance)の機能を持った組織が所属しています。

以前の記事でチームトポロジーの概念を参考にしていると紹介しましたが、その観点でいうと、ストリームアラインドチームは学習PMF部とGrowth部で、プラットフォーム部はシステムプラットフォームチームの位置付けとなります。

プロダクトボードの導入 *1

プロダクトボードと横断ボード

今回、各部の意思決定の仕組みとしてプロダクトボードという仕組みを構築しました。ここはこれまでプロダクトマネージャーが担う職責でしたが、一人にかかる負担が高く、専門性も広く見切れないという課題がありました。それに対して、プロダクトマネジメントをそれぞれの職能のリーダーが分業で行うプロダクトボードという仕組みを採用し、それぞれの職能の合議をもって意思決定をしていくようにしています。

プロダクトボードの構成として、プロダクトマネジメントトライアングルを参考にしており、エンジニアリングサイドのリーダー、顧客サイドのリーダー、ビジネスサイドのリーダーを配置し、良い綱引きができるような体制です。まだまだ理想的な配置ができているかといえば、まだまだですが、ここをこれから育成し、きちんとしたプロダクトマネジメントが行える体制としていきたいと考えています。

また、これまでの課題として、ファンクションチームで構成されていたため、Classi全体のプロダクトの方向性を作り上げる組織が不明確でした。そこで今回は、それぞれのプロダクトボードの上に横断ボードという会議体を設置し、事業の計画やプロダクトロードマップの検討、それぞれの開発のモニタリングなどを検討できるようにしています。

Classiは2025年のビジョンとして、

先生とともに、学びから学ぶ仕組みを創り、ワクワクする子供を増やします

というビジョンを掲げています。今回の組織変更はこのビジョンの達成に向けて一丸となって動いていけるようにしているつもりです。今後も運用を続ける中でさまざまな困難が出てきて、都度見直すこともあるでしょうが、それも全てClassiの掲げるミッションやビジョンを達成するためです。

このような取り組みに共感いただける方は、ぜひとも当社の採用ページより話を聞きにきてください。

採用ページ

*1:プロダクトボードはエス・エム・エス社の取り組みを参考にしています

【開発者インタビュー #4】小川 耀一朗

こんにちは!Classiで働く開発者インタビューシリーズ企画の第4回は、データAI部の小川さんです。

まず簡単に自己紹介をお願いします

2020年4月に新卒でClassiに入社しました。開発本部のプロダクト開発部に配属され、その後2022年7月に同じ開発本部のデータAI部に異動しました。

ー どのような経緯でデータAI部に異動したのですか?

大学で機械学習に関する研究を行っていたため、就職活動の時からデータAI部に興味がありました。ただ、プロダクト開発にも興味があったり、当時は配属がプロダクト開発部しかなかったため、まずはソフトウェアエンジニアとしてプロダクト開発部に配属となりました。その時からいずれはデータAI部に入りたいと思っていましたね。

異動希望をお願いしていて相談の結果データAI部に異動*1することとなりました。今振り返ると最初はプロダクト開発部でソフトウェアエンジニアとして学べたことはとても良かったと思っています。

ー 最初の配属がプロダクト開発部で良かったと思うのはなぜですか?

Classiがどうやって動いているのかなど、開発を経験したからわかることが沢山あると感じています。 特に現在担当しているCALEはClassiのサービスとの接続が強いため、経験がとても役に立っています。 自分の成長や経験に合わせて、入社時に相談し想定していたキャリアステップを進むことができているなと思っています。

Classiへの入社経緯、入社理由は?

大学で自然言語処理の研究をしている中で、それを応用して教育や学校現場に役に立つものが作りたいと思っていました。就活で逆求人イベントに参加した時にClassiと出会い「ここだ!」と思って決めました。

ー なぜ教育だったのでしょうか?

言語習得や言語教育を支援するための研究がテーマで、その中で自然言語処理を用いて文法の誤りを訂正するツールを作っていました。それを、言語学や教育現場の先生などが集まるような教育関連の学会で発表した時に、実際の現場を知る方々に「こういうものがあると嬉しい!」と声をかけていただいたことがきっかけでした。

ちなみに、研究室選びの時は「プログラミングをやりたい」という気持ちぐらいで教育に興味があったり何かやりたいことがあるわけではありませんでした(笑)。プログラミングができる研究室に入った結果、教育のテーマを持っている研究室だったため、その中で自分は文法誤り訂正を研究テーマに選んだ、という偶然な出会いですね。

ー Classiに決めた具体的なポイントはどのようなところでしたか?

Classiのミッションがいいなと思ったところですね。

教育学会で先生方とお話をしている中で、その先生方の業務の負荷が高いということを知りました。Classiを知った時、自分ができるプログラミングや言語処理を使って、全国の先生方に貢献できるんだなと思いました。自分の想いがミッションに体現されていると感じました。

プログラミング教育などにも興味はありましたが、まだまだ自分が学ばないといけないことが沢山あって人に教えるほど詳しくないので、それは将来的にやっていきたいなと思っています。

Classiでの仕事内容を教えてください

プロダクト開発部として、EC2からECSへの移行や、CALEというレコメンドエンジンをWebテストのおすすめ演習機能として搭載することを行いました。 データAI部に異動してからは、学習トレーニング機能にCALEを連携したり、CALE自体の開発・運用・検証を行なっています。

※その他CALE関連の記事はこちらをご覧ください。 tech.classi.jptech.classi.jp

ー Webテストおすすめ演習機能と学習トレーニング機能についてもう少し詳しく教えてください。

どちらも先生が課題を配信して生徒が問題を解いて学ぶ機能です。Webテストは主に模試対策を重視したコンテンツ、学習トレーニングは模試の範囲にも対応しつつ、日常的な学習にも使えるコンテンツとなっています。 CALEによって生徒さんの回答状況に合わせて個別最適化した問題をレコメンドできます。先生方が直接回答状況を確認したり、問題を出したりしなくてもシステムで学習トレーニングに取り組めるようになりました。

ー プロダクト開発部からデータAI部に異動したことで何か変化はありましたか?

データAI部に異動したことでやはりデータという観点で検討する意識が強くなりました。例えば、リリースした機能が本当に使われているかということをデータから確認したり、ユーザーの課題をデータから特定できないかを考えたりするようになりました。また、データを活用するためのデータ基盤やデータ連携についても興味を持つようになりました。

Classiでの仕事の面白さや、やりがいについて教えてください。

データから利用状況を把握し、データに基づいてユーザーの課題を見つけ「仮説を立てる→検証する→修正する→改善」と繋げていくことに面白さややりがいを感じます。まだまだ課題は多いですが、着実に改善していきより良いプロダクトに成長させたいと思っています。 また、リリースした機能がちゃんと使われているということも嬉しいです。

ー 具体的にどんなデータを見ているのですか?

TableauやRedashのダッシュボードのグラフを毎日見ています。 Webテストの機能を例に上げると、テストを配信している学校数、テストが完了している学校数、テスト後のレコメンドした問題への取り組み状況など、さまざまなデータを見ています。 模試の前は利用率が上がったりしているのがわかったり、色々なことがデータから読み取ることができてとても面白いです。

日次利用学校数

ー 嬉しかったリリースはどのようなものがありますか?

1月にリリースした絞り込み機能ですね。 Webテストのおすすめ問題の先生画面の改善で、生徒のおすすめ問題の回答状況を確認する画面に学年やクラスで絞り込みをしたり、並べ替えができる機能をリリースしました。ダッシュボード上ではなかなか効果が見えにくい部分ではあったのですが、データだけでは見えない機能を使った現場の先生の嬉しいお声もいただいたりもして、とても嬉しかったですね。その他にも色々な機能改善のリリースを行っていて、ユーザーの声が届くことはとても嬉しいです。

WEBテストおすすめ問題取の先生画面(赤枠:改善箇所)

Classiでの仕事の難しさや課題について教えてください。

少し矛盾する部分もありますが、まだデータからはなかなか見えにくいことも多いという難しさを感じます。例えば、生徒に学習内容を定着させたいと考えたとき「定着したかどうか」を何をもって判断できるのか、など非常に難しいです。

ー なぜデータからは見えにくい部分も多いのでしょうか?

まだ教育業界ではデジタルよりも紙がまだ強いのが現状です。例えば先生はクラスの模試の偏差値変化を紙でチェックし、情報をそのまま紙で残していることも多いです。そのため直接先生や生徒さんにヒアリングして定性情報を集めてわかることも多く、Classiを使ってくれている人の感情や状況などの理解を深めていかないといけないと思っています。

一方で、マストハブサーベイを取得するなど、先生の声を定量情報として取得していけるようにも動いています。

働く上で大切にしていることは何ですか?

Classiのミッションをもじる形ですが「自分の無限の可能性を解き放つこと」です。自分で自分を制限せず、まずはやってみることを大切にしています。Classiのミッションを実現するためには、まずそれが必要なのかなと思っています。

ー 具体的に日頃意識していることはありますか?

自分にできないことが沢山あるということはわかってますが、最初から「できない」と諦めないようにしています。そういう意識を持っていないと、自分の弱さに負けてしまうので(笑)やってみてわかることもあるし、わからないところは教えてもらいながら進めています。

ー 意識していたからこそできた成長は何かありますか?

色々できるようにはなったと思っています。例えばバックエンドやフロントエンド、データ基盤など、好き嫌いなく触った結果、色々な技術を学ぶことができています。技術はもちろんだけど、周りを巻き込みながら自分主導で進めていく、ということにもチャレンジしていっています。

ー 新卒4年目で感じる自身の成長は何かありますか?

1年目2年目はわからないことだらけだったし、一緒にやってくれる先輩がいたからこそというのがあったのですが、今は「自分で進めていく」という気持ちが強くなってきているのかなと思っています。まずは技術を身につけ目の前を頑張ることが中心でしたが、徐々に全体を見れるようになってきたり、視野が広くなったと思っています。それがわかるようになってきて、チームのことやプロダクトのこと、どういうプロセスで開発していくのか、などの話に目を向けられるようになってきたと感じています。

でもまだ、臆病になっている自分もいたり、やってみたけれどうまく話を進められなかったり、関係者を巻き込めなかったりということもあるのでまだまだです。これからも部署や組織にとらわれず、改善したいという気持ちを前に出して頑張っていきたいと思っています!

最後にひとこと

教育という大きなテーマの中で、先生や生徒の役に立つものをつくること、それは難しいことであり面白いことでもあると思っています。Classiの仲間たちと力を合わせながら、より良いプロダクトを作っていきたいと思います。


新卒入社してこれからの成長も楽しみな小川さんでした。次回もお楽しみに!

Classiでは「子どもの無限の可能性を解き放ち、学びの形を進化させる」というミッションに、ともに向き合っていただける仲間を募集しています。ご興味をお持ちの方は、 お気軽にお問い合わせください!

■採用ページ

corp.classi.jp

■各ポジションごとの求人

*1:Classiではプロダクトで収益を上げているため、最終的にプロダクトのデリバリーまでを変えていかないとデータ関連業務の価値を発揮できません。そのため、キャリアの初めにエンジニアリングの素養を身につけることでデータ関連業務の幅を広げられるメリットがあると考えており、ソフトウェアエンジニアリングとデータサイエンスの両方をバランスよく学ぶことができる環境を提供し育成しています。

AWS GlueからAWS Batchにしたことで費用を75%削減した

こんにちは、最近データエンジニア業を多くやっているデータサイエンティストの白瀧です。

これまでClassiのデータ基盤は、Reverse ETLをしたり監視システムを導入したりとさまざまな進化をしてきました。しかし、Classiプロダクトが発展するとともにデータ量が増加し、これまでのデータ基盤では耐えられない状態に近づいてきました。

そこでデータ基盤の一部(DBからのExportを担う部分)のリアーキテクチャを実施したので、この記事で紹介したいと思います。

概要

Classiのデータ基盤では、Amazon RDSからAmazon S3へJSONで出力し、その後GCS→BigQueryという流れでデータを送り、BigQueryからもBIツールやReverse ETLなどで使っています。詳細は、Classiのデータ分析基盤であるソクラテスの紹介 - Classi開発者ブログを参照してください。

AWS Glueを使ってRDSからS3に出力していた基盤から、AWS BatchとスケジューラーにGoogle CloudのCloud Composerを使った基盤にリアーキテクチャしました。 主な変更点は以下になります。

  • 並行処理を管理しやすくするためにバッチ処理を実装する言語をPythonからGolangに変更した
  • CloneしたRDSに接続することで負荷増加によるサービス影響が出ないようにした

【リアーキテクチャ前】

Glue JobでS3にJSON形式でExportしてた
Glue JobでS3にJSON形式でExportしてた

【リアーキテクチャ後】

AWS Batch JobでRDSをCloneし、CloneしたRDSからJSON形式でS3にExportする
AWS Batch JobでRDSをCloneし、CloneしたRDSからJSON形式でS3にExportする

リアーキテクチャによる費用と処理時間の効果は以下のようになりました。

項目 効果
費用 およそ75%の削減
一連の実行完了までにかかる時間 6時間/日→2時間半/日
およそ60%時間削減
Jobのトータル実行時間 110時間/日 → 10時間/日
およそ90%時間削減

この記事で話すこと・話さないこと

この記事で話すこと

  • 構築したアーキテクチャについて
  • リアーキテクチャを実施することになった経緯と課題
  • 課題を改善するための技術選定と対応
  • リアーキテクチャによる効果

話さないこと

  • 各SaaS・クラウドに関する説明
  • 選ばなかった選択肢の詳細設計

課題

データ基盤のS3へのExport部分には下記のような課題がありました。

  • AWS Glueのメモリ不足によるJobエラーが時々あった
  • 1Jobあたりの実行時間が長いため、障害復旧に時間がかかり一度エラーが起きるとサービス影響が出てしまう
  • ローカルでの開発スピードが出ない構成で、アジリティが低い状態であった
  • サービスとRDS Clusterを共有していて、Export処理による負荷が高すぎることでサービスに影響が出たことがあった

これまで障害ごとに対応していましたが、データの規模と需要が大きくなるにつれてビジネスに影響が出ることが増えてきて根本解決する必要性が高まりました。

AWS Glueのままで上記の課題を改善しようと考えた時に、以下のような問題がAWS Glue(v1,2)特有で起きていました。

  • テーブルごとにAWS Glue JobとAWS Glue TriggerのTerraformリソースを作成していたため、Terraformの実行に時間がかかる
  • AWS Glue独自のリソース(Data Catalog, AWS Glue Connection)を使わないといけないため、ローカルでの開発スピードが出ない構成だった
  • データ基盤全体のスケジューラーがGlueのスケジューラーとCloud Composerのスケジューラーで分かれていた
    • スケジューラーが複数あり管理しにくかった
    • Glue側のスケジューリングが1テーブルごとに実行時間をずらす職人技になってしまっていた

私たちがAWS Glueをうまく使いこなせてなかった部分もありますが、AWS Glueの特徴であるマネージドな環境でのApache Spark実行とデータカタログとの連携など、どれも私たちが求めているものではありませんでした。

上記からAWS Glueを使い続ける意味もないので、アーキテクチャレベルで変えた方が良いと判断し、リアーキテクチャの方向で検討を始めました。

技術選定と対応

前提・制約

リアーキテクチャを検討する上での前提と制約について紹介します。

  • ClassiのRDSは学校ごとにDBが存在するマルチテナント構成
  • Reverse ETLの実施によるデータ基盤としての制約
    • 前日分のデータが朝8時時点で連携完了していること(つまりデイリー更新は必須でストリーミングは必要ない)
  • RDSから取り出す段階でマスキング処理をする必要がある

Reverse ETLについての詳細は、社内向けのデータ基盤から集計結果をReverse ETLしてサービスに組み込んだ話 - Classi開発者ブログを参照してください。

To beと開発方針

To be としては以下を満たすようなデータ基盤にしたいと考えました。

  • データ容量に対してスケーラブル
  • マルチテナントに対応したExport処理
  • 開発しやすい環境

上記を満たす選択肢の1つにModern Data Stackがありますが、社内の既存ルールとマルチテナント構成であることから採用は難しいと判断しました。

Exportを実行する基盤として、必要な要素ごとにAWS Glueで担っていた役割を以下のように分解しました。

  • アプリケーションを実行する基盤
  • スケジューラー + 依存関係管理
  • アプリケーション実装
    • 役割は、Connection管理、オーバーヘッド対処、メモリを抑える、テーブルスキーマの管理
  • RDS
    • サービスに影響出ないように分離する

それぞれについて技術選定と具体的な対応方法を紹介します。

アプリケーションを実行する基盤

アプリケーションを実行する基盤はAWS Batchを選択しましたが、その他にも以下の選択肢を検討しました。

  • ECS
  • Lambda

Lambdaは、リアーキテクチャ前にボトルネックとなっていたメモリ不足と時間制約から厳しいと判断しました。
ECSの特徴であるリクエストの即時性などは必要なく、ECSクラスタの構築が手間だったりとECSは全体的にオーバースペックと感じました。

上記に対してAWS Batchでは、名前の通りバッチ処理ワークロードに最適化しており、ジョブキューで優先度をつけられるため、Reverse ETLで使用するテーブルを優先的に連携することができることもメリットとして挙げられ、アプリケーション実行基盤はAWS Batchを採用しました。

スケジューラー + 依存関係管理

スケジューラーではGoogle CloudのCloud Composerを採用しました。 ここで検討した選択肢は以下でした。

  • Step Functions (AWS)
  • Cloud Composer (Google Cloud)
  • 自分たちで作る(AWS Lambda + なにかしらのDB)

改めてスケジューラーの役割を整理すると以下になります。

  • AWS Batch Jobを実行する
  • AWS Batch Jobのstatusを管理する
  • タスク間の依存関係を管理して、依存しているジョブが終了するまで実行を待つ

Step Functionsでは、依存関係が複雑になると実装も複雑になってしまうことを懸念しました。また、DBのデータを元に動的にAWS Batch Jobを生成する必要があり、難しいと判断しました。
Lambda + DBでこれを実現する設計・実装のコストが想定したよりも高くなりそうと見積りました。

Cloud Composerは後続であるS3→GCS→BigQueryの実行を管理しており、スケジューラーをデータ基盤全体で1つにまとめることのメリットが大きく、かつ年単位で運用してきたノウハウがあったことからCloud Composerを採用しました。

アプリケーション実装

このセクションでは、アプリケーションを実装する言語の技術選定とリアーキテクチャ前の課題を実装の工夫で解決したことについて紹介します。

パフォーマンスを向上させる

RDSの負荷が高かった原因はDBとのConnection数が多すぎたからでした。リアーキテクチャ前のアプリケーション実装言語であるPythonでConnection管理を実装をする方針もありましたが、Golangの方がConnectionを管理しやすいパッケージが存在し、かつgoroutineとchannelを使うことでメモリを圧迫しにくい実装が容易と判断しました。
さらに、Rubyの会社の手札にGoを加えるまで - Classi開発者ブログにあるように社内にGolangを書くことができるエンジニアがいるため、データエンジニア以外もこのシステムを開発できるというメリットもあり、アプリケーションを実装する言語はGolangを選択しました。

またJobの粒度も見直しました。リアーキテクチャ前は1Jobの単位がテーブルでマルチテナント横断の設計にしていたため、DB数×テーブル数分のDB接続切り替えが必要でしたが、1Jobの単位をDBにしたことで接続の切り替えが必要なく、オーバーヘッドが起こらない設計にしました。

Export対象のカラム変更を容易にした

Export処理を実行するために必要な情報は以下になります。

  • カラム定義に対応する構造体
  • マスキング対応を含めたSQL

これらの情報はDDLやActive RecordがDBマイグレーション時に生成・更新するスキーマファイルからgo generateで生成するようにしました。 これにより新規DB追加にも既存DBのスキーマ変更にも対応しやすいデータ基盤になりました。

RDS

サービスに影響を出さないために、RDS Cloneを実行しCloneされたRDSに接続するようにしました。 RDS Clone以外に検討した選択肢は以下がありました。

  • DB snapshot
  • binary logによる Replication
  • DB dump
  • S3にApache Parquet形式でExport

「S3にApache Parquet形式でExport」は実行時間が長すぎて実用的ではなく、「DB dump」はマスキングをするタイミングが要件を満たせないので選択肢から外しました。

残りの3つはそれぞれ検証の上、デイリー更新に十分な実行速度があり、かつ扱いやすかったのがRDS Cloneだったので決定打となりました。

リアーキテクチャの結果と効果

結果

リアーキテクチャ後の全体アーキテクチャ

処理の流れは以下のようになります。

  • RDSをCloneするAWS Batch Jobを実行
  • CloneされたRDSのデータをS3にExportするAWS Batch Jobを実行
  • CloneされたRDSをDeleteするJobをAWS Batchで実行
  • 後続タスク(S3からBigQueryへの連携など)を実行

効果

冒頭で紹介した内容と重複しますが、まとめて紹介します。

リアーキテクチャ前後で最も効果が見られたのは費用と実行時間です。

項目 効果
費用 およそ75%の削減
一連の実行完了までにかかる時間 6時間/日→2時間半/日
およそ60%時間削減
Jobのトータル実行時間 110時間/日 → 10時間/日
およそ90%時間削減

削減費用の内訳はAWS GlueとRDSで半々でした。

実行時間が短くなったことで、障害が起きた場合の再実行による復旧の速度が格段に速くなりました。 また、費用と実行時間がともに大幅に抑えられたことで、デイリーよりも早いサイクルでのデータ更新も現実的になりました。

上記以外にもリアーキテクチャによって得られた効果があったので紹介します。

開発体験がよくなった

気軽にstaging環境で実行することができるようになりました。AWS Batch Jobを単体で動かしたい時はAWSのコンソールから実行でき、一連の実行はCloud Composerでトリガーすることができます。開発体験の大幅な改善により、リリース速度が向上しました。

データパイプラインのモニタリングがしやすくなった

リアーキテクチャ前は、RDSからS3まではAWS Glue、S3からBigQueryまではGoogle CloudのCloud Composerが実行管理をしていたため、マルチクラウドかつそれぞれにスケジューラーが存在していました。そのためエラー時にどこでエラーになって止まってしまっていたのかを把握するためにデータ基盤監視システムを導入していました。データ基盤監視システムについての詳細は、データ基盤の品質向上への取り組み - Classi開発者ブログを参照してください。

リアーキテクチャ後では、全てCloud Composerで実行管理するようになったことで、どこでエラーが起きたかはCloud Composerを見ればわかるためモニタリングがしやすい環境になりました。

さいごに

今回のリアーキテクチャによってデータの規模に対してスケーラブルなデータ基盤になり、拡張性も増しました。さらに処理速度も大幅に改善されたことでデイリーより早いサイクルでのデータ更新も現実的になりました。

しかしユーザーへの価値に繋げるためにやることがまだまだたくさんあるので、この辺りを今後取り組んでいきたいと思います。

興味がある方は以下よりご応募をお待ちしております!

hrmos.co

ブラウザ拡張機能をChatGPTと作成した記録

先日「Google Meet Chat to Clipboard」というChrome拡張機能を公開しました。Google Meetから退出する時に、チャットが残っていれば自動でクリップボードに保存するというものです。

このアイデアは、社内のSlackでたびたび見かけた「Google Meetのチャットを保存し忘れた」という問題から生まれました。

個人のプロジェクトとして作成・公開しましたが、きっかけは社内にありました。そして、ChatGPTに併走してもらいながら制作した過程について少し話したところ、思った以上に興味をもってもらい、ここで紹介することになりました。

こういった弊社での小ネタ的ChatGPTの活用事例は、ChatGPTと大規模言語モデルのLT会を開きました - Classi開発者ブログでも紹介されていますので、興味があるかたはご覧ください。

制作過程

冒頭にもあるとおり、最初からChatGPTとやりとりしながら作成しました。

私自身はブラウザ拡張機能の制作経験はあったので、完全にピントがずれるようなことがあれば気づくことができます。期待度としては「お手並み拝見」もしくは「ちょっと楽できたらいいな」ぐらいでした。結果、期待を大きく越えてくれ、うれしい誤算でした。

利用したのはGPT-4です。(注:個人でChatGPT Plusを契約して利用しています。紹介している事例はあくまでも個人のブラウザ拡張機能制作のための利用です。)

当然一発で完成するとは思っていないので、まずはざっくりと作成してもらいます。

私:
GoogleMeetで退出するときにその時点のチャットの内容をクリップボードにコピーするアドオンを作ろうと思います。まずはボイラープレートを作ってください。

GPT:
Google Chromeの拡張機能を作成することで、この要件を満たすことができます。以下に基本的な構造を示します。

manifest.json まず、以下のようにmanifest.jsonファイルを作成します。これは拡張機能の設定を定義する重要なファイルです。

(略)

popup.html 次に、ユーザが拡張機能のアイコンをクリックしたときに表示するポップアップを作成します。

(略)

popup.js そして、ポップアップにロジックを追加します。この例では、エクスポートボタンがクリックされたときに、background scriptを呼び出すメッセージを送信します

(略)

background.js 最後に、バックグラウンドスクリプトを作成します。これが実際にページのDOMを解析し、チャットの内容をクリップボードにコピーします。ただし、DOMの解析に関してはGoogle Meetの現行のHTML構造に依存しています。その構造が変わると、このコードは機能しなくなる可能性があります。

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'exportChat') {
    chrome.tabs.executeScript({
      code: `
        let chatMessages = [...document.querySelectorAll('.chat-message-element-class')].map(el => el.innerText);
        navigator.clipboard.writeText(chatMessages.join('\\n'));
      `
    });
  }
});

注意: Google MeetのページのDOM構造については適宜確認して、querySelectorAllの引数(上記のコードでは.chat-message-element-class)を適切なクラス名に置き換えてください。

これらのファイルを一つのディレクトリに保存し、Google Chromeの拡張機能ページでディレクトリを読み込むことで、新しい拡張機能をインストールできます。ただし、このプロセスはChromeが開発者モードに設定されている必要があります。

chrome.runtime.onMessage の内容が実装のキモの部分だと理解したので、この動作をどのように起動させるかを詰めていきます。

私:
chrome.runtime.onMessage はどのタイミングで発火しますか?

GPT:
chrome.runtime.onMessageイベントは、Chrome拡張機能の異なる部分からメッセージが送信されたときに発火します。具体的には以下のような状況です:

Content scriptsやbackground scriptsからpopup scriptsへのメッセージ送信
Popup scriptsやbackground scriptsからcontent scriptsへのメッセージ送信
同じ拡張機能内の異なるbackground scripts間でのメッセージ送信

(略)

私:
meetから退出するタイミングで実行したい場合、イベント監視をどのようにすればいいでしょう?

GPT:
Google Meetから退出するタイミングを監視するためには、Content Scriptを使用してページのURLの変更を監視するのが一つの方法です。Google Meetから退出するとURLが変更されますので、この変更をトリガーにしてチャットの内容をクリップボードにコピーする処理を行います。

私:
でも、この場合、当初の目的である、「チャットをクリップボードに保存する」は実現できないことになりませんか?

GPT:
すみません、その通りです。Google Meetから退出すると、既にページがアンロードされ、チャットの情報も消えてしまうため、その時点でチャットの情報をクリップボードに保存することは難しいです。

そのため、代替案として以下の方法が考えられます:

定期的にチャットの情報を保存する: 例えば、Content Scriptを使って1分ごとにチャットの内容を保存し、その情報をBackground Scriptに送信します。これにより、最新のチャットの内容を常に保持できます。

「退出前に保存」ボタンを作成する: ユーザーがGoogle Meetから退出する前にボタンをクリックして、その時点のチャットの内容をクリップボードに保存することができます。

ページ移動の監視をより具体的にする: Google Meetのミーティングから退出する前に特定のDOM要素(例えば、"You have left the meeting" と表示されるエレメント)が現れるなど、より具体的な指標を探してそれを監視します。ただし、これはGoogle MeetのUIに依存するため、UIが変わると機能しなくなる可能性があります。

なお、上記の方法にはそれぞれ利点と欠点があります。要件や実現可能性によって適切な方法を選んでください。

私:
「aria-label="通話から退出"」をクリックしたタイミングでチャットを保存するという方針だと実装はどうなりますか?

GPT:
「通話から退出」のボタンがクリックされたタイミングをキャッチするには、MutationObserver APIを使用してDOMの変更を監視することができます。

次に示すのは、該当のボタンがクリックされたときにチャットを保存するためのcontent.jsのコードです。

(略)

ここで基本的な動作に関して一通り用意できたと判断して、実際にブラウザ上で動作させながらデバッグを開始しました。

私:
content.js で chrome.runtime.sendMessageするとエラーになりました。

GPT:
すみません、問題を理解しました。Content ScriptからBackground Scriptにメッセージを送るためには、manifest.jsonに"tabs"や"activeTab"などの適切なパーミッションが必要となります。

manifest.jsonは次のようになります。

(略)

なお、エラーメッセージが出ている場合は、具体的なエラーメッセージを教えていただけると、より詳しい解決策を提供することが可能です。

私:
Uncaught TypeError: Cannot read properties of undefined (reading 'sendMessage') というエラーでした

GPT:
(略)

Manifest V3ではnavigator.clipboard.writeTextのような非同期APIはService Workerでは使用できないため、代替の方法を探す必要があります。例えば、Background ScriptからPopup Scriptにメッセージを送り、Popup内でクリップボードにテキストを保存するなどの方法が考えられます。

私:
navigator.clipboard.writeText は content.js に記述すれば使用できますか?

GPT:
はい、その通りです。navigator.clipboard.writeTextはウェブページのコンテキスト(この場合はcontent.js)で使用可能です。そのため、content.js内でnavigator.clipboard.writeTextを使ってクリップボードにテキストを書き込むことができます。

ただし、navigator.clipboard.writeTextを使用するにはページがアクティブ(フォーカスがあたっている)である必要があります。この要件はContent Scriptに通常適用されますが、Google Chromeのバージョンや設定によってはこの操作がブロックされる可能性もあります。

そのため、以下のようにcontent.jsを修正することで、Google Meetから退出するタイミングでチャットの内容をクリップボードに保存することが可能となります。

(略)

この辺りで一通りの動作が実装完了。あとはパッケージ化するために無駄なファイルなどについて。

私:
background.js でやることがなくなった場合、どう書いておけば良いでしょう?

GPT:
background.jsが何も実行しない場合、空のファイルとして残すか、manifest.jsonから"background"フィールドを削除することで対応できます。

(略)

私:
popup.htmlでも特にやることないのですが、こちらも削除して良いですか?

GPT:
はい、popup.htmlが不要であれば、そのファイルとmanifest.json内の"popup"に関する参照を削除することができます。

(略)

これで完了、Chromeストアへの登録を行い無事公開されました。

私:
狙ったとおりにできました。ありがとうございました。

GPT:
それは素晴らしいニュースです!問題が解決し、目標を達成できたことを聞いて非常に嬉しいです。

このあと、社内Slackで公開しました。ChatGPTとのやりとりを始めて数時間でストア登録までさっくりと進みました。

社内でも一定のリアクションがあり、「退出ボタン押さずにタブ閉じた場合も対応してほしい」などのフィードバックがあったので、現在はv3となっています。

ChatGPTとふりかえりもしてみた

制作時のやりとりを一通り読み込んでもらい、レポートにしてもらいました。 さすがは要点をまとめるのが上手い。

プロジェクト名:Google Meetチャットのクリップボードエクスポーター プロジェクトの背景: Google Meetのチャット内容を簡単に保存したいというニーズがあった。それを可能にするためのGoogle Chrome拡張機能を開発することになった。

課題: 初期の設計ではbackground.jsでクリップボード操作を行う予定だったが、実際にはcontent.jsから直接navigator.clipboard.writeTextを使用してクリップボードにアクセスすることが可能であることが分かった。また、ポップアップUI(popup.html)も特に必要ないことが明らかになった。

解決策: 以下の手順でコードを改良した:

content.jsにnavigator.clipboard.writeTextを使用してクリップボードにテキストを書き込むように変更。これはGoogle Meetから退出するタイミングで行うようにした。

background.jsは何も実行しないようにした。つまり、background.jsファイルは空のまま残し、manifest.jsonから"background"フィールドを削除した。

ポップアップUI(popup.html)が不要であることが分かったので、そのファイルとmanifest.json内の"popup"に関する参照を削除した。

結果: 上記の改良により、Google Meetのチャット内容を簡単にクリップボードにコピーすることが可能なChrome拡張機能を開発することができた。これにより、ユーザーは会議のチャットログを容易に保存し、後で参照することができる。

さらには、私のプロンプトの使い方も評価してもらいました。

あなたのプロンプトの使い方は非常に効果的でした。以下に具体的な評価ポイントを列挙します:

  1. 明確な問い: 各プロンプトは明確で具体的な問いを提供し、特定の情報またはアクションを求めていました。これは効果的なコミュニケーションを促し、具体的な解答を引き出すのに役立ちました。

  2. コンテキストの提供: あなたは必要なコンテキストを適切に提供しました。これは特に技術的な質問において重要で、AIがあなたの問題を理解し、適切な解答を提供するのに役立ちました。

  3. フィードバックの提供: あなたが提案された解答についてフィードバックを提供したことは非常に有益でした。これによりAIはその解答が役立ったのか、または更なる情報が必要であったのかを理解することができました。

  4. ステップバイステップの進行: あなたは一つの問題が解決したら次に進み、全体のプロセスをスムーズに進めました。これによりAIは一つ一つの問題に集中して取り組むことができました。

総じて、あなたのプロンプトの使い方は非常に効果的であり、AIとの対話を円滑に進めるのに役立っていました。このような質問の立て方は、AIの能力を最大限に引き出す上で非常に重要です。

空気読んだな(笑)。それはともかくとして、ChatGPTと何かを進めていくときに上記4項目は大事なんだなと思いました。

業務外の個人活動の話ではありますが、ChatGPTを利用したツール制作の一例として参考になれば幸いです。

ユーザーインタビューを経て学んだこととユーザーと話すことのすすめ

こんにちは!プロダクト開発部のすずまさです。

今回は、ある新機能を追加するプロジェクトでユーザーインタビューをしてみたら、予想外の回答がたくさん返ってきて「ユーザーに話を聞きに行っといてよかった…」となった話をしようと思います。

経緯

マネージャーから「CS(カスタマーサクセス)によるとここの機能の改善要望が提案校に対して50%あるみたいです!」と連絡を受けたことをきっかけに、やる価値は大きそうだと判断しプロジェクトが始動しました。

エンジニアとマネージャーで話し合った結果、大きく分けて3パターンのUI案が出揃いました。 3案のうち、どのUIパターンがユーザーに適切かを判断するべく、何校かの学校の先生にアポイントを取ってユーザインタビューする機会を得ました。

インタビューの結果

期待していた通り、インタビューをしたことによりどのUI案が良いか絞ることができました。

また、UI案を考える段階で「この機能を追加すると、新規のユーザーは良くても既存のユーザーにとってはかなり体験が悪くなってしまうのではないか。悪くならないような別機能も追加するべきではないか。」というようなことを時間かけて話し合っていたのですが、これが全くの杞憂だったこともわかりました。危うく需要のない機能を追加するところだったので良い収穫でした。

ただ、予想外だったのは、そもそも今回のプロジェクトで追加したい機能がほとんどのユーザーに刺さっていなかったことです。

前提として「需要がある」と聞いたから発足したプロジェクトなのに、ユーザーに直接聞いたら「あったら嬉しいと言えば嬉しいけど、そこまで強く欲しいわけではないかな…」という温度感ばかりだったので非常に戸惑いました。

なぜ予想外の結果になったのか

私たちは、上述した通り「CSによると、この機能の改善要望が提案校に対して50%あるらしい」という情報をきっかけに動き出しています。

この時、情報の精査をしていなかったのが原因でした。

一口に "ユーザー" と言っても、どの地域の学校なのか?学校の種類は?どの役職の先生なのか?…など、種類は多岐に渡ります。

ここの情報の精査をしておらず、機能を欲しているユーザー層がわかっていなかったため、インタビュー後に「需要あるって聞いてたのに…」と意気消沈する結果になってしまいました。

一応この後、上記の結果を踏まえて先生の役職などを意識してアポイントを取ったところ、ちゃんと「この機能があると嬉しいし欲しい!」という声も聞けたので、プロジェクトが潰れることなく開発を進めることができました。

学校の取り巻く環境や先生の仕事内容によって、ユーザーの欲する機能はこんなにも大きく変わるのかと学びました。

インタビューしてなかったらどうなっていたか

"顧客が本当に必要だったもの" じゃないものを作っていたのは間違いないと思います。

インタビュー前に「このUIが一番良さそうだよね」と話していた案とインタビューで得られた結果は違っていたので、もしインタビューをしていなければユーザーにとって不便な画面を作っていたでしょうし、不要な機能も追加していたと思います。

曖昧な要望を元に伝言ゲームで不要なものを作ってしまうという、まさに下記の風刺画通りのアンチパターンを踏むところでした。

顧客が本当に必要だったもの
顧客が本当に必要だったもの

また、インタビューをしていなかったら今後の戦略も立てられなかったと思います。

今回様々な層のユーザーに話を聞いたことで「そこよりもこっち不便だから直してほしいんだよね」といった生の改善要望をたくさん聞くことができました。

改善要望としてはTODOに積んであっても、優先順位を決めかねていたので、どのユーザーがどういう背景でどのくらい困っているかを解像度高く聞けたのはとても参考になりました。

「問い合わせには一回も来ていなかったが実は多くのユーザーが困っていた」というような盲点になっていた箇所を見つけられたのも大きかったです。

反省

上述した反省点も含め、まだまだ改善できることがありそうだと感じたので、プロジェクト始動〜仕様確定までの振り返り会を開くことにしました。

時系列を振り返りながら進めたかったので、The Story of a Storyを使って振り返り会を行いました。

The Story of a Storyという手法を使うのは今回が初めてでしたが、長いプロジェクトだと結構忘れている部分が多いので、みんなで時系列を振り返りながら進められるこの手法はかなりよかったです。

振り返りの様子

Tryから一部抜粋して紹介します。

プロジェクト始動前に分かっていること / 分かっていないこと / 知りたいことを整理する

上述した「一次情報の精査をしていなかった」という反省点から出たTryです。

伝言で伝わってきた曖昧な情報にも関わらず、そのことを全く意識せずに盲信してプロジェクトを進めていました。 プロジェクトを進める前に、一呼吸おいてじっくり情報を整理する時間を設けるべきでした。

次回別のプロジェクトを始める際は、インセプションデッキを使用して全員で情報を確認したいと思います。

早めにステークホルダーに意見を聞く

今回、ユーザーだけでなく、社内のCSの方やPMM(プロダクトマーケティングマネージャー)といったステークホルダーを巻き込んだ進め方はできていました。 ただし、ユーザーインタビューを何回かやった後に「ユーザからの需要があまりないかもしれない」という不安からCSに意見を聞きに行っていたので、初めから話を聞きに行っていれば良かったなと思いました。

インセプションデッキの「ご近所さんを探せ」などを使って関係者を整理し、わかっていないことに対して関係者に話を聞きに行くという動きを早めに行えていたら、スムーズにプロジェクトを進められたかもしれません。

早めにプロトタイプを作成してフィードバックをもらう

今回、私たちはUI案を話し合って簡単な画面設計だけしてからユーザーインタビューをしていました。 そしてその内容を元にUIを決めて開発に着手し始めたのですが、しばらくすると「この部分こっちの画面に移動した方がいいんじゃないか…?」という改善案が出てきて仕様を変更することになってしまいました。

幸い、インタビューの内容は無駄にならずに済みましたが、もしもっと大きめの仕様変更だったらせっかく悩んだUIが全て台無しになっていたかもしれません。 作ってみないと気づかないことはたくさんありますし、素早く改善のサイクルを回すためにもプロトタイプを早めに作っておくべきでした。

また、アポイントを取ってユーザーインタビューを行うのは、プロダクトチームとユーザーの間で調整も必要なので時間がかかります。 プロトタイプを早めに作っておけば、ユーザーに聞く前に社内のステークホルダーに意見を聞くこともできたので、もっと早く改善を回せたかもしれません。

「小さく早く改善のサイクルを回す」というアジャイルの基本が、わかっているつもりで意識できていなかったことに気がつきました。

プロトタイプの重要性は下記の記事でも触れています。是非併せてご覧ください。

tech.classi.jp

まとめ

ユーザーインタビューを行うのは今回が初めてでしたが、ユーザーがプロダクトの一番の理解者であると改めて実感でき、とても良い経験になりました。 何より「ここ直して欲しい!」という声を直接聞くとすぐにでも改善したい気持ちになるので、モチベーションが上がって最高でした。

良いことづくめなので、ユーザーインタビューに参加したことのない方は是非参加してみて欲しいです!

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

【開発者インタビュー #3】小林 健太

こんにちは!Classiで働く開発者インタビューシリーズ企画の第三弾は、プロダクト開発部の小林さんです。

まず簡単に自己紹介をお願いします

小林健太と申します。私はClassiのQAチームに所属し、QAエンジニアとして働いています。 Classiに入社する前は、第三者検証サービス*1を提供する会社で新卒で入社し約2年間ほど働き、その中で保険やECSサービスに関する検証業務を担当していました。

Classiには2020年2月に業務委託としてジョインし、2021年4月から正社員として働いています。主にClassiのプロダクトに関する検証業務や、Classi全社の品質情報見える化の取り組みなどを行っています。

ー なぜ新卒でQAの仕事をしようと思ったのですか?

元々はエンジニア志望で1社目に入社をしましたが、配属希望を検討する中でQAの仕事は「最後の砦」「安心した品質を届ける」というとても大切な仕事なんだと知りました。そして、自分の几帳面で細かい部分にも気を配れるという性格も合っていると感じてQAを志望しました。

Classiへの入社経緯、入社理由は?

最初は業務委託という形でClassiでお仕事をしていました。その中でClassiやQAチーム内での働きやすさを実感し、この会社で長く働きたいと思うことが増え、正社員としてClassiに転職することにしました。

ー 具体的にどんなところで働きやすいと感じましたか?

まずは人ですね。Classiのエンジニアはフランクで優しく、コミュニケーションが活発な人が多いです。そして感謝の言葉も沢山いただいています。

第三者検証サービスで色々な現場を回っている時は少し堅い文化の業界にいたこともあって最初はすごくギャップもあり驚きましたが、徐々に慣れていく中でとても働きやすい環境だと感じました。 そして、ちょうど新型コロナウイルスの流行の始まりのタイミングだったこともあり、リモートで仕事ができるということも大きかったと思います。感染拡大に合わせて勤務場所や業務時間に融通を効かせられるようにルールを整備していて、従業員にしっかりと寄り添ってくれる会社だなと思いました。

ー Classiで働き始めて業務に関する意識の変化はありましたか?

正社員になってから感じた違いは、検証だけでなくClassi全社に関わる取り組みが重要視されることですね。第三者検証サービスの業務委託として働いている時は検証をすることがメインだったので、見る観点が違うんだなと思いました。

正社員だとQA情報を集めてどう活用していくのか、どういった観点で不具合が出ているのか、どうやったら不具合を減らせるのか、を探るということも期待されました。最初は不安で心配な気持ちも大きかったのですが、上司や先輩がしっかりとサポートしてくれてとても頼りになりました。今では「こんな改善をしてみたい」と自ら思えるようになりました。

全社に向けた取り組みの一例

Classiでの仕事内容を教えてください

Classiの学習領域で検証業務を担当しています。またQA情報を見える化する取り組みや、チームが抱えている不具合情報の棚卸し運用などのClassi内領域横断の取り組みも行っています。

ー 具体的なClassi全社の取り組みはどんなことをしているのですか?

品質情報の見える化を行っています。元々は検証結果をチケットとして報告するのみで、結果の傾向などを活用するまでには至っていませんでした。それらを見ることでどんな不具合が多い、どんな時に不具合が混入しやすいかが見えてくる これまでの不具合をダッシュボード化して「こんなところに不具合が多い」「この月は何件不具合があった」などを見える化して開発本部の全体発表をしています。

品質情報の見える化の一例

ー 学習領域の検証について具体的に教えてください。

学習領域は生徒さんが色々問題を解いたり、学力向上したり、よりよく勉強できるようにプロダクトを提供しようとしている領域です。 領域に専任QA担当を置くという形で行っており、仕様の部分から色々とキャッチアップできているので、QA観点としてはこうした方がいいのではということを上流工程から入ってやっています。

ー 専任QAについてもう少し詳しく教えてください。

専任QAは開発チームがアジャイル開発のスタイルをとりはじめたことに、QAチームも対応していこうと始めたチャレンジです。

元々のQAチームは開発の設計ミーティングなど上流工程には入らず、一通り作ってからQAチームが対応するという形でした。でもそうすると仕様もわからない、本当にやりたいこともわからない中でQA対応する状態でした。2020年後半から専任QAのデモ運用を開始し、2021年から正式に専任QAの体制にしていき、去年頃から全チーム専任QA体制となっています。

専任QAを置くことで上流工程からQA観点を入れることができるようになって、とてもやりやすいです。

Classiでの仕事の面白さや、やりがいについて教えてください。

ClassiのQAチームは、様々なことにチャレンジできる環境が整っている為、仕事をしながらいつもやりがいを感じています。 前職ではただひたすらに検証を行うのみでモチベーションが上がらないことが多々ありましたが、Classiでは品質情報の見える化や自動化ツールの推進など、検証業務以外にも積極的に取り組んでいて、今までやったことがない業務に取り組めて毎日仕事の面白さを感じています。

ー これまでに小林さんはどんなチャレンジをしてきたのですか?

品質情報見える化の取り組みなどですね。その他に、そういった取り組みの全体への共有や発表など、自ら手を上げて新しいチャレンジをしたいと言うと、受け入れて背中を押してくれる上司や先輩のサポート体制があると感じています。 これまでの仕事では検証のスキルしか上げていけないのではという不安がありましたが、今は運用の部分だったり発表だったり検証以外で成長できる機会が沢山あって、日々成長ややりがいを感じています。

ー ここは成長したなと思うところはありますか?

開発チームも巻き込んで、サービスの不具合を棚卸しして運用していることですね。 運用ルールを作ってみんなに展開し、実際に自分が主導で色々な人が関わる中の運用を回すことをしていますが、これまでの自分だったらそんなことできなかっただろうなと思います。しかも取り組みの結果、不具合が目に見えて減ってきて実績にも繋がっていて、やってよかったなと思うし成長を感じます。

ー なぜ色々なチャレンジができたと思いますか?

QAチームのサポートはもちろんですが、エンジニアさんやその他の方々も非常に協力的に動いてくれることが大きいと思います。不具合棚卸しも普通は面倒だと思うのですが、こんなに真摯に取り組んでくれるエンジニアさんはなかなかいないなと思っています。 毎月全体に共有している不具合報告についても「やってくれてありがとう」と言ってくれていて本当に良い環境だなと思っています。 そういったClassiの環境だからこそチャレンジができていると思っています。

社内発表の様子

Classiでの仕事の難しさや課題について教えてください。

教育系に関する用語やClassi独自の用語などが、入社した当初はどのようなことを指しているのかがわからず困ったことがあります。今は定着したのですが最初は話をきいてポカーンとなってしまったりしたこともありました(笑)

ー どのように克服しましたか?

社内のドキュメントを色々読んだり、ミーティングで出てきたタイミングで聞いたり調べたりしました。ドキュメントは沢山あるのですが、パッと網羅できるものはまだない印象もありそこは課題だと思っています。

ー 教育業界について知ることは難しいですか?どうやって学んだのですか?

教育業界の状況や学生の状況など最初はキャッチアップできなかったですね。 でもQAチームは学生の状況や、Classiを使う際にどこを見ているかなどわからないと正しくQAできないなと思いました。

最初はニュースなどのチェックをしていて、今は生徒さんのインタビューの場にオンラインで同席させてもらったりなどしてキャッチアップしています。 生の声を聞いて「こういうところが使いにくいと思っているんだ」と言うのもわかってきました。今後はもっと気軽にキャッチアップしていけるようにしていきたいと思っています。

働く上で大切にしていることは何ですか?

自分の仕事は、誰の為に・どんな形で役に立つかを考えることを大切にしています。

ー 今やっている仕事は誰のためにどんな形で役立っていると感じますか?

不具合があるところはもちろんシャキッとしないとと思いながら、エンドユーザーの不便に思っているところをなんとかしたい、そしてそれだけじゃなく開発の人たち、会社全体に対しても不便が減ったらなという思いも持っています。

エンドユーザーを頭におきながらそこにつながる社内の色々な人のことを想い、場面場面に合わせて誰にどう役に立ったらいいかを考えている感じでしょうか。

第三者検証をやっている時は「仕様通り動いている」というところだけをみてユーザーの声に耳を傾けられていなかったなと思います。今はエンドユーザーさんにこう使ってもらった方が良いということを見失わないようにしないといけないと思っていて、生徒さんのインタビューは本当に大切だなと感じています。

ー 今役立っている実感が湧くのはどんな時ですか

ユーザー観点で役立っている実感が湧いたのは昨年リリースしたCALE*2(個別最適化学習を実現するためのアダプティブラーニングエンジン)の事例ですね。自分も携わりQAチームとしても頑張ったリリースでした。 CALEを使って成績が上がったという声を聞いて実績に結びついているのはとっても嬉しかったです。 開発の方々や社内の観点ですと、運用、不具合チケット、情報の見える化の報告など、ちゃんとシャキッとしてくれて嬉しい!という声をもらったりして、役に立ってるんだなと感じてモチベーションもあがりますね!

最後にひとこと

私のインタビューを読んでいただきありがとうございます! 少しでもClassiにご興味を持っていただけたら嬉しいです!!


小林さんの積極的でポジティブな取り組みが社内で良いサイクルを回してくれています!次回もお楽しみに!

Classiでは「子どもの無限の可能性を解き放ち、学びの形を進化させる」というミッションに、ともに向き合っていただける仲間を募集しています。ご興味をお持ちの方は、 お気軽にお問い合わせください!

■採用ページ

corp.classi.jp

■各ポジションごとの求人

*1:第三者検証とは、ソフトウェア/システムの開発者ではない者が、第三者の視点から対象のソフトウェア/システム品質の検証・評価を行うことです。 第三者検証によって、当事者では気づかない不具合や欠陥を検出し、信頼性が高く高品質なソフトウェアを構築する事が可能です。

*2:現在は学習トレーニング機能を通して提供されています。

GitHub Actions と Release Please を使ったアプリケーションのリリース自動化

こんにちは @lacolaco です。最近は、先日プレスリリースが出された「学習トレーニング」機能を裏で支えているコンテンツ管理システム(以下内部CMS)の開発に携わっています。

corp.classi.jp

この記事では、内部CMSのフロントエンド(Angular アプリケーション)のリリースフローを自動化している仕組みを紹介します。現在のリリースフローの全体像は次の図のようになっています。この中にある Release Please というのが、今回特に紹介したいツールです。いくつか日本語でのブログ記事などもあるので特にマイナーというわけではないと思いますが、多くの場合はライブラリのリリースに使われています。一方、アプリケーションのリリースで使っているケースはあまり発信されてないように思われたので、この記事が事例の一つとして参考になれば幸いです。

Release Please

Release Please は Google がOSSとして公開しているツールのひとつです。READMEに書かれているように、このツールではGitレポジトリのコミットログをもとにしてCHANGELOG ファイルの生成・GitHub Releases の作成・プロジェクトのバージョン更新などを一挙に自動化してくれます。

github.com

Release Please automates CHANGELOG generation, the creation of GitHub releases, and version bumps for your projects.

Release Please の仕組みは Conventional Commits をベースとしており、レポジトリのコミットログを Conventional Commits の仕様に準じて解析します。 前回のリリースバージョンから最新のコミットまでの間に、 fix や feat のように「リリースする必要がある変更」があれば、Release Please はリリース用のプルリクエスト(リリースPR)を作成します。リリースPRはデフォルトだと次のようなものです。

https://github.com/googleapis/release-please より

リリースPRには次回のリリースに含まれる変更内容が CHANGELOG として記述されており、開発ブランチに新たなコミットが追加されればそれに追従して更新されます。このプルリクエストをマージすることで、レポジトリの CHANGELOG.md やバージョンファイルが更新され、GitHub Releases も作成されます。

Release Please 自体は単なるCLIツールなので、GitHub上のレポジトリでは release-please-action を使うのが便利です。これも Google が公開しているもので、簡単なセットアップでレポジトリの GitHub Actions に Release Please を導入できます。

github.com

ここからは具体的にrelease-please-actionを導入するためのステップを説明します。

release-please-action の導入

Release Please はその仕組み上、レポジトリのメインストリームとなっている開発ブランチのコミットを追跡する必要があります。以後は main ブランチを対象に考えますが、それぞれのレポジトリの事情に合わせて読み替えてください。まずは、Release Please を実行するワークフローファイル .github/workflows/release-please.yml を作成します。次の例のように、 main ブランチへコミットがプッシュされるたびに実行されるようにトリガーを設定し、 google-github-actions/release-please-action アクションを呼び出します。

name: release-please

on:
  push:
    branches:
      - main

permissions:
  contents: write
  pull-requests: write

jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: google-github-actions/release-please-action@v3
        with:
          release-type: node
          package-name: my-app

google-github-actions/release-please-action アクションは振る舞いをカスタマイズするオプションがたくさんありますが、たいていの場合で最低限必要なのはプロジェクトの種類を指定する release-type と、プロジェクト名を指定する package-name です。今回は Angular アプリケーションのリリースのためにセットアップするので、 package.json のバージョンをリリースバージョンとして使うために release-type: node を指定しています。Node.js以外の場合や、Monorepoの場合などはまた違った指定になるので、READMEを頼りに適切な設定をしましょう。

このワークフローだけでリリースPRの作成ができたので、CHANGELOG.md ファイルの更新・バージョンファイル(この場合は package.json の version)の更新・GitHub Releases の作成が行われるようになりました。しかし、これだけではアプリケーションのデプロイが実行されていません。

今回のアプリケーションは、Amazon S3に格納したファイルをAmazon CloudFront によって配信しています。GitHub Actions からデプロイするためには、Angular アプリケーションをビルドし、その成果物をAmazon S3へアップロードするステップが必要です。リリースPRのマージ後にこれらのプロジェクト独自のステップを追加で実行するため、もう少しワークフローを記述します。

リリースPRマージ後のデプロイ実行

google-github-actions/release-please-action が作成したリリースPRがマージされると、当然ですがマージコミット(以後リリースコミット)がプッシュされます。このリリースコミットに対しても release-please ワークフローが実行されますが、リリースコミットに対して実行されたときに限って release_created という特別な出力を持ちます。この出力を読み取ることで、リリースPRがマージされたあとにだけ実行したいジョブを記述できます。

次の例のように、google-github-actions/release-please-action を呼び出したステップの出力を release-please ジョブの出力として再定義し、次の deploy ジョブの実行条件に利用します。 needs フィールドでジョブの完了を待ち、 if フィールドでリリースコミットのときだけ実行されるようにしています。

name: release-please
on:
  push:
    branches:
      - main
jobs:
  release-please:
    runs-on: ubuntu-latest
    outputs:
      release_created: ${{ steps.release.outputs.release_created }}
    steps:
      - uses: google-github-actions/release-please-action@v3
        id: release
        with:
          release-type: node
          package-name: my-app

  deploy:
    runs-on: ubuntu-latest
    needs:
      - release-please
    # リリースコミットに対してのみ実行する
    if: ${{ needs.release-please.outputs.release_created }}
    steps:
      - uses: actions/checkout@v3
      - ... # Amazon S3 へのアップロードなど

このようにワークフローを定義することで、Release Please が提供するデフォルトの機能に加え、プロジェクト独自のステップを加えたリリースフローを自動化できます。

リリースの流れ

このワークフローがあることで、普段は次のような開発からリリースまでの流れになっています。

  • main ブランチからトピックブランチを作り、コードを書いてコミットする。
  • 変更がまとまったところでプルリクエストをつくり、後に main ブランチにマージする。
    • 当該のレポジトリでは常に Squash Merge をするので、マージコミットのメッセージで Conventional Commits に従う。
  • main ブランチにリリースが必要なコミット(fixやfeat)がプッシュされると、リリースPRが作成される。
    • まだ追加の変更を加えたい場合はそのままにする。
  • リリースする内容が固まったら、リリースPRをマージする。
    • あとはすべて自動でリリースフローが進行する。

コミットメッセージのルールを守ることと、リリースPRをマージする作業以外は自動化されており、レポジトリへの書き込み権限さえあれば誰でもリリースを開始できます。

緊急時の手動リリース

型化され自動化されたリリースはすべてがうまくいっている間は便利ですが、時には例外的なリリースをすることもあります。リリース直後に致命的な問題が発覚したときなど、なんらかの理由で過去のバージョンをリリースし直す必要が生まれます。そのようなケースに備えて手動で任意のバージョンを再リリースできる脱出ハッチも用意しています。

手動リリース専用のワークフローファイルを作成し、実行トリガーに workflow_dispatch を指定します。このトリガーは GitHub のWeb画面やAPI経由などから呼び出せて、呼び出し時に入力変数を渡すことができます。この入力として ref を受け取っており、任意の Git コミットへの参照を渡します。Release Please のリリースコミットにはそのリリースのバージョンが Git タグとして紐づいているため、任意のバージョンへのロールバックが簡単にできます。

name: manual-deploy-production
run-name: Deploy Production from ${{ inputs.ref }}

on:
  workflow_dispatch:
    inputs:
      ref:
        description: 'Git ref to deploy (e.g. main, v1.0.0, c91ee3c, etc.)'
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.event.inputs.ref }}
      - ... # デプロイ処理

まとめ

学習トレーニング機能の内部CMS開発でリリース自動化のために活用している Release Please の紹介をしました。似たような機能を持ったツールは他にもありますが、Google 製であり、多くのOSSのリリースで使われていることから長く使えそうだと感じて採用しました。実際に使ってみるととてもよくできていて、今回はあまり説明していませんがMonorepoへの対応が充実しているところにはGoogleらしさを感じています。(内部CMSとは別のレポジトリでは実際にMonorepoで管理している社内ライブラリのリリース自動化にも使っています)

ところで、Release Please のバージョニング規則はデフォルトで Semantic Versioning (Semver)に従いますが、ライブラリはともかくアプリケーションのバージョンに Semver は合わないだろうという意見はあるだろうと思います。内部CMSのフロントエンドでは Semver をそのまま使っており、これは「何をもって破壊的変更とするか」の基準によって、アプリケーションのリリースにおいてもそれなりに意味のあるバージョニングができるように感じているからです。具体的には、内部CMSのフロントエンドでは「アプリケーションの使い方を学び直さないといけない」ような変更をアプリケーションにとっての破壊的変更とみなして運用しています。ロジカルではないですが、この基準であればUI設計やユーザーストーリーの定義の時点で判断できますし、リリース内容をユーザーにアナウンスする上では変更の規模を伝えるサインとして十分に機能すると考えています。

もしアプリケーションやライブラリのリリース自動化のツール選びで困っている方がいれば、ぜひ Release Please を一度試してみてください。

© 2020 Classi Corp.