【DevOps】開発の振り返りをアップデートした話

はじめに

こんにちは、プレックスの池川です。

2023年2月に「DevOpsの指標を開発の振り返りに活用しはじめた話」という記事をこのブログに投稿して、はや10ヶ月。10ヶ月の間に振り返りのやり方も変わってきました。

product.plex.co.jp

そこで今回の記事では、振り返りのやり方が10ヶ月前と比べて何がどう変わったのかを紹介したいと思います!

目次

エンジニア組織について

振り返りについて紹介する前に、プレックスのエンジニア組織について簡単に紹介します。というのも、振り返りの方法を見直すきっかけが「エンジニア組織の拡大」だったからです。

プレックスの開発体制は下記の画像のような事業部制を基本としています。エンジニアの人数は10ヶ月前と比べると、正社員が4名→6名インターン1名→3名に増えました。また、コーポレートマーケという社内システムの開発や管理を行う事業部も立ち上げられ、現在エンジニアは各々3つの事業部に所属しています。

開発体制

以前はエンジニア全員での振り返りを行っていましたが、各事業部ごとにサービスがあり取り扱っているドメインも異なることから、私が所属しているプレックスジョブ開発チームでも振り返りを導入するようにしました。

プレックスジョブ開発チームでの振り返り

振り返りMTGについて

プレックスジョブ開発チームではスクラムを採用しており、1スプリントを1週間としています。振り返りもスプリント単位で行っており、スプリント開始のタイミングでMTGを行い前スプリントの内容を振り返っています。 下記が振り返りの内容です。

  1. KPT
    • 事前に振り返りシートを共有し、各メンバーにKPT用のJamboardに今週のアクションや課題を記入してもらう
    • Jamboardに各自作成してもらった今週のアクションや課題をKeepとProblem、Tryに振り分けて発表する
    • 全員のKeepとProblemが分類でき次第、Tryをタスク化する
  2. 開発のパフォーマンス改善
    • Four Keys の指標をもとに前スプリントのパフォーマンスを振り返り

KPTについて

KPTを用いた振り返りは元々エンジニア全体で行なっていましたが、事業部単位で行うようにしました。

Jambordの作成と共有はGASを使って自動化しており、MTGの前日にSlackのエンジニアチャンネルで共有されるようにしています。

実際のMTGで仕様したJambordです。

Jambord

KPTを行うことでポジティブ、ネガティブなことに関わらずメンバー間で情報共有ができるほか、課題の早期発見と解決にもつながるので振り返りにオススメの方法です!

開発のパフォーマンス改善について

次にFour Keysを用いたパフォーマンスの振り返りについて、どのように変わったか紹介します!

Four Keyesの指標は元々集計していたものの、振り返りの指標としてあまり運用できていなかったため、振り返りのMTGで運用できるようにダッシュボードや運用方法の見直しを行いました。

まず、ダッシュボードを Google Looker StudioからRedashに移行しました。Redashは他のプロジェクトでも使用されていて、データの可視化や取り回しがしやすく、関係者が全員アクセスできるといった点で採用しました。

現在のダッシュボードです。

現在(2023年10月時点)のダッシュボード

集計している指標は10ヶ月前と同じデプロイ頻度リードタイムですが、それぞれの指標についてGoogleのパフォーマンスレベルをもとに下記の目標を定めています。

  • デプロイの頻度:スプリント平均で一日一件以上
  • リードタイム:120時間(5日以内)

集計フローはこちらです。

  1. GitHub API から取得したデータを加工して BigQuery に保存するスクリプトを実行
  2. Redash で Google SQL を実行し、必要なデータを取り込み
  3. 取り込んだデータを Redash でグラフ化してダッシュボードに表示

MTGではダッシュボードを確認し、目標が達成できていない項目については、プルリク単位で振り返りを行い原因の特定と改善策を話し合います。 数字をベースに振り返りを行うことで、KPTとは違ったアプローチで現状把握や課題の発見ができるようになりました。

運用する中で効果のあった改善策の一例を紹介します。従来、パッケージのバージョン管理をDependabotで行なっていましたが、プルリクが大量に作られることから対応が後回しとなり、結果、リードタイムが長くなっていました。そこでマイナーバージョンのプルリクをまとめて作成してくれるRenovateに移行することにしました。導入した結果、リードタイムの平均(コミットされてからデプロイされるまでの時間)は268時間(11日)130時間(5.4日)と半分になり、大幅にリードタイムが改善されました。

リードタイム_平均

次に、エンジニア全体で行なっている振り返りについて紹介します!

エンジニア全体での振り返り

エンジニア全体の振り返りは元々週一回行っていましたが、隔週で金曜日に1時間程度、定例MTGを行うように変更しました。

従来、MTGではKPTを行いアクションや課題間を共有していましが、振り返りの方法を見直す中でKPTは廃止して各々が開発する中で得た学びをシェアする時間を設けました。

メンバーはそれぞれあらかじめNotionに共有したい学びをまとめておきMTGで順番に発表していきます。

振り返りシート(一部抜粋)

開発する中で得た学び開発中に遭遇したバグや、新しい技術を試してみた話、便利なツールの紹介など興味深いトピックが多く、知識の共有だけでなくエンジニア全体の結束を高めることにも繋がっていると感じています。

さいごに

今回の記事では、プレックスのエンジニア全体とプレックスジョブ開発チームでの「振り返り」の取り組みに関して紹介させていただきました。

前回の記事から10ヶ月が経ち、振り返りの継続実施はできるようになりましたが、今後も組織や事業の成長に合わせて随時やり方を見直し柔軟に取り組んでいくことが必要だと考えています。

振り返りの取り組みやFour Keysをはじめとした指標の活用の変化に関しても引き続きアップデートがあれば、本ブログで紹介していきます。

今回紹介したような開発の効率化や振り返りに少しでも興味を持っていただけた方はぜひ一緒に取り組みましょう!

dev.plex.co.jp

プレックスのエンジニア組織初となるオフライン勉強会を開催しました!

匠技研工業×プレックス ミートアップ

こんにちは、株式会社プレックスの石塚です。 2023/08/30(水)にプレックスのエンジニア組織では初めてとなるオフライン勉強会を開催したので、今回のブログでは勉強会の様子を少しでもお伝えできればと思います。

plex.connpass.com

オフライン勉強会を開催した背景

勉強会のレポートの前に、なぜオフラインの勉強会を開催したのかについても簡単に触れておきます。理由は大きく分けて2つあります。

1つ目は個人にとってリアルな繋がりを広げることができる場所を作っていきたいということです。コロナの影響もあり、IT業界ではリモートワークがより一般的になってきました。これは不可逆な変化であり、インターネットの進化によってますます加速していく流れであると感じています。しかしながらそうしてリモートワークがより一般的となった社会では、リアルな繋がりを作れる場所の重要性はますます高まってくるはずです。事実、今年の始めくらいからはエンジニア界隈でもオフラインイベントが目立つようになってきました。

2つ目が社内のエンジニアが登壇したり、外部のエンジニアと交流することによって、成長機会として活用してもらいたいということです。どうしても普段と同じ人と、同じ仕事を続けていると、視野が狭くなってしまったり、偶然による新たな発見が得づらくなってしまいます。意図しない出会いや外部からのフラットな目線を入れることで、普段の仕事の中でも工夫や違った視点からの提案が生まれることを期待しています。さらにはこういった社内のイベントでの登壇をきっかけに、大きなカンファレンスやイベントで登壇する方が出てきてくれたら嬉しいなと思っています。

当日の様子

話が少し脱線してしまいましたが、本題の勉強会の様子に入っていきます。勉強会は共同開催している匠技研工業さんのオフィスで実施しました。今年の6月に移転されたばかりということもあって、綺麗なオフィスで、弊社のメンバーもテンションが上がっていました。

イベントスペースは執務室に机と椅子を用意して設置。勉強会のために大きめのモニターも購入していただきました。ありがたい...🙏

準備中の様子

タイムテーブルは

  1. テーブルごとに分かれて乾杯、自己紹介
  2. プレックス 種井による発表
  3. 匠技研工業 井坂による発表
  4. 懇親会

という流れでした。

プレックス 種井による発表

弊社エンジニアの種井からは「Plex Jobと技術負債のマネジメント」というテーマの発表がありました。 Plex Jobの事業やプロダクトの特性を踏まえて、どうやって技術的負債と向き合ってきたかが、0→1の立ち上げフェーズと1→10のグロースフェーズでどのように変わっていったかというお話でした。

プレックス 種井による発表

手前味噌ではありますが、プレックスでは技術的負債を認知→蓄積→返済していくというプロセスを整理しており、仕組みとして技術的負債を返済しているという点が印象的でした。

技術的負債に対する取り組みとサイクル

匠技研工業 井坂による発表

匠技研工業の井坂による発表は「高速なMVP検証を支えた技術的負債とその解消」でした。

匠技研工業 井坂による発表

シードからシリーズAとMVP検証を重ねる中で、検証スピードを高速化するために匠技研工業が意図的に抱えていった技術的負債が、技術的負債の4分類の中でどれに属するかを参加者の皆さんに考えてもらって手を挙げてもらうなど、インタラクティブな発表でした。

技術的負債の分類

中にはパブリックな場では公開できないような内容もあり、オフラインイベントならのライブ感がありました。

懇親会

懇親会は運営が事前に用意していた発表に関連したテーマをベースに、5, 6人でテーブルごとに分かれたグループでディスカッションをする形式でした。テーマは「技術的負債に取り組む上でどうやってチームの理解を得ているか」であったり、「今までに経験した大規模なリプレースの思い出」などがありました。

プレックスや匠技研工業は比較的若い会社、プロダクトでしたが、参加者の中には10年近く運用を続けているようなプロダクトに携わっている方もいたり、組織が成長して大規模化して後のフェーズの方もいたりと、また違った悩みや課題に触れることができたのも新鮮でした。

次回のイベントについて

プレックスでは匠技研工業さんとのイベントを、3ヶ月に1回程度を目安に継続的に実施していく予定です。まだまだ試行錯誤の段階なので、テーマや形式は変わっていくかと思いますが、温かい目で見ていただいて、ご参加いただけると嬉しいです。

また次回イベントの情報の通知も来るようになるので、connpassページからぜひメンバー登録をお願いします。

plex.connpass.com

プレックスジョブが目指す世界と課題

はじめまして、株式会社プレックスの平井と申します。

私は、一般的にプロダクトマネージャーとWebマーケティングと言われる役割を担当しており、 インフラ産業に従事するエッセンシャルワーカー向けの転職・採用プラットフォーム「プレックスジョブ」の改善や集客に関わる意思決定や施策の実行を担当しております。

今回の記事は、プレックス及びプレックスジョブにご興味を持っていただいた方に、企業とプロダクトが目指している世界、それに対しての現状や課題、弊社が候補者様に提供できるものについて少しでも理解が深まる記事になればと思っております。

簡単な自己紹介

主題をお話する前に簡単に私の経歴をお伝えすると、新卒でナイル株式会社というデジタルマーケティングを主軸にしたコンサル・事業を行っている会社に入社し、SEOを中心としたデジタルマーケティングコンサルティングを担当した後、プレックスに5人目のメンバーとして入社しました。

プレックスに入社後は、下記のような役割を担当してきました。

  • 求職者様の集客するためのメディアの立ち上げ
  • 自社採用・人事機能の立ち上げ
  • 採用広報機能の立ち上げ
  • 人材紹介事業部の業務改善
  • 新規領域の人材紹介事業の選定

個人としてはあまり役割にこだわりがあるタイプではなく、目的に対して自分がバリューを発揮できるのであればOKというタイプのため、このような経歴を歩むことになりました。

プレックスが5人→150人くらいの企業になるまでの様々な酸いも甘いもある経験を積み重ね、今はプレックスジョブで目指す世界の実現に向けて、開発、セールス、カスタマーサクセス、他事業などとの連携を取りつつ、プロダクトや集客の改善に取り組んでいます。

プライベートでは最近はとうとうゲーミングPCを購入し、valorant、信長の野望civilizationの練習に勤しんでおります。

プレックスという企業がどういう世界を目指しているか

まずプロダクトが目指す世界のお話の前に、プロダクトの上段の概念にある企業がどういう世界を目指しているのか、という点についてお伝えできればと思います。

ざっくり「自分たちが解決できると仮説立てられる中で、大きな課題を手法や領域に縛られず解決していこう」という、いわゆるマーケットの課題から事業を始めるタイプの企業だと大枠で捉えていただければ良いかと思います。

具体的に、現在のプレックスは「日本を動かす仕組みを作る」というミッションを掲げており、下記のような事業を展開しています。

  • 物流領域の人材紹介事業
  • エネルギー領域の人材紹介事業
  • 製造領域の人材紹介事業
  • エッセンシャルワーカーのダイレクトリクルーティングサービス(プレックスジョブ)
  • M&A仲介事業
  • SaaS事業

ミッションを少し具体化して解釈すると、「日本を動かすくらいのインパクトがある産業の課題に対して、事業という仕組みを使ってより良くしていく」といった意味があり、現に今取り組んでいる事業も、領域やビジネスモデルに縛られず、自分たちが解決すべき、解決できると信じる領域に対して始めた事業になっています。

今後企業の成長次第ではミッションの変更もあるかとは思いますが、プレックスが現時点で見えている将来としては、多種多様の産業や課題に対して、ビジネスで解決していくことを続けていこうと考えている企業で、今後も新たな事業・プロダクト・サービスを生み続けていこうとしています。

プレックスという企業全体にご興味がある方は採用ピッチ資料をご覧ください。

プレックスジョブの目指す世界と現在地

「日本を動かすくらいのインパクトがある産業の課題に対して、事業という仕組みを使ってより良くしていく」企業の中で、プレックスジョブが目指す世界は「エッセンシャルワーカーNo.1の採用プラットフォーム」になります。

具体的に「エッセンシャルワーカー」とは、物流、エネルギー、製造など人の生活の基盤となる産業で働いている方々を指しています。エッセンシャルワーカーを採用するすべての企業様、すべてのエッセンシャルワーカー様にとって「プレックスジョブを使えば、早く、効率的に、双方の希望を満たした採用・転職ができる」という世界を目指しています。

エッセンシャルワーカーは日本全国で3,100万人、エッセンシャルワーカーが関わる産業の市場規模は100兆円を超えると言われており、まさしく日本を動かしている方々、産業に対して、採用・転職の課題を解決するプロダクトになるべく、プレックスジョブが存在しています。

エッセンシャルワーカーの市場規模

2023年7月時点、弊社では累計約38万人の求職者様からの登録がありますが、エッセンシャルワーカー全体で見ると約1%、契約事業所数も12,000と、市場の大きさを鑑みるとNo.1採用プラットフォームへの道のりとしてはまだスタート地点とも言える状態にあり、多くの課題、成長余地が残されています。

ただ、エッセンシャルワーカーの採用市場の状態としては、人口減少や高齢化で採用が難しくなった流れに応じて、変化が起き始めている時期と言えます。

恐らく、この記事を読まれている方はIT系企業などに属していたり、営業/マーケティング/エンジニアといった職種の方かと思いますが、そういった方々が転職する際は人材紹介、ダイレクトリクルーティング、リファラル、SNSをメインの転職手法として活用し、ハローワークや求人媒体で転職先を探すといったことは少ないかと思います。

IT系職種を採用する企業側もハローワークや求人媒体だけでは採用が難しく、様々な採用チャネルを駆使してなんとか採用計画を達成するような状況です。

しかし、物流のドライバー採用では、ハローワークでの求人募集、紙/Webの求人広告が未だに多く活用されており、人材紹介やダイレクトリクルーティングは聞いたこともないという企業様や求職者様の方が割合としては多いです。

歴史を遡ると営業やエンジニアなどの職種も、日本の労働人口が多く、採用市場の企業側のパワーが強かった時代は、ハローワークや求人媒体がとても強力な採用手法だったと思いますし、ダイレクトリクルーティングという概念も存在していませんでした。

しかし、労働人口の減少、採用競争の過激化、テクノロジーの進歩に伴い、企業と働き手のパワーバランスや情報格差に変化が起き、採用手法が多様化したという歴史があると捉えています。そして、このような採用手法の変化が、エッセンシャルワーカーが働く領域にも今後起こると考えています。

人口減少と社員の平均年齢の高齢化が重なり、従来の採用手法では採用が足りず、企業が働き手に対して能動的に採用活動を仕掛けていくトレンドが、ドライバーや電気主任技術者、製造職などの領域に広がっていく中で、その先頭をプレックスジョブが担える可能性があります。

数多の進歩により物質的に豊かになった現代において、人々の当たり前になっている活動・行動・トレンドを変えることができる領域はそう多くはないと個人的には感じていて、その大きな変化を動かす中に身を投じる経験は一定のやりがいや楽しみがあるのではないかと思います。

そして、エッセンシャルワーカーのNo.1採用プラットフォームというだけでも、とても先の長い話ではありますが、日本全体で見たマクロトレンドも踏まえると、プレックスジョブの「エッセンシャルワーカーのNo.1採用プラットフォーム」というミッションも更新される可能性が高いと見立てています。

現在のエッセンシャルワーカー、インフラ領域は「人が足りないから採用をしたい」という文脈が強いですが、人口減少や技術進歩が進むに連れて、

  • 経験者が採用できないから、未経験でも活躍できる教育環境を整えなければいけない
  • 採用しても退職を減らさなければそもそもの労働人口が足りない
  • 退職が抑えられても労働人口が足りないから業務を効率化が必要

といったように、大きなトレンドに対してマーケットの課題認識や緊急度が変化していくことが想定されます。

そのようなマーケットの変化に合わせて、採用というプラットフォームから定着、働き手のパフォーマンス向上、業務効率化など、プロダクトが提供する価値の範囲、大きさが広がっていく可能性を秘めていると考えています。

また、採用というアプローチから入ることで働き手と企業との関係性、データなどを集めることができますので、採用以外の事業に取り組む際に顧客の課題の解像度を上げる上でも、より早く、濃いインプットや示唆を得ることができます。

今後、様々な可能性の広がりを秘めたエッセンシャルワーカー、インフラ領域というマーケットでは1つずつ大きな山を乗り越えていかなければならないため、まず最初に目指す世界として、エッセンシャルワーカーの採用No.1プラットフォームになることが現時点でのプロダクトのミッションとなっております。

プロダクトミッション実現に向けた直近の登り方

プロダクトのミッションに向けて、そこに到達するための道筋は無限にありますが、その中から現時点で最も速く到達できると信じられる仮説が、直近の事業戦略、プロダクト戦略となります。

現時点で、という但し書きをつけているのは、プロダクトのフェーズや組織のフェーズ的に、直近1ヶ月で得たインプットや、新たに入ったメンバーの活躍により、より良い仮説が出てくることが多いためです。

直近取り組むべき仮説としては、「企業様の採用活動において、サービス利用のハードルを下げる」というポイントをテーマとしています。

「採用No.1プラットフォーム」は企業様と求職者様という2者のユーザーのニーズが満たされることで達成できるミッションになっています。そのため、両方のニーズを同時に満たすことを考えると、逆にどちらも中途半端になってしまうリスクがあります。

そこで2023年の第1四半期では求職者様に向けた集客やプロダクトの改善をメインに進めて一定の進捗があったため、2023年の第2四半期では企業様に向けたプロダクトの改善を進めていこうという方針になっています。

「サービス利用のハードルを下げる」という方針に決めた理由としては、大前提としてプロダクトやサービスモデルに対して馴染みがない企業様がとても多いからです。

そのため、少しでも分からないことや既存のオペレーションよりも大変だと感じると、離脱が起こってしまう可能性が高く、いち早く新しいサービスを取り入れていただいている企業様が離れてしまうことは今後のマーケットへの影響を踏まえても避けておきたいところです。

そこで、まずはダイレクトリクルーティングというプロダクトに馴染みを持っていただき、今の採用活動のオペレーションよりも早く、効率的に採用ができる体験をしていただくことを1歩目として設定し、直近の方針としています。

具体的には、求職者様とのメッセージのやり取りを行うメッセージ機能の改善、選考の進捗を確認・記録できる選考管理画面の使いやすさの改善、採用活動のオペレーションを効率化できる機能の開発などを中心に取り組んでいきます。

一定の機能開発が進んだ後は、ドライバーなど現在弊社がメインで支援している職種以外のエッセンシャルワーカーへの支援を広げ、エッセンシャルワーカー内での人員配置の最適化を行っていくなど、プロダクトが与える影響範囲の拡張を進めていこうと考えています。

プロダクトミッション実現に向けての課題

大きく分けて3つの課題感があります。 1つ目は、「企業様、求職者様からダイレクトリクルーティングという手法及びプロダクトの理解を得る」ということです。

顧客にとって新規性の高いサービスは、なんとなく怪しく見えたり、費用対効果などが読みづらかったりと導入の難易度が高いです。そのため、セールスやマーケティング活動を通じてプロダクトの価値を理解してもらい、実際に使って価値を享受する経験を積んでもらうことが重要になります。

その中でプロダクトチームとして取り組むポイントは、「軽く触ってみたけどよくわからないな」「使ってみたけど他の採用チャネルより大変だな」などといった、新しいプロダクトに馴染む1歩目のハードルを乗り越えることがとても重要になると考えています。

2つ目は「産業、職種ごとの特徴や傾向を捉えてプロダクトを最適化する」ということです。

具体的には、ドライバーを採用したい企業様と電気主任技術者を採用したい企業様では知りたい求職者様の情報が異なっています。同じように求職者様側にとっても、企業様について知りたい情報がドライバーと電気主任技術者では異なります。

そのようなユーザーのセグメントごとに違うニーズを、プロダクトとしてどのように捉え、機能や実装を行っていくかという点は、弊社のプロダクトが成長するに連れて出てくる課題になると考えています。

また、トラックドライバーから製造オペレーターへなど、異職種、異産業への転職というのも増えてくることが予測されます。その際の企業様・求職者様データベースの取り扱いをどうするか、管理画面をどうするかなど、将来的な広がりを踏まえつつ、短期的に必要なもの、長期短期のバランスを捉えた開発が必要になります。

3つ目は「顧客が想定していなかった選択肢の提案を行えるようになる」という点です。

企業様、求職者様は同じ産業や職種への知見がとても深いです。それは、基本的に同産業同職種への転職がスタンダードな世界で、ドライバーはドライバーへ転職をするというのが一般的なためです。

そのため、企業様もドライバーであればどういう物を運んでいたら自社にフィットするかなどは、過去の経験から判断することができます。ただし、ドライバー以外の仕事をやっていた人が自社で活躍できるかという点については、実際採用や入社の経験が多くなく、判断が難しいケースがあります。

そこで、プレックスジョブが得てきたデータを活かして「こういう職種の人はドライバーで活躍する傾向がある」などの提案を行えると、企業様に対して今までの採用手法では出会えなかった人に出会える可能性があります。また、求職者様も同じく、自分で探していたら出会えなかった企業に出会う機会をプレックスジョブが提供できるようになるとミッション達成により近づくのではないかと思っています。

プレックスジョブプロダクトチームで働く方に弊社が提供できるもの

弊社及びプロダクトとしての現在、想定している未来についてお話してきましたが、個々人のキャリアや人生において何を提供できるかについて、あくまで私の主観的な話にはなりますがお伝えできればと思います。※報酬面などは他で記載があるので、どちらかというと非金銭報酬や定性的なお話をメインにさせていただきます。

マーケットに新しい常識を作る経験

これは前述した通り、ダイレクトリクルーティングが浸透していないマーケットに普及していくという文脈で、影響や変化の大きさ自体にやりがいを感じる方にとっては面白い経験になると思っています。

マーケットインでビジネスを構想し、エンジニアリングで実現する組織

せっかく開発したプロダクトや機能も使われないと事業を継続して運営していくことも、改善を回すことも、価値を生み出した実感も感じづらいものです。そうならないようにセールスやマーケティング、プロダクトマネージャーがビジネスとしてニーズがあるかどうかを検証していきます。

ビジネスサイドとエンジニアリングサイドという分断はプレックスにはなく、「顧客への価値提供とビジネスとして成り立つか」を目的とした1つの組織であり、その中の役割分担があるという考え方の組織になっているため、優劣や貴賤はなく、相互に尊重しあい、目的達成のために向かう組織になっていると感じています。

ビジネスモデル、マーケットごとの違いを知ることで、自身の知見に深みを出せる

ビジネスモデルやマーケットが異なるものの、セールス、マーケティング、開発などの職種は共通して存在しています。例えば、同じ「セールス」という職種にしても、どういう営業リストを作るか、どういう営業トークスクリプトを作るかなどは、顧客の属性、事業・プロダクトの状態などによって大きく異なります。

そのような各事業での成功施策、失敗施策の背景や施策の具体をインプットし抽象化すると、成果への関連性が高い共通要素が浮かび上がってきます。そして、共通要素を自分が担当する事業やプロダクトに当てはめて改善と検証を繰り返すことで、徐々に成功確率が高まっていき、自身が担当する役割や業務における再現性を持てるようになり、新たな事業やプロダクトであっても、初速から精度高く施策を当てられるようになってきます。

プレックスでは複数事業経営のスタイルのため、役割や業務の学びのサイクルを速く、大量にインプットすることができるため、知的好奇心や成長実感を得やすい環境になっているかと思います。

最後までお読みいただき、ご興味を持っていただけた方に

企業としてもプロダクトとしてもまだまだ発展途上ではありますが、目指している世界や環境についてご興味をお持ちいただけた方はぜひ一度PLEXメンバーとお話の機会をいただけたら嬉しいです。

複数の事業や職種での募集がありますので、プレックスジョブ以外での可能性も含めてお話ができると思いますので、気軽に下記からご応募ください!

エンジニア職向けカジュアル面談フォーム

docs.google.com

ビジネス職求人情報

plex.co.jp

Notionのページプロパティを ChatGPT APIが作った文書で更新する!

はじめに

こんにちは小松です。

プレックスでは日々のタスク管理ツールとしてNotionを利用しています。 美しいUI、その印象とは裏腹に機能は骨太、ここ1〜2年でユースケースも劇的に増えて「こんな使い方もありなのか〜」と思う日々が続いております。 (そう、私はNotionが大好きです)

そして世は生成AI時代ですがNotionでも今年の春にAI機能がリリースされました。 めっちゃ便利ですよね。テキストのちょっとした言い換えであったり、フォーマットの修正であったり、当然翻訳もしてくれるし。 本当に良いところばかりです。ただ少しお値段が、可愛くないんですよね。ChatGPTのAPIと比べちゃうと。もちろん利用頻度によっても変わってくるので、参考までに、記事の最後に今回のユースケースでの費用比較を掲載しています。

全社的に使うのであれば良いのですが、やりたかったのは1プロパティに本文の要約を入れてほしいだけだったので今回はChatGPTに働いてもらうことにしました。

今回やりたいこと

今回はNotionのAIプロパティ編集機能("AIによる要約","AI:重要情報","AI:カスタム自動入力"みたいなやつ)をChatGPT APIを利用して実現したいと思います。

現在プレックスジョブのプロダクト開発では毎月60〜70程度のタスクが起票〜クローズされています。プロダクト開発を運用する上で、例えば今月何をやったか一覧で振り返りたいな、という時があり一覧表示にしたりするのですがタイトルを一覧表示してもそれが何を成したタスクなのかって思い出せる事もあれば思い出せない事もかなりあります。そこで私たちはそのタスクで何を成したかを要約するプロパティがほしいなと思いました。ただし、運用上今まで書いていなかった項目を新しく各タスクの担当者が書いていくというのも難しい話なので、ここでAIに要約してもらおうとした次第です。

なので今回はプレックスジョブの開発タスクを管理するデータベースにタスクページの'目的/ゴール'プロパティを本文から抽出して登録する。

これを今回のゴールとします。

これをDIYするぞ!

今回登場する予定の人たち

  • Notion API
    • 本文の提供
    • プロパティの更新
  • ChatGPT API
    • Notionの本文を要約したテキストを生成
  • node.js(+TypeScript)の実行環境(今回作るスクリプト)(何でも良い)
    • Notionから更新対象のページを取得する
    • Notionから本文を取得してChatGPTへ渡す
    • ChatGPTから返ってきた値をNotionのプロパティに戻す

今回触れないこと

  • Notion API tokenの取得、インテグレーションの設定
  • Open AI tokenの取得
  • AIプロンプトの精度
  • スクリプト実行環境の詳細、トリガー

のあたりは今回触れません。特にプロンプト周りで書いてある事は参考情報で、これで良い応答が得られることを約束するものではありません。

実装!

今回はNotion周りのライブラリが充実していたのでnode.js(TypeScript)でスクリプトを書きます。

詳細はステップ毎に説明しますが、今回は以下のようなステップでNotionのページプロパティを更新します。

①Notionから更新対象のページリスト(ページID)を取得する
②本文を取得してマークダウンに変換する
③マークダウンに変換した本文をChatGPTに投げ込む
④ ③で出力した情報をNotion構造に変換してNotionプロパティを更新する

事前にページIDが明らかになっており、単ページを更新するだけであれば②〜④のステップで実現できます。

①Notionから更新対象のページリスト(ページID)を取得する

実用を考えると、まずは更新対象のレコード(ページ)を抽出する必要はあります。コード見本にあるfilterオブジェクトに関しては公式のこのあたりに説明がありますが、Notionの画面から設定する要領とほとんど一緒になります。

  • 前提
    • 今回NotionAPIへのアクセスは全てNotionSDKを使う
    • 対象のレコード(ページ)は'目的/ゴール'プロパティが空欄のものとする
  • ここでやること
    • 対象のデータベースから今回更新対象のページリスト(ページID)を取得する
import { Client } from "@notionhq/client";
import { QueryDatabaseResponse } from "@notionhq/client/build/src/api-endpoints";

// 各環境に合わせて設定してください
const notionToken = "xxxxxxxxxxxx";
const databaseId = "xxxxxxxxxxxx";

const notion = new Client({
  auth: notionToken,
});

const pages = async (): Promise<QueryDatabaseResponse> => {
  const response = await notion.databases.query({
    database_id: databaseId,
    // 更新対象のプロパティが未記入のものが対象
    filter: {
      property: "目的/ゴール", 
      rich_text: {
        is_empty: true,
      },
    },
  });

  return response;
};

この後の処理はここで取得したpagesに対して1ページずつ処理を行っていくイメージになります。

②本文を取得してマークダウンに変換する

Notion APIを使うとまず驚くのですが、notionのpageオブジェクトはかなり複雑な構造体になっています。notionのリッチなテキストエディタを実装する上で必要な情報なのでしょうが、今回の用途では必要のない要素も多いので本ステップでは本文をマークダウン形式へ変換してChatGPTのトークンを節約しつつ理解のし易い形に変換します。

  • 前提
    • 本文のマークダウンへの変換はライブラリNotion-to-MDを使う
  • ここでやること
    • ①で取得したページリストの本文を取得する
    • 本文をマークダウン形式のテキストに変換する
import { Client } from "@notionhq/client";
import { NotionToMarkdown } from "notion-to-md";

// 各環境に合わせて設定してください
const notionToken = "xxxxxxxxxxxx";

const notion = new Client({
  auth: notionToken,
});

const pageId: string = "xxxxxxxxxxxx"; // ステップ①で取得したpageId

const n2m = new NotionToMarkdown({ notionClient: notion });

const mdblocks = await n2m.pageToMarkdown(pageId);
const mdString = n2m.toMarkdownString(mdblocks);
const mdPageBody = mdString.parent;

③マークダウンに変換した本文をChatGPTに投げ込む

これでようやくChatGPTが食べやすい文書になったのでこれを渡します。

  • 前提
  • ここでやること
    • ChatGPTにテキストを要約してもらう
import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
const gptModel = "gpt-3.5-turbo"; // or "gpt-4"

const mdPageBody: string = 'xxx'; // ステップ②で作成したマークダウン形式の本文、実際の運用では1000字切り捨てとしている

const openAiApiKey = "xxxxxxxxx";
const openAiOrganization = "xxxxxxxxx";

const openAiConfiguration = new Configuration({
  apiKey: openAiApiKey,
  organization: openAiOrganization,
});

const openai = new OpenAIApi(openAiConfiguration);

const messages: ChatCompletionRequestMessage[] = [
  {
    role: "system", // 全ページの処理に共通するプロンプト
    content:
      "以下のテキストで目指しているゴール、目的を抽出して1〜6個程度の箇条書きで提出してください。箇条書きの始まりは'- 'から始まるマークダウンの形式で出力してください。",
  },
  {
    role: "user",
    content: mdPageBody.slice(0, 1000), // 1000字まで渡す
  },
];

const chat_completion = await openai.createChatCompletion({
  model: gptModel,
  messages: messages,
  temperature: 0.5, // お好みで
  max_tokens: 2000,  // 運用に合わせて設定してください
});

const contents: string = `${
  chat_completion.data.choices[0].message?.content // 生成された本文
} \n\n- Created by ${chat_completion.data.model} Usage: ${JSON.stringify(
  chat_completion.data.usage // この処理で利用したトークン数が取得できるので残している
)}`;

プロンプトはこの記事では重点を置きませんが、とりあえず'マークダウンぽいリストで返して'ってお願いするとGPT3.5でも4でも返却フォーマットをコントロールしやすかったです。

プロンプト実行時に設定できるパラメータは以下のページに説明があります。temperatureは数字が大きい(~2)ほど揺れの大きい回答になるそうですが、今回はあまり調整していません。

platform.openai.com

④ ③で出力した情報をNotion構造に変換してNotionプロパティを更新する

まず、今回データを投入したいプロパティですが、Notionの画面上はテキスト(Text)で作成されています。これはNotion APIの世界ではrich_textタイプになるようです。

rich_textについてはNotionの公式に説明はあります。上手く使えれば書式をいじったり色々遊べるのですが、なかなか仕様も複雑なので今回は極力シンプルな実装としています。

  • ここでやること
    • ChatGPTから取得したテキストをrech_textのブロックに入れる
    • Notionプロパティを更新する
import { Client } from "@notionhq/client";
// 各環境に合わせて設定してください
const notionToken = "xxxxxxxxxxxx";


const contents: string = 'xxxxxx'; //前ステップでChatGPTが生成した文字列
const pageId: string = "xxxxxxxxxxxx"; // ステップ①で取得したpageId

const richText: any = []; // notionに返すrich_text構造体 (any...)

// プロパティの内容を作成
richText.push({
  type: "text",
  text: {
    content: contents,
    link: null,
  },
  plain_text: contents,
  href: null,
});

const notion = new Client({
  auth: notionToken,
});

// Notionのページプロパティを更新
await notion.pages.update({
  page_id: pageId,
  properties: {
    "目的/ゴール": {
      rich_text: richText,
    },
  },
});

これでNotionのプロパティが更新されているはずです。お疲れ様でした!

こうなった!

ちなみに私のプロンプト力だとGPT-4の力を持ってしてもNotionAI程気の利いた要約を作れませんでした・・・。NotionAIではそこまでプロンプト作成に気を遣わずにイケちゃったので、Notion側でどのようなベースプロンプトが実装されているのかはかなり気になります。

おわりに

今回、NotionAIの費用にびびってなんちゃってNotionAIをChatGPTを利用してDIYしてみましたが、結果的に本家NotionAIの便利さを再確認することになりました。やはり組み込みで機能が提供されているという点での完成度の高さに敵いません。ただし、この後付録としてつけますが、利用用途によってはDIYした方が遥かに安価に運用できる可能性が高いので、まずはAIの運用を試してみるというのは全然ありじゃないかなーと思いました。

また、プレックスでは一緒に働いて頂けるエンジニア、デザイナーをまだまだ募集しております!気になるところがあれば、こちらに採用情報まとまっておりますので、ご覧いただければと思います。

以上、長くなってしまいましたがここまでお付き合い頂きありがとうございました!

付録:NotionAIとChatGPT APIの費用比較

以下全ての利用料は2023年7月19日時点の情報です。

notionはワークスペースの参加メンバーに対する課金、ChatGPTは処理量に対する課金なのでケース・バイ・ケースではあります。今回以下のような条件で比較します。

  • 処理が必要なページは月100ページ
    • 800字程度のページを140字程度に要約するという想定
  • Notionワークスペースのメンバーは10名、年払い
サービス 基本情報 計算方法 コスト(ドル) コスト(円)
1ドル140円で計算
Notion API 基本人数課金(処理量に関係なく人数に対して定額) 10名 × 8ドル 80ドル 約11,200円
GPT3.5 Turbo input:$0.0015 / 1K tokens
output: $0.002 / 1K tokens
[input] 750 * 100 / 1000 * 0.0015 + [output] 150 * 100 / 1000 * 0.002 約0.15ドル 約21円
GPT4 input:$0.03 / 1K tokens
output: $0.06 / 1K tokens
[input] 750 * 100 / 1000 * 0.03 + [output] 150 * 100 / 1000 * 0.06 約3.0ドル 約420円

今回スクリプトの実行環境は考慮していないので、

  • 何をトリガーとして実行するか
  • 行基盤を何にするか

によってもChatGPT API実装の方は費用感変わってくると思います。

が、処理する件数が多くない場合はGPT-4を使ってもChatGPTの方が圧倒的に安いです。

【Stable Diffusion】API経由で画像を大量に生成する方法

はじめに

こんにちは、プレックスの池川です。

最近、AI の話題を聞かない日はないと言っても良いくらい AI が身近になりました。 弊社でも ChatGPTGitHub Copilot を会社負担でエンジニアに配布しており、日々の開発に役立てています。

テスト環境の構築時には大量の画像や写真が必要となることが多く、それらの準備には一定の時間と労力がかかります。 この問題を解決するために、画像生成AIを使用して大量の画像を準備してはどうかと考え、今回、それを検証してみました。 Web UI からでも画像生成は可能ですが、生成できる枚数に上限があり、また保存先が限定されるため、API経由で画像生成を行うスクリプトを作成しました。

この記事では検証する中で得たノウハウについて、共有できればと思います!

【この記事で触れること】

  • Stable Diffusion を使って API 経由で画像生成する方法

【この記事で触れないこと】

  • 画像生成AIモデルの詳しい説明
  • プロンプトやパラメータのこと

実行環境

  • Stable Diffusion 2.1
  • Stable Diffusion web UI(AUTOMATIC1111)
  • Google Colab Pro
    • Runtime type: Python3
    • Hardware accelerator: GPU
    • GPU type: A100(何でもOKだが A100 > V100 >T j4 の順にスペックが高い)

手順

1. Google Colab の環境設定を行う

今回は簡単に環境構築ができる Google Colab 上で stable-diffusion-webui を動かしました。 Google Colab の設定は実行環境にまとめていますので参考にしてください。

注意点として Google Colab には無料枠がありますが、UI上で画像生成をする用途だと制限されるため、有料のプランを準備する必要があります。 無料枠のまま stable-diffusion-webui を動かそうとすると下記のような警告のメッセージが表示されます。 日本語の規約には2023年5月29日時点では反映されていなかったため、英語版の規約を確認ください。

2. Stable Diffusion web UI を起動する

Google Colab で下記のコマンドを実行します。最後の行に書かれてある python コマンドのオプションに—-apiをつけることで、APIが実行できます。

# 必要なライブラリをインストールする
%pip install torch==2.0.0+cu118 torchvision==0.15.1+cu118 torchtext torchaudio torchdata==0.6.0 --index-url https://download.pytorch.org/whl/cu118

# stable-diffusion-webui のソースコードをクローンしてくる
!git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui
%cd /content/stable-diffusion-webui

# モデルをインストールする
!wget https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors -O /content/stable-diffusion-webui/models/Stable-diffusion/sd_v2.1.safetensors

# 起動
!python launch.py --share --api --xformers --enable-insecure-extension-access

起動すると、public URL が発行されますのでこちらにアクセスします。 後ほど API を実行する際にもこの URL を使います。

アクセスして下記のような画面になれば起動完了です。

3. API を実行するスクリプトを作成する

続いて画像生成用のスクリプトを作成します。 スクリプトは普段、Rubyを使うことが多いのでRubyで書きました。 public URL と生成したい画像の枚数をオプションで渡すことで、画像を生成後ローカルに保存することができます。

require 'uri'
require 'net/http'
require 'net/https'
require 'json'
require 'base64'
require 'date'
require 'optparse'

# コマンドラインオプションをハンドルする
def parse_arguments
  opt = OptionParser.new
  params = {}
  opt.on('-i VAL') { |v| params[:i] = v }
  opt.on('-n VAL') { |v| params[:n] = v }
  opt.parse!(ARGV, into: params)
  params
end

# 画像生成に必要なプロンプトやパラメータを設定する
def build_payload
  {
    "prompt" => "warehouse entrance",
    "negative_prompt" => "human",
    "sampler_name" => "DDIM",
    "restore_faces" => true,
    "steps" => 150,
    "width" => 1200,
    "height" => 675,
  }
end

# リクエストを送信する
def send_post_request(uri, payload)
  Net::HTTP.post(uri, payload.to_json, "Content-Type" => "application/json")
end

# 画像をローカルに保存する
def save_image(data, index)
  decode_image = Base64.decode64(data["images"][0])
  file_name = "#{DateTime.now.strftime('%Y%m%d%H%M%S')}_#{index + 1}.png"
  File.open("./output/#{file_name}", "wb") do |file|
    file.write(decode_image)
  end
  file_name
end

# 画像を生成する
def create_and_save_images(input_url, number_of_images)
  uri = URI.parse("#{input_url}/sdapi/v1/txt2img")
  payload = build_payload

  number_of_images.times do |i|
    puts "Generate #{i + 1} image"

    response = send_post_request(uri, payload)
    image_data = JSON.parse(response.body)

    file_name = save_image(image_data, i)

    puts "Download #{file_name} to local"
  end
end

params = parse_arguments
create_and_save_images(params[:i], params[:n].to_i)

build_payload メソッドでは画像生成に必要なプロンプトやパラメータを指定しています。

  • prompt
    • 画像生成系AIでどのような絵を出力して欲しいかを指示する文字列
  • negative_prompt
    • 排除したい要素
  • sampler_name
  • restore_faces
    • true にすることで顔の崩れを防ぐ
  • steps
    • ノイズを除去する回数、多いほどノイズが除去されるが生成に時間がかかる
  • width、height
    • 生成する画像のサイズ

APIの仕様は「public URL/docs」(例:https://1234566789abcde.gradio.live/docs)で確認できますので、そちらを参考にしてください。今回はテキストからイメージを生成したいので、使用するのは「/sdapi/v1/txt2img」です。プロンプトに加えてステップ数が画像のサイズなどが指定できます。

4. スクリプトを実行して画像を生成する

3で作ったスクリプトを実行します。

$ ruby ファイル名 -i https://1234566789abcde.gradio.live -n 100

実行中は Web UI で生成した時と同様に、Colab 上で状況を確認できます。

画像生成が完了するとローカルの指定していたディレクトリに画像が保存されています。

生成された画像の一例です。

今回指定した条件だと、100枚の画像を生成でおよそ40分かかりました。また、使用した Google Colab のリソースは9コンピューティングユニットで、費用としては約106円でした。(費用はGoogle Colab のプラン一覧参照)

さいご

本記事では画像生成AIを使ってAPI経由で画像生成する方法について、触ってみて得られたノウハウをまとめました。 はじめて WebUI 上で画像生成したときも面白いなと思いましたが、API経由で画像生成ができることでより幅が広がりそうです。

API 経由で画像生成する方法としては、今回、取り上げたもの以外にも DreamStudio が提供しているStability APIがあり、今後もどのようなことができるかも含め様々な検証を行っていきたいと思います。

最後になりますが、プレックスではソフトウェアエンジニアフロントエンドエンジニアを募集しています。 ご興味がある方はぜひご応募ください!

FactoryBot運用ガイドを作りました

はじめに

こんにちは、プレックスの種井です。

PlexJob開発チームではRSpecによるテストに使用するfixtureの作成に、FactoryBotを使用しています。 テストコードに対してはrubocop-rspecにより一定のルールに則ったコードが作成されていますが、Factoryの定義やオブジェクトの生成方法などは個々のメンバーに委ねられており、オンボーディングやコードレビューの際に方針に対して方針に対して疑問が上がる箇所になっていました。 今回、所属するチーム向けに運用やコーディングのルールを作成したので、この場を借りて紹介したいと思います。

目次

  • はじめに
  • 目次
  • 運用ガイド
    • 運用の観点
    • 運用時のルール
  • 終わりに
  • 参考資料

運用ガイド

前提と運用の観点

運用するにあたっての観点として「可読性」や「再利用性」はもちろんですが、DBへのアクセスを伴うこともあるため「パフォーマンス」にも配慮する必要があります。

また、オペレーションやコーディングの作業上のどの部分で考慮すべきかがわかりやすいように、各方針やルールを「設定」、「定義」、「作成」に分類しました。

運用時のルール

  • 設定
    • FactoryBot::Syntax::Methodsを設定、クラス名を省略して、メソッドの呼び出しをする
    • FactoryBot公式のlinterを使う
  • 定義
    • ファイル名は複数形にする
    • 例: Userを定義する場合はusers.rbにする
    • 1ファイル1定義にする
    • デフォルトデータはなるべく最小限かつ簡潔にする
      • テストに必要なattributeのみを定義する
      • traitでhook(after(…))を使用して、関連データ(has_manyな)の作成はオプションにする
      • 特殊な状態の最小単位をtraitで定義する
        • ただし、なんでもtraitにせず汎用的なデータ状態のみに限る
    • 固定値ではなく本物に近いデータを定義する
      • sequenceやFakerの活用を検討する
  • 作成
    • 何でもcreate(…)にしない
      • association先はassociationで定義する
    • 1度に複数のオブジェクトが必要になる場合はbuild_list,create_listを利用する

設定

クラス名を省略して、メソッドの呼び出しをする

公式のセットアップガイドにもありますが、rails_helper.rbFactoryBot::Syntax::Methodsを定義することで、クラス名を省略してメソッドを呼び出すことができます。

# rails_helper.rb

RSpec.configure do |config| 
  config.include FactoryBot::Syntax::Methods 
end
# クラス名(FactoryBot)を省略してメソッドを呼び出します。

# build 
user = build(:user) 

# create 
user = create(:user)

FactoryBot公式のlinterを使う

FactoryBot.lintを実行することで、定義された全Factoryに対してcreateを実行し、データの作成時に例外が発生する場合は、該当するFactoryの一覧とともにFactoryBot::InvalidFactoryErrorを例外として投げてくれます。

未定義の必須フィールドのような、Factoryのデータ定義の不備を事前に検知することができます。

公式にあるようにrakeタスクを作成して、GitHub Actionsなどから呼び出すことが推奨されています。

# lib/tasks/factory_bot.rake

namespace :factory_bot do
  desc "Verify that all FactoryBot factories are valid"
  task lint: :environment do
    if Rails.env.test?
      conn = ActiveRecord::Base.connection
      conn.transaction do
        FactoryBot.lint
        raise ActiveRecord::Rollback    
      end
    else
      system("bundle exec rake factory_bot:lint RAILS_ENV='test'")
      fail if $?.exitstatus.nonzero?
    end
  end
end
$ bundle exec rake factory_bot:lint RAILS_ENV='test'

定義

ファイル名は複数形にする

例として、Userモデルが定義されている場合に対応するFactoryのファイル名はusers.rbにします。

1ファイル1定義にする

例として、users.rbを作成する場合

FactoryBot.define do 
  factory :user do # 1ファイルに対して1定義にする
    ... 
  end
end

デフォルトのFactory定義はなるべく最小かつ簡潔にする

テストに必要なattributeのみを定義し、使用しないattributeまでデフォルトのデータを定義しないようにします。

traitでhook(after(...))を使用し、has_manyな関連データの作成はオプションとして呼び出し時に選択できるようにします。

FactoryBot.define do
  factory :user do
    trait(:with_posts) do # 最小単位にする&オプションとして、使用時に選択できるようにする
      transient do
        posts_count { 5 }
      end

      # has_manyな関連データの作成
      after(:create) do |user, evaluator|
        create_list(:post, evaluator.posts_count, user: user)
        user.reload
      end
    end
  end
end

使用頻度が高い、特定の状態はtraitで定義しておきます。

ただし、なんでもtraitにするのではなく、汎用的なデータ状態のみに限ります。

FactoryBot.define do
  factory(:post) do
    trait :published do
      published { true }
    end  

    trait :unpublished do
      published { false }
    end
    
    trait :week_long_publishing do
      start_at { 1.week.ago }
      end_at { Time.now }
    end
    
    trait :month_long_publishing do
      start_at { 1.month.ago }
      end_at { Time.now }
    end

    factory :week_long_published_post, traits: [:published, :week_long_publishing]
    factory :month_long_published_post, traits: [:published, :month_long_publishing]
    factory :week_long_unpublished_post, traits: [:unpublished, :week_long_publishing]
    factory :month_long_unpublished_post, traits: [:unpublished, :month_long_publishing]
  end
end

固定値ではなく本物に近いデータを定義する

一意性の必要な値にはsequenceの利用を検討します。

また、本物に近い適当な値を設定したい場合はFakerの利用を検討します。

FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "person#{n}@example.com" } # sequence
    
    name { Faker::Name.name } # Faker
  end
end

作成

何でもcreate(...)にしない

createメソッドは毎回SQLが実行されるため、テスト実行のパフォーマンスに影響を与えます。

モデルのバリデーションなど、DBに値が生成されている必要がないテスト対象の場合はbuildメソッドをはじめとした、メモリ上にオブジェクトを作成する機能を使用します。

build(:user)
build_stubbed(:user) # buildで済ましたいが、idやtimestampが必要な場合はbuild_stubbedを使用する

association先を事前に作成する場合は、associationで定義しておくことが推奨されます。

createメソッドを使用してassociation先を作成すると、buildした際に常にcreateが伴ってしまうためです。

FactoryBot.define do 
  factory :post do
    user # belongs_toの関連(associationの省略記法) 
    user { create(:user) } # NG postをbuildした場合でもuserがcreateされてしまう
  end
end

また、作成時にかかる処理はinstance_double,spy(RSpecの機能) > build(…) > create(…) の順で速いです。

1度に複数のオブジェクトが必要になる場合はbuild_list,create_listを使用する

必要以上にオブジェクトを作成しないようにします。

また、transientを使用して、作成数に対してラベル付けをしておくようにします。

FactoryBot.define do 
  factory :user do
    trait(:with_posts) do
      transient do
        posts_count { 5 } 
      end

      # 必要な分だけ作成する 
      after(:create) do |user, evaluator| 
        create_list(:post, evaluator.posts_count, user: user) 
        user.reload 
      end 
    end 
  end 
end

終わりに

公式ドキュメントや先人達がベストプラクティスとして公開してくださっている資料を参考に、まず弊社チームでも取り組んでみるとよさそうなものをピックアップし、ガイドの作成を行いました。

今回作成したものをベースに運用しつつ、その中でさらに取り入れたものや、工夫したものは今後もブログなどで紹介したいと思います。

最後になりますが、プレックスではソフトウェアエンジニアフロントエンドエンジニアを募集しています。

上述のような、開発上の取り組みや課題に対してご一緒していただける方、少しでも興味を持っていただけた方は業務委託や副業からでも、ぜひご応募いただけると幸いです。

参考資料

【入社エントリ】事業とエンジニアの関わりからみたプレックスの働きやすさ

はじめまして、小松と申します。2ヶ月連続入社エントリで恐縮です。

昨年2022年11月に入社しました。なんだかんだ入社5ヶ月程たっており、先月の池川の1ヶ月先輩です。ちょっと何を書こうか考えているうちに先をこされたりなぞしてしまいました。えへへ。

簡単な自己紹介

社会に出てから何年か数えてみましたが12年目だと思います。ここまで一瞬です。なぜプレックスに来たのかと言うと、みんな元気があって強そうだったからです。

以下が私の略歴です。ざっくり社会人になってからエンジニアを志して、気がついたらここにいました。

  • 2013年 SES企業へ入社、ITエンジニアとしてのキャリアを開始
  • 2016年 保険代理店へ転職、情シスとして事務業務の改善業務を行う
  • 2018年 オンラインでカーメンテナンスを行う事業会社へ転職、WEBエンジニアとしてのキャリアが始まる
  • 2022年11月 プレックスへ入社 ソフトウェアエンジニアとしてスキルを広げたいと考える

入社前のプレックスの働き方への不安について

まず正直に感じた不安から書いておきます。みんなが元気そう、という理由で入社を決めたのとは矛盾しますが、今までフルリモートで働いていたところから出社を伴う働き方になるというところには不安を感じていました。

入社前はフルリモートだったが果たして出社に戻れるか

事前にリモートは週3回までと聞いていており、個人的にプレックス入社前に感じていた不安はこれにつきました。毎日でないにせよちゃんと通えるのかと。

前職はほとんどフルリモートであった為、出社に対する抵抗感は正直少なからずありました。かといって前職も始めからフルリモートだったわけではなく、例のごとくコロナ禍を経てのリモート化だったので初めからフルリモートの会社に勤める事とは事情が違った事もあり、はなからフルリモートの会社を選択することにも抵抗がありました。

最初の2週間は慣らしの為に全日出社しました。意外にも、新しい職場に慣れる事に精一杯であった為、出社のつらさはあまり感じませんでした。そして、現状は私は週2リモート、週3出社というスケジュールで行動しています。リモートの日はプロダクトチーム内で共有して基本的に作業集中日として、出社日にコミュニケーションを凝縮させるというスタイルの働き方をしています。今の所これは中々心地よく効率的に感じます。

ただし、出社/リモートのハイブリッドワークに伴う課題は月並みに存在する為、メンバーが増えてくれば課題解決をもっと真剣に考える必要があると思っています。

事業とエンジニアの関わりから見るプレックスの働き易さについて

プレックスのエンジニア文化の良さ、面白さというのは過去のエントリーから分かるかなと思うので、今回はエンジニアの組織とビジネスサイド(という呼び方は好きじゃないですが便宜上)との関係からプレックスの意外(?)な働きやすさを書いてみます。

事業部とエンジニアグループの距離感

現在の組織図はこちらのエントリーから変わらず、エンジニアグループの中に事業部で割られているマトリクス状の組織図となっています。プレックスのエンジニア組織ではこの形が上手く機能しているなと感じます。

組織の中に階層や役割分担はありますが、プレックス内ではそれがゆるやかで、プロダクト開発において情報のやり取りを誰かをハブとする点の形ではなく、各々が能動的に取り合う正に網のような形のコミュニケーションを行っています。また、プロダクト(事業)軸の定例の他、網の中で見つけた情報や課題を持ち帰り共有できるエンジニア定例も行っており、事業部とのコミュニケーションとエンジニア間のコミュニケーションが安心して両立できる仕組みとなっています。

事業とプロダクトの絶妙な関係

プレックスには(必ずしも1対1ではありませんが)いくつかの事業部とプロダクトがあります。

広義の意味でのソフトウェアエンジニアとして、プロダクトを前面に出して自社サービスを行っている事業会社で働くというのはある種のロマンがあります。しかし、すげープロダクトを作ってやるぞと入社してみると、多くのプロダクトの表に出ている面は全体のごく一部で、それは営業、CS、バックオフィスなど多くのメンバーのオペレーションで成り立っている事を知ったりします。また、ここでのバランス感覚が崩れると事業部とプロダクトチームの気持ちがばらばらになってしまう事もままあります。

プレックスで私はPlexJobというサービスに関わっており、出面としてはWEBサービスですが、例に漏れずに多くのメンバーのオペレーションで成り立っています。そんなPlexJobの事業責任者はオペレーションとプロダクトに対して中立的であると感じます。非常に現実主義ではありますが、良いものを作りたい気持ちも強く持っています。おかげで事業部内では職種に関係なく互いをリスペクトする文化が醸成されており、エンジニアが働きやすい環境の大きな要因となっていると感じます。

経済的に独立した事業会社であるということ

エンジニアって技術を武器に社内でそれなりに独立した姿勢を見せられそうな感じは見せますが、実際のところはトップの経営判断や社内の政治情勢の波をそれなりに強く受けますよね〜。会社にもよるのでしょうが、私が以前勤めた会社のいくつかは母体の大きい子会社だったので本当に良くも悪くも親会社の出す意見、方針の影響が仕事にありました。

この辺りは専門外である為細かい記載はしませんが、現状そうでないプレックスはここにいる代表、事業責任者、メンバーが本気でプレックスの成長の為に会社を動かしています。当たり前とい言われればそれまでですが、自分たちの作るものが本気で自社の成長を願って作られているものであるという実感は大きなモチベーションとなっています。

最後に

前回の池川さんも書いていましたが、プレックスに来てから毎日が爆速で進んで行きます。各職種に様々なキャラクターのメンバーがおり、日々が刺激的です。

そして恒例になりますが、プレックスでは一緒に働くメンバーを募集しています。組織にもプロダクトにもまだまだ改善点があり、人の手も脳みそも足りていません。

現在エンジニアの他、UIデザイナーの募集もありますので、今回の記事を読んで少しでも興味を持って下さった方はお気軽にご連絡ください。

dev.plex.co.jp

宣伝:私のプレックス内の私的な活動について

Plex電子工作部やってます

なぜ私がエンジニアリング(というより最初はプログラミング)の世界に興味を持ったかというと、なんとなく格好良い感じがしたからです。というより出身が文系というのもあり、工学全般に憧れを感じています。

そこで最近ははんだごてを購入して電子工作をはじめました。まずは電子工作入門の王道、自作キーボードから始めています。

そしてなんとプレックスにはそういった事に興味を持ってくれるメンバーがおりましたので、(非公式ですが)電子工作部の活動も始まりました。プレックスでは一緒に働くメンバーを募集しておりますが、私は一緒に電子工作をしてくれるメンバーも探しております。今回の記事を読んで少しでも興味を持って下さった方はお気軽にご連絡ください!

↓は個人活動ですがこんな感じでやってます・・・!

自作キーボード チェリーパイ(CherryPie)ビルドログ 前編

youtu.be