DEEPCORE × OpenAI AI Hackathon から4か月。使い方は何が変わったか

こんにちは。 リードエンジニアの冬鏡です。

2026年4月24日に DEEPCORE × OpenAI AI Hackathon がありました。 HEARTBEATS からはバックエンドチームの S.K さん と Y.S さん が出て、他社のエンジニアと組んだ5人チームで優勝しています。 当日なにを作ったか、1時間半をどう使ったかは、同じチームだったテックタッチさんの記事に書かれているので、そちらを読んでください。

tech.techtouch.jp

私が聞きたかったのは、その後です。 ハッカソンからちょうど4か月たった8月24日、2人に1時間ほど話を聞きました。 4月の時点で AI に何を期待して、どう使っていたのか。 8月まで残ったやり方は何で、何をやめたのか。 任せる範囲、指示の出し方、レビュー、仕事の進め方はどう変わったのかを取材してみました。

シンプルに投げるようになった

ハッカソンの1時間半で、2人はコードをほとんど見ずに動くものを作りました。 コードを見ずに作り切ったのは、Y.Sさんにとっては初めてだったとのことです。 当時の流行はいかに工夫をして大規模なタスクを行うか、でした。 複数のエージェントを組み合わせて、あの手この手を尽くして任せていました。 トークン消費も気にしていません。 当時は、そういう時期でした。

4か月後、Y.S さんのやり方は逆に簡素になっていました。

Y.S: 昔と比べて、いろいろな技を繰り出して任せる必要はだんだんなくなってきました。 むしろシンプルに任せる感じです。複雑なマルチエージェント構成はやめました。 変なハックもしなくなりました。 いまは安いモデルに短い指示を出す、という使い方に落ち着いています。 トークンの消費を気にするようになった、という事情もあります。

S.Kさんの場合、簡素になったのは指示の中身より入力のほうでした。

S.K: とにかく、プロンプトを打つのすら面倒になっている、という変化があります。

最近は Mac 標準の音声入力でそのまま投げているそうです。 精度は普通で、使い物にはなるし、昔よりは上がっているそうです。 細かい認識ミスは LLM が文脈で補正してくれるので、あまり困らないと K は言います。 それぞれのやり方で、AI への投げ方は簡素になっていました。

コードの外まで任せるようになった

投げ方は簡素になった一方で、任せる範囲は広がっています。 S.Kさん が「4月から結構変えた」と言ったのはここでした。

S.K: いろいろ仕事上のサービスを連結するようにしたのが一番の変化ですね。 Slack と Atlassian と Google と、全部つないだ結果、AI が直接見て、直接操作できるようになりました。

4月の時点では、コードは AI が書いても、Jira のチケットを切る、Confluence を書くといった周りの作業は人間の仕事でした。 8月には、そこも人間がやらなくてよくなってきています。 もちろん、つないだだけで品質は出ません。 S.Kさんがやっているのは、ルールをスキルに落とすことです。

S.K: 例えば Jira のチケットを書くルールをスキル化したり、PR の作り方もスキル化したり、ということを最近はやっています。 全部任せた上で自分が求める形のチケットや PR を簡単な指示で AI が出してくれるように、ワークフローを作っています。

スキルというのは、このチームではチケットはこう書く、PR はこう作る、と AI に教えておく手順書のことです。 一度書いておけば、短い指示でも狙った形にそろえやすくなります。 第1章の「シンプルに投げる」が成り立つ理由の半分は、工夫の置き場所がプロンプトからスキルに移ったことにあります。 そのスキル自体も人が手で書くわけではなく、「今やったことをスキルにして」と AI に頼んで作らせているそうです。

Y.S さん側でも同じ方向の変化がありました。 大きな課題を Jira に落とすためのタスク分解を AI に頼んだり、社内向けの問い合わせボットを新しく作ったりしています。 ボットのほうは、Claude Code を動かすような大がかりなものではありません。 SDK でコードを読ませて調査させる作りで、プロンプトと知識ベースを用意すれば AI が勝手に読んで答えてくれる、と Y.Sさんは説明していました。

ボトルネックは仕様に移った

実装はもう AI の仕事になり、人間の出番は減ったようにも見えます。 一方、レビューではまた事情が異なるようです。 Y.S さんが受け持つバックエンドのレビューは、この4か月で中身が変わりました。

Y.S: どちらかというと、仕様に関するレビューがメインになってきています。 リファクタリングも結構いろいろやっていたので、AI にそこの設計がちゃんとできるかというと、まだあまりできていない感じです。

コードの良し悪しを見る時間は減りました。 代わりに増えたのが、この仕様で合っているかを見る時間です。 設計やリファクタリングの判断は人間に残っていて、S.K さんもバックエンドについては変わっていないと同意しました。 レビューそのものを AI に任せる試みは、現在も途上です。

Y.S: 過去の PR を読ませてスキルを作っていましたよね。

S.K: そうですね。 でも、まだうまく機能できていない気がします。

細かいテストをたくさん書けばレビューが要らなくなるのでしょうか。 この問いにも S.K さんは「多分、レビューはなくならないと思います」と答えました。 テストの修正やパフォーマンス改善のように入出力が事実上決まっているタスクはほぼ全部 AI に渡せますが、問題は、そうではないタスクも多いことです。

Y.Sさんは、難しいのは仕様がちゃんと合っているかどうかで、開発チームと企画側のコミュニケーションの問題だと言います。 仕様だけ書いて AI に任せても、仕様に矛盾があれば、出てくるコードも変になります。 実装が速くなった分、詰まる場所は実装の手前に移っていました。

AI側が人の工夫を吸収して変化した

この変化はどこから来たのか。 OpenAI の発表では、GPT-5.5 の公開は4月23日でした。 ハッカソンはその翌日です。 会場ではまだ前の世代を使っていたと考えられ、7月9日には 5.6 が出ています。 モデルの世代は、この4か月で少なくとも一つ進みました。

4月からの変化は劇的だったのか、それとも馬力が上がっただけなのか。

S.K: そうですね。 やり方は特に変わっていません。 ただエージェントがパワーアップしただけ、という感じです。 劇的に変わった印象は、個人的にはあまりありません。

投げ方は同じで、任せられる仕事の範囲だけが広がりました。 Y.Sさんが技を繰り出さなくなったのも、これまで人間が工夫で埋めていた分をモデル側が吸収したからだと私は理解しています。

ただ、モデルが賢くなったから何でも任せられる、というところまでは行っていないようです。

S.K: Claude Fable にしても、レビューはまだ全然必要な品質だと思います。 結局、根本のモデル自体が「もうレビューはいらない、完璧だ」と言えるレベルにはまだ届いていないと思っています。

書く速さと幅では、人間の工夫を要らなくするところまで来ました。 書いたものにレビューが要らない、と言えるところまでは来ていません。 では、これからどうなってほしいのでしょうか。 S.Kさんが4月からの変化として真っ先に挙げたのは、モデル本体ではありませんでした。 周りのツールで、そこがもっと充実してほしいという答えでした。

S.K: 周辺のツールが進化してきていると思っています。 モデル自体の性能はまだ足りないと思うところもありますが、以前よりずっと便利になっているので、周辺のツールはさらに充実させてほしいです。

理想を言えば、IDE そのものを Claude に出してほしいそうです。 今はアプリと CLI を使い分けていて、それが面倒だからです。 1つのアプリで全部できるようにしてほしい、と S.Kさんは話していました。

おわりに

4月の目玉は、AI に何をどう投げるかの工夫でした。 8月には、任せる作業をスキルに落とし、人間は仕様とレビューに時間を使うようになっています。

実装が速くなると、仕様が追いつきません。 取材の後半で2人が何度も戻ってきたのはそこでした。 HEARTBEATS でも、開発者がこれまでより仕様に関わることが課題になっています。 凝った投げ方を覚える必要はもうあまりありません。 まずつないでシンプルに任せ、空いた時間を仕様に回す。 2人の4か月から見えたのは、その順番でした。

最後に、HEARTBEATS では開発系職種も積極採用中です。 この記事に書かれているような開発の進め方に興味を持った方、あるいはプロダクトに興味を持った方がいたら、ぜひ気軽にご応募ください。

heartbeats-inc.co.jp

hrmos.co

入社前に知りたかった、ORICAL フロントエンドコンポーネントシステムの話

株式会社HEARTBEATSでフロントエンドエンジニアをやっている何です!今回は会社のプロダクトであるORICALのコードベースについて話をしていきます。

heartbeats-inc.co.jp

エンジニアとして転職活動をする際に、会社の事業内容や技術スタック、開発体制については、面接の中である程度知ることができます。

でも、実際に毎日触るコードベースの実態は、入社するまでほとんど分かりません。

どんな設計思想で書かれているのか。
画面が増えたときに、ちゃんと拡張できる構造になっているのか。
複雑な業務ロジックと UI が絡み合って、触るのが怖い状態になっていないか。

私自身も、入社前にはそういう不安がありました。

特にフロントエンドは、プロダクトが成長するほど画面が増え、状態が増え、例外対応も増えていきます。外から見たサービスがきれいでも、実際のコードがどう設計されているかは、なかなか分かりません。

この記事では、入社して自分が実際にコードを読みながら感じた、当社フロントエンドの Atomic Design をベースにしたコンポーネント分類と、カラーパレットまわりの設計について書きました。

Atomic Design にはいろいろな流儀がありますが、この記事では、通信責務やドメイン分割まで含めた設計論全体ではなく、UI コンポーネントを粒度ごとに分け、その置き場所と命名をどう扱っているかに絞って見ています。

「入社したら、どんなコードに触れることになるんだろう」という不安を少しでも減らせる記事になればうれしいです。

このコードベースの規模感

今のコードベースは執筆現在、以下の規模になっています。

  • コミット数は約 10,500
  • 実装コードは約 32 万行
  • 主な技術スタックは Nuxt、Vue、TypeScript、Pinia、Sass
  • テストは Jest、Vitest、Playwright

概算したものなので、厳密な数字ではありませんが、6年以上あるだけあってかなり大きいです。

UI コンポーネントも多いのですが、その整理方法については、このあとの Atomic Design の項目で詳しく書きます。

慣れてきて気づいたコードベースの良いところ

最初は、Atomic Design のレイヤー、カラーパレット、パートナーごとの差分、API リクエストまわりのカプセル化など、プロジェクト独自の書き方もあるので、正直に言うと覚えることが多いです。

ここでいうパートナーとは、ORICAL を導入している球団、スポーツ団体、エンタメ企業などのことです。
サービスの基本構造は共通していますが、各パートナーのブランドやファン体験に合わせた見た目・画面・導線の違いがあります。

ただ、少しずつ読み進めていくと、それらが単なる独自ルールではなく、大きなプロダクトを長く運用するための設計だと分かってきました。

ここからは、実際にコードを読んでいて「これは良いな」と感じたところを紹介します。

1. Atomic Design によって、大量のコンポーネントをチームで扱える形にしている

メインのコンポーネントフォルダだけで 687 個のコンポーネントがあります。

これだけの量があると、分類と命名が曖昧なだけで、すぐに探しづらくなります。

このコードベースでは、コンポーネントの置き場所と名前に Atomic Design のレイヤーがはっきり反映されていて、その混乱を防いでいます。

記事を書いている現在のコードベースを集計すると、メインのコンポーネントフォルダ配下は次のような規模でした。

src/components/UI
├── atoms      91 個
├── molecules  120 個
├── organisms  232 個
└── templates  244 個

それぞれのレイヤーの意味は、だいたい次のように分かれています。

  • atoms 最小単位の UI コンポーネント
  • molecules atoms を組み合わせた小さな UI コンポーネント
  • organisms プロダクト固有の概念を扱う UI コンポーネント
  • templates 画面全体に近い構造を組み立てるコンポーネント

たとえば atoms には、ボタン、テキスト、画像、動画、枠、ラベル、ローディングなど、画面の最小単位になるコンポーネントが置かれています。

src/components/UI/atoms
├── AButton.vue
├── ADescription.vue
├── AImg.vue
├── AVideo.vue
├── ARect.vue
├── AProgressBar.vue
└── ...

molecules には、複数の atom を組み合わせた UI パーツが置かれています。

src/components/UI/molecules
├── MCarousel.vue
├── MTabBar.vue
├── MOrica.vue
├── MSelectButtons.vue
├── MPopupBase.vue
└── ...

organisms になると、プロダクト固有の概念を扱うコンポーネントが増えます。

ここでいうプロダクト固有の概念とは、パートナーごとの見た目の違いではなく、ORICAL の機能として出てくるミッション、ランキング、ガチャ、ログイン状態のようなものです。

たとえば、ミッションの進捗や達成状態、報酬の受け取り可否といったプロダクト固有の概念を扱うコンポーネントは、organism として実装します。一方で、見た目や操作だけで完結するボタン、テキスト、タブ、カルーセルのようなコンポーネントは、より下位の layer で実装します。

src/components/UI/organisms
├── cardpack
├── bingo
├── game
├── mission
├── ranking
├── login
└── ...

templates は、ページ内の大きな領域を組み立てるレイヤーです。

ここでいう template は、単なる skeleton というより、organism や molecule を並べて、一覧、詳細、空状態、ローディング中など、ユーザーに見える画面のまとまりを作るものです。必ず「1 ページ \= 1 template」ではなく、画面内の大きな領域ごとに分けることもありますが、API request や細かい業務判定を抱えるというより、渡されたデータを使ってページに近い UI 構造を組み立てる役割に近いです。

src/components/UI/templates
├── cardpack
├── home
├── mvp
├── binder
├── missions
├── collection
└── ...

命名もかなり分かりやすいです。ファイル名とコンポーネント名にはレイヤーを表すプレフィックスが付いています。

AButton        atom
ADescription   atom
MCarousel      molecule
MTabBar        molecule
OMissionItem   organism
OBingoFooter   organism
TMissions      template
THomeTop       template

A / M / O / T のプレフィックスがあるので、初めて見るコンポーネントでも「だいたいどの粒度のものなのか」が名前だけで分かります。

なお、このコードベースには A / M / O / T 以外のプレフィックスもあります。たとえば CColor のような C* は、見た目のレイヤーに入るものというより、色設定などを配下の UI コンポーネントに渡すためのコンポーネントです。また、MdDialog のような Md* はモーダル用のコンポーネントで、ボタンやテキストなどの基礎コンポーネントを組み合わせて、1 つのポップアップ体験を作ります。

たとえば OMissionItem は、ユーザーが達成するミッションの1つを表示する organism です。

ここでいうミッションは、条件を満たすと報酬を受け取れるタスクのようなものです。概念的には、次のように atom を組み合わせてミッションの UI を作ります。

<template>
  <li>
    <a-description>
      {{ mission.title }}
    </a-description>

    <a-progress-bar
      :value="mission.currentCount"
      :max="mission.requiredCount"
    />

    <a-button
      v-if="mission.isCompleted && !mission.rewardReceived"
      color="primary"
    >
      報酬を受け取る
    </a-button>
  </li>
</template>

このコンポーネントは「ミッション名」「進捗」「達成済みかどうか」「報酬を受け取れるか」といったプロダクト固有の情報を知っています。
なので atom ではありません。

一方で、ミッション画面全体の一覧、空状態、タブ切り替えまで組み立てるわけではありません。
なので template でもありません。

この粒度だから organism に置かれている、という納得感があります。

Atomic Design の良さは、実装者だけでなくレビュワーにも効いていると感じます。

  • atom の変更なら、共通 UI への影響を見る
  • molecule の変更なら、組み合わせ方や再利用性を見る
  • organism の変更なら、プロダクト固有の概念として自然かを見る
  • template の変更なら、画面全体の流れを見る

新しく入った方にとっても、「まずどこを探せばいいのか」「この変更はどのレイヤーに入れるべきか」が分かりやすくなります。

2. 業務データの流れと UI 表現の間に、はっきりした境界がある

まず良いなと思ったのは、UI コンポーネントがすべての責務を抱え込んでいないことです。

このコードベースでは、コンポーネントのレイヤーによって、扱う責務がかなり変わります。

たとえば AButton や ADescription のような atom は、基本的に最小単位の UI 表現を担当します。

<a-button color="primary" font-color="white" :emboss-size="3">
  購入する
</a-button>

このレイヤーでは、「このボタンをどう描画するか」「disabled 状態のときにどう見えるか」「リンクとボタンをどう切り替えるか」のような、UI の基礎的な振る舞いに集中します。

一方で、`MCarousel` や `MTabBar` のような molecule は、複数の atom を組み合わせて、少し大きな UI パーツを作ります。

<m-tab-bar
  :items="['イベント', '達成済み']"
  :routes="['/missions/event', '/missions/completed']"
/>

さらに TMissions のような template になると、画面全体に近い構造を組み立てます。

<t-missions
  :loading="loading"
  :missions="missions"
  :selected-tab="selectedTab"
/>

ここで大事なのは、atom がいきなり API リクエストの詳細を知ったり、画面全体のビジネスフローを抱えたりしないことです。

もちろん、template や organism は、ミッションの進捗や報酬の受け取り可否、ランキングの順位のような、プロダクト固有の状態や表示条件を扱います。完全にビジネスロジックがゼロというわけではありません。

ただ、業務データの流れと、UI をどう表現するかの間には、かなりはっきりした境界があります。

この境界があると、長期的な保守性にかなり効いてきます。

たとえばミッション画面で、API レスポンスに新しい達成状態が追加されたり、報酬を受け取れる条件が変わったりしたとします。
もし ボタン(AButton) や 説明文(ADescription)、タブ切り替えコンポーネント(MTabBar)、カルーセル(MCarousel)のような基礎 UI コンポーネントに、ミッション固有の条件分岐やリクエストの知識が入り込んでいたら、仕様変更のたびに共通コンポーネントを触ることになります。

共通コンポーネントは使われる場所が多いので、変更の影響範囲も広くなります。
本来はミッション画面だけの変更なのに、他画面のボタンやタブ、カルーセルの挙動まで確認しなければならなくなります。
これが続くと、「小さな変更なのに、どこまで壊れるか分からない」という状態になります。

先ほど書いたレイヤー分けのおかげで、コンポーネントの責務も名前と置き場所から読み取りやすくなっています。
AButton はボタンとしての見た目や基本的な挙動のみを扱い、TMissions はミッション画面としての構造や表示条件を扱います。

つまり、変更理由が違うものを同じコンポーネントに押し込まない設計になっています。
API や業務条件が変わったときは、データや画面に近いレイヤーを見ればよく、ボタンやテキスト、タブ、カルーセルのような基礎コンポーネントは安定したまま保ちやすいです。

大きなコードベースでは、「どこを直すべきか」だけでなく、「どこを直さなくてよいか」が分かることも大切です。
この分離は、その安心感を作ってくれていると感じました。

3. 高度にカスタムされた UI と開発効率の間で、いいバランスを取っている

このプロダクトの UI は、カード、動画、アニメーション、MVP、ビンゴ、バインダー、ゲーム的な画面、パートナーごとのホーム画面など、かなりカスタマイズ性が高く、第三者の UI ライブラリを用いた開発では要件に対応しきれません。

つまり、私たちでコンポーネントを細かく作りこみ、管理する必要がありますが、そのやり方にはいくつかの方式があります。大まかには、コンポーネントを別パッケージもしくはリポジトリにするか、同じリポジトリでまとめて管理するかどうかです。

以前、自分がいた大きめの会社では、コンポーネントを別パッケージとして切り出し、npm パッケージとして管理していました。

その方法にも良いところはあります。複数プロダクトで同じ UI を使う場合は、特に効果があります。
ただし、別パッケージにすると、どうしても次のようなコストが出ます。

  • UI ライブラリ側とプロダクト側で調整が必要になる
  • 修正してからnpmやプライベートレジストリにパブリッシュする必要がある
  • プロダクト側でバージョンアップデートが必要になる
  • 細かい UI 調整のたびに確認フローが増える

このコードベースでは、UI コンポーネントシステムをメインリポジトリの中に持っています。そのため、業務要件と UI の変更を同じリポジトリの中で一緒に進められます。これは、かなり現実的で効率の良い選択だと感じました。

  • 高度にカスタムしたい
  • でも開発速度も落としたくない

その両方を満たすために、Atomic Design コンポーネントをメインリポジトリの中で育てている、という印象です。

4. ユーザー体験の一貫性がコンポーネントシステムによって担保されている

良いコンポーネントシステムは、見た目を再利用するだけではありません。ユーザー体験の一貫性も再利用します。

たとえば AButton は、単にボタンの見た目を提供するだけではありません。
内部で button や a などを切り替え、disabled 状態を扱い、グラデーションやシャドウを扱い、クリック時のアナリティクスのトラッキングも行います。
概念的には、次のような責務を atom 側に閉じ込めています。

const componentName = computed(() => {
  if (to.value && !disabled.value) {
    return "internal-link";
  } else if (href.value && !disabled.value) {
    return "a";
  }

  return "button";
});

const click = (e: MouseEvent) => {
  if (!disabled.value) {
    $analytics.trackUiInteraction({
      componentName: "a-button",
      eventAction: "click",
      pathName: $route.name || $route.path,
      label: gaLabel.value,
    });

    emit("click", e);
  }
};

こうした基礎的な振る舞いが atom に入っていることで、画面ごとにバラバラなボタン実装が増えにくくなります。

「ボタンを押したときのトラッキングをどうするか」
「外部リンクと内部リンクをどう扱うか」
「disabled 状態のときにどう振る舞うか」

こういう細かい体験の差が、コンポーネントレイヤーに吸収されています。

小さなことに見えるかもしれませんが、画面数が多いプロダクトではかなりの効果があります。
UI/UX の一貫性は、こういう基礎コンポーネントの積み重ねで守られているのだと思います。

5. カラーパレットとパートナー差分を、散らばった CSS ではなくシステムとして扱っている

カラーパレットまわりも、とても面白い設計です。

画面上には、「購入ボタン」「選択中のタブ」「カードの枠線」のように、同じ意味を持つ UI が何度も出てきます。

ただ、約 30 パートナーそれぞれでブランドカラーやグラデーションが違うため、各コンポーネントが直接カラーコードを持つと、変更箇所がすぐに散らばってしまいます。

このコードベースでは、色そのものではなく、UI 上の意味をキーにして色を管理しています。
たとえば「メインボタンの明るい配色」「選択中タブの配色」のような意味に対して、背景色、文字色、枠線、影、グラデーションなどをまとめて設定します。

そのための型や仕組みとして、ColorConfigNames、Pallet、CColor コンポーネント、カラーパレット(getColorConfigやgenerated/style) などがあります。
名前だけを見ると少し内部実装寄りですが、役割としては「意味のある色設定を定義し、コンポーネントに渡すための仕組み」です。

たとえば Pallet は、概念的には次のように定義されています。

export type Pallet = {
  [P in ColorConfigNames]?: ColorParams<ColorValue>;
};

つまり、各パートナーのパレットは、自由な文字列キーを好き勝手に追加するのではなく、ColorConfigNames に定義された意味のあるキーを上書きしていく形です。

たとえばパートナーごとのパレットでは、次のように UI の意味に対して色を指定します。

export const partnerNamePallet: Pallet = {
  "main-button-light": {
    color: "primary",
    gradientType: GradientType.fixed,
    gradientOpts: {
      to: "bottom",
      startColor: "primary",
      endColor: "#B38900",
    },
    fontColor: "white",
  },

  "tabbar-light-selected": {
    color: "white",
    fontColor: "#C2961C",
  },
};

ここで重要なのは、コンポーネント側が「このパートナーではこのカラーコードを使う」と直接知らないことです。コンポーネント側は、意味のあるトークンを使います。

たとえば先ほど触れた C* の一つである CColor コンポーネントは、指定された colorConfig から設定を取得し、スロット経由で子コンポーネントに渡します。

使う側は、次のように書けます。

<c-color v-slot="{ config }" color-config="main-button-light">
  <a-button
    v-bind="config"
    :emboss-size="3"
  >
    購入する
  </a-button>
</c-color>

この書き方だと、コンポーネントは「このボタンはメインボタンである」という意味だけを知っています。

実際に何色になるかは、カラーパレット側で決まります。

もし各コンポーネントが直接 #B38900 のような値を持っていたら、パートナーごとの差分はあちこちに散らばります。
変更したいときに、どこを直せばいいのか分かりづらくなります。

カラーパレットによって、視覚差分が設定レイヤーに集約されています。

これは複数パートナーを持つプロダクトではかなり大事な設計だと思います。

まとめ

入社前にコードベースの中身を知ることはなかなかできません。だからこそ不安になります。

実際に入社してコードを読んでみると、派手な仕組みというより、日々の開発で迷いにくくするための工夫が多いコードベースだと感じました。

コンポーネントの粒度、名前の付け方、色の扱い方を見ると、ただ機能を作るだけでなく、あとから触る人が読みやすいか、画面ごとの体験が揃うかまで考えられています。

もちろん、規模が大きいので最初からすべてが簡単に理解できるわけではありません。
ただ、複雑なところにちゃんと名前と置き場所が用意されているので、読み進めるほど設計の意図が見えてきます。

こういう細部に、チームの技術力や設計のセンスが出ていると思いました。

入社前の自分がこの情報を読めていたら、もっと安心していたと思いますので、当社にご興味のある方の参考になれれば幸いです。

heartbeats-inc.co.jp

hrmos.co

バインダー開発の裏側: 仕様づくりからCanvas実装まで

こんにちは。リードエンジニアの冬鏡です。HEARTBEATS のプロダクトである ORICAL には、ユーザーが集めたカードや装飾アイテムを自由に配置して、自分だけの画面を作れる「バインダー」という機能があります。

バインダー機能の紹介画面

バインダー機能の利用イメージ。権利保護のため、一部カード画像をぼかしています。

単に「Canvas を使った機能を作った」という話ではありません。なぜ開発者が仕様検討段階から入ったのか。なぜグリッド配置ではなく自由配置にしたのか。Canvas の上で、移動・回転・衝突判定・ピンチ操作をどう扱ったのか。そして、複数パートナーに展開するプロダクトとして何を意識したのか。

普段の HEARTBEATS の開発がどのように進んでいるのかを、ひとつの機能開発の事例として紹介します。

今回は、バインダー機能の開発に携わった加古さんに、仕様づくりから実装、リリース後の横展開までの話を伺いました。

普段の機能開発はどう進むのか

冬鏡: まず、読者向けに普段の機能開発から運用までの流れを軽く説明していただけますか。機能開発は基本的には事業部側から要望が上がってくる感じですよね。

加古: そうですね。基本的には事業部と、仕様を決めるプロダクトチームがいて、そこから仕様書が降ってくることが多いです。ただ、仕様書を決める段階から開発が触ることも結構あります。「この仕様で本当にいいんですか」といった話をプロダクトチームとすることは多いです。

バインダー機能も実際それに近いと思います。大元の発案はプロダクトチームで、そこから仕様を話し合いながら開発が肉付けしていく形でした。

冬鏡: 仕様がある程度決まったら、開発チケットを取って実装に移る。今の我々だと、基本的にはバックエンドが先で、ある程度できたらフロントエンドが入る、という流れですよね。

加古: はい。一般的にはそうです。

資料: 通常の開発フローとバインダー機能の位置づけ

通常の開発フローと、バインダー機能で開発者が早期から関わった位置づけ。

  • 事業部・プロダクトチームが機能の方向性や仕様を検討する。
  • 仕様書が固まった段階で、バックエンド・フロントエンドの実装に入る。
  • ただし、仕様検討段階から開発側が相談に入ることもある。
  • バインダー機能は、開発者が仕様づくりにかなり早い段階から関わったケース。

バインダーは何が特殊だったのか

冬鏡: バインダー機能の場合はどうだったんでしょう。

加古: バインダーはちょっと特殊でした。リリース前にプロトタイプを作る段階が3回ぐらいあったんです。「こういう感じで動きます」と見せながら、コンテンツパートナーシップ側とも調整して進めていました。

冬鏡: 数段階のプロトタイプ提示のラリーで、仕様まで遡ってブラッシュアップしていく感じですか。

加古: そうですね。バインダー機能は仕様書の変更が結構ありました。途中で大きかったのは、もともとアイテムを自由に配置できなかった仕様を、自由に配置できるようにしたことです。最初はグリッド上にアイテムを並べるだけでした。でも、やっぱり自由に配置できた方が楽しくないか、という話になっていきました。

冬鏡: そのプロトタイプは API まで作り込んだ状態だったんですか。

加古: バインダー機能の開発の流れは少し特殊で、API はまだ薄い状態でした。フロントエンドだけ作って見せる、という形に近かったと思います。保存などもまだちゃんとできていない段階で、まず見通しを立てるためのプロトタイプでした。

資料: グリッド配置から自由配置へ

グリッド配置から自由配置へ変更することで、体験の自由度と実装難度が上がった。

最初のバインダー機能は、カードやアイテムをグリッド上に置く仕様でした。これは実装上は扱いやすい一方で、ユーザーが自分で作っている感覚は弱くなります。

自由配置にすると、実装難度は上がります。

  • アイテムを任意の位置に置ける。
  • 拡大・縮小・回転を自然に扱う必要がある。
  • アイテム同士の重なりや衝突判定が必要になる。
  • タッチ操作の途中状態を丁寧に管理する必要がある。

それでも自由配置にしたのは、バインダーの体験として「自分だけの場所を作る」感覚を強めるためでした。

開発者が仕様書を書く

冬鏡: バインダー機能は、開発が仕様に関わる割合がいつもより多かった感じなんですか。

加古: 仕様のあたりでいうと、バインダー機能は初稿の仕様書を僕が書いています。それをプロダクトチームの方が、人に見せられるものにアップデートしてくれました。仕様書の作成にはエンジニアとして関わりましたが、もともと僕がソーシャルゲームを遊ぶユーザーでもあったため、その視点も活かしています。

冬鏡: ソーシャルゲームだと、バインダーっぽい機能は見かけますよね。

加古: そうですね。カードを飾るような機能は見かけます。仕様書では「なんでこの機能を作るのか」という整理から始めました。どう遊ぶか、という機能面の整理を僕が書いて、売上やプロダクト上の狙いのような専門的なところをプロダクトチーム側が整理してくれた形です。

冬鏡: バインダー機能に開発が最初から関わっていたのは、ソーシャルゲームの知識や、仕様書を最初に書いたという個人的な事情もありましたか。

加古: あるかもしれないですね。バインダーはもともと仕様書があったものの、なかなか企画が進んでいませんでした。森田さんが進めたいと思っているところに、僕も「カードの使い道が増えたら面白いだろうな」と思っていたので、手助けしに行った側面は強いと思います。

「正しい」仕様かどうかは、リリースまで分からない

冬鏡: 開発側から仕様書をゼロから書くことには、どんな難しさがありましたか。

加古: 難しいのは、「正しい」かどうかはリリースするまで分からないことです。つまり、これでちゃんと楽しんでもらえるかどうかです。

たとえば「自由配置にする」という判断も、グリッドより楽しいはずだと思って決めるわけですが、本当にそれが「正しい」かは出してみるまで分かりません。グリッドをやめて自由配置にするのは大きな決断でした。

冬鏡: 仕様のまま進めるか、変えるかの判断が大変だった。

加古: そうですね。もう少し自由度を上げた方が楽しいんじゃないか、という判断がありました。

資料: 仕様判断と実装難度はつながっている

体験を優先した仕様判断が、Canvas上の座標変換や衝突判定の複雑さにつながる。

「自由配置の方が楽しそう」という仕様判断は、そのまま実装の複雑さにつながります。自由配置を選ぶと、Canvas 上で次のような処理を自前で扱う必要があります。

  • 座標変換
  • 拡大縮小
  • 回転
  • 衝突判定
  • 複数ポインターの管理
  • 描画ループのパフォーマンス

バインダーでは、プロダクトとしての楽しさを優先した結果、フロントエンドの作り込みが必要な設計になりました。

Canvasでゲームに近い描画をする

冬鏡: 実装の話に移ると、バインダーは普段あまりやらない実装が多いですよね。たとえば 60 FPS で描画するような、ORICAL ではあまりなかった実装です。

加古: そうですね。結構ゲーム寄りに近い書き方をしていると思います。Canvas に requestAnimationFrame で描画しているので、本当にゲームみたいな感じです。

冬鏡: カードの中には、静止画ではなく動画を表示するものもありますよね。

加古: 動画は video 要素を Canvas にそのまま描画できます。video 要素を autoplay と loop にしておいて、画像を描画するときと同じように描けばできます。ただ、裏で autoplay した状態の video 要素を持っておく必要があるので、その管理はしています。

資料: バインダー機能の主要モジュール

約3000行規模のドメインロジックを、移動・衝突判定・ジェスチャーなどの役割で整理した。

バインダー機能のドメインロジックは約3000行規模で、主なモジュールは次のように整理されました。

  • base.ts: メイン composable、イベントハンドリング
  • collision.ts: 衝突判定
  • move.ts: アイテム移動、スケール、回転
  • gesture.ts: ピンチズーム認識
  • sprites.ts: UI ボタンスプライト管理
  • draw.ts: Canvas 描画ユーティリティ
  • stage.ts: ステージサイズ計算

通常の DOM ベースの UI では、ボタンやフォームの基本的な振る舞いをブラウザに任せられます。Canvas の場合は、指を動かしたときに何が起きるべきかをアプリケーション側で定義する必要があります。

回転、移動、衝突判定を自前で扱う

冬鏡: base.ts がかなり重めで、他にも衝突判定やアイテム移動、回転のモジュールが大きいですよね。そこは苦労しましたか。

加古: Canvas を使っているので、ボタンを押したら何が起こるか、指を動かしたらどう動くか、全部自分で書かないといけません。

たとえば、ある点からある点に指を動かしたとき、どう動くのが自然なのかをずっと考えていました。回転なら、指が動いたときに何度回転するのが正解なのか。中心からの距離を測って、角度を測って、それを数式に起こして実装する必要があります。

冬鏡: 線形代数や三角関数を使ったと言っていましたね。

加古: 回転は三角関数系ですね。Canvas は XY 座標なので、回転系の座標は三角関数で変換します。衝突判定も大変でした。ただ、アイテムは全部長方形なので、凸多角形であることが保証されています。そこは少し楽になる点でした。

冬鏡: 計算量も気にしないといけない。

加古: 重い処理をするとすぐ処理落ちします。なので、ループの回数には気を使っています。forEach や map のような書き方がきれいなこともありますが、それだと使いにくいアルゴリズムもあります。速さを重視して for 文を使うこともあります。

資料: Canvas上の「自然な操作」は数式でできている

自然に見える操作も、内部では移動量・回転角・衝突判定などの計算で成り立っている。

ユーザーから見ると、カードをつまんで動かしたり、回転させたりするだけです。しかし内部では、次のような処理が走ります。

  • 指の移動量から、アイテムの移動量を計算する。
  • 中心点と指の位置から、回転角を計算する。
  • 拡大縮小の中心と倍率を計算する。
  • 回転した長方形同士が重なっているか判定する。
  • 描画ループ内で処理が重くならないようにする。

見た目の自然さは、座標計算とパフォーマンス設計の積み重ねです。

ピンチ操作と複数ポインターの管理

冬鏡: ピンチやドラッグのイベント管理はどうでしたか。昔触ったときは、認識が安定しなくてうまく拡大できなかった記憶があります。

加古: ちゃんと実装しようと思うと大変です。たとえば、人間は2本の指を完全に同時には置けません。1本目を置いてから、少し遅れて2本目が置かれます。そういうときの処理を考えないといけません。

あとは、2本指で拡大中にもう1本指を置いたらどうするのか。急に3本目の指を置いたらどうするのか。そういうケースをちゃんと考えないと、バグを引き起こしやすいです。

資料: タッチ操作で起きるエッジケース

ピンチやドラッグでは、指の増減や状態遷移を考慮しないと意図しない挙動につながる。

  • 2本指が完全同時には置かれない。
  • ピンチ中に片方の指だけが離れる。
  • ピンチ中に3本目の指が置かれる。
  • ドラッグからピンチへ、ピンチからドラッグへ状態が変わる。
  • 操作中のアイテムと他アイテムの衝突状態が変わる。

こうした途中状態を曖昧に扱うと、アイテムが急に飛ぶ、拡大率が壊れる、意図しない回転が起きる、といった問題につながります。

WeakMapで画像管理を切り離す

冬鏡: WeakMap でキャッシュしている部分もありました。これはどういう意図ですか。

加古: あるアイテムのオブジェクトに対する画像データは、画像を描画する処理のところでしか使いません。なので、オブジェクトに WeakMap で画像を結びつけておくと管理が楽です。

オブジェクトがなくなったら、WeakMap ならメモリも自動で解放されます。画像描画の部分を切り離せるのが便利でした。

冬鏡: 逆に、思ったより楽だった部分はありましたか。

加古: Canvas の描画関数は結構充実していました。回転も関数でできますし、video を直接描くのも、画像を引数に与えるところに video を入れればそのまま動きます。そのあたりは Canvas API が充実していると思います。

実装していて面白かったところ

冬鏡: 実装していて楽しかったところや、気に入っているところはありますか。

加古: メインの部分が一番面白かったんじゃないですかね。バインダー機能にはランキングなどのサブ機能もありますが、ランキングは API から返ってくるものをそのまま表示するだけに近いです。

一方で、カードを配置していくメインの部分は、自分が作ったものがそのままプレイヤー体験につながります。大変ではありましたが、やればできあがるものなので、そこは面白かったです。

冬鏡: バックエンドとは違う、フロントエンドの作り込みの余地がある場所ですね。

加古: そうですね。メイン部分の実装はほぼ僕がやっていました。

横展開しやすいUIにする

加古: バインダー機能には、まだ話していないアピールポイントがあります。

ORICAL の課題として、パートナーへの横展開が大変という問題があります。

パートナーごとにやりたいことが出てくるので、個別対応が増えやすいんです。そこでバインダー機能では、できる限りそれを少なくするために、UI をすごくシンプルにしました。

冬鏡: パートナーごとのカスタム余地を減らして、横展開しやすくした。

加古: そうですね。個性はアイテムで出してもらうことにして、UI 自体はシンプルに抑えました。実際、そのおかげでバインダー機能はすぐ他のパートナーに広がったと思います。

ロビー画面も、背景1枚とボタンだけです。ボタンもパートナーごとに作っているわけではなく、パートナーのプライマリーカラーごとに決めています。新規パートナーが増えても、ほぼ自動的に決められるようにしています。

資料: 横展開を前提にしたUI設計

パートナーごとに作り込まず、共通UIと色差分で横展開できる設計にした。

バインダー機能では、パートナーごとに画面を作り込むのではなく、共通 UI と色差分で展開できるようにしました。

  • パートナー固有の個性はカードやアイテムで表現する。
  • バインダー本体の UI はできるだけシンプルにする。
  • ロビー画面は背景とボタンを中心に構成する。
  • ボタン色はパートナー単位ではなく、プライマリーカラー単位で決める。

これは、デザインを簡素にしたというだけではありません。複数パートナーへ低コストで展開するためのプロダクト設計です。

目的を理解して作る

冬鏡: 仕様を書くところから運用まで、いつもより早い段階から関わってみてどうでしたか。

加古: やっぱり、物を作るにあたって、それを作る目的が分かっているとだいぶやりやすいです。目的が分かっていないと、言われたものをただ作るだけになってしまいます。ちゃんと理解して、その上で提案できたらさらにいいと思います。

グリッド配置の仕様を最初に作ったときも、なぜグリッドにしようと思ったかというと、自由度が高すぎたらユーザーが大変じゃないか、という考えがありました。ORICAL は野球プロダクトでもあるので、スマホにすごく慣れている人ばかりではないかもしれない。そういうユーザー像を考えることも大事です。

冬鏡: 作りながらユーザー像を理解していく方が、ただ資料などを見て勉強するより分かりやすい。

加古: そうですね。実感を伴って理解できるというか、心で理解できる感じがあります。

これから入るフロントエンド開発者に期待すること

冬鏡: これからフロントエンドにジョインされる方に期待したいことはありますか。

加古: ありきたりな答えかもしれませんが、言われたことをやるだけではなく、自分で考えられる人だといいと思います。

「この仕様で本当にいいんですか」とか、「これを実装するとコード管理が大変になりそうです」といったところに気づけることです。ORICAL は管理が大変なプロダクトなので、自分でメリット・デメリットを考えられるとありがたいです。

冬鏡: やることの種類も多様ですし、いろいろな事情がある中で、自立して動いてくれる人がありがたいということですね。

加古: そうですね。

おわりに

バインダー機能は、Canvas を使ったゲーム的な実装が目立つ機能です。衝突判定、回転、ピンチ操作、描画キャッシュなど、技術的にも面白い要素が多くあります。

ただし今回のインタビューで印象的だったのは、技術的な工夫が単独で存在しているわけではないことでした。

自由配置にするからこそ、衝突判定やジェスチャー実装が必要になる。横展開を重視するからこそ、UI をシンプルにする。目的を理解しているからこそ、実装者が仕様にも踏み込んで判断できる。

HEARTBEATS では、こうした形でプロダクトの目的と実装を行き来しながら開発しています。フロントエンドの作り込みを通じて、ユーザー体験と事業上の展開しやすさの両方を考える。その一例として、バインダー機能開発の裏側を紹介しました。

HEARTBEATSでは開発系職種も積極採用中です。この記事に書かれているような課題に興味を持った方、あるいはプロダクトに興味を持った方がいたら、ぜひ気軽にご応募ください!

RubyKaigi 2026に参加してきました!

こんにちは。リードエンジニアの冬鏡(@nkowne63)です。 2026年4月22日から24日にかけて函館で開催された「 RubyKaigi 2026 」に参加してきました!

会場ボード

rubykaigi.org

弊社では、バックエンドの基盤として Ruby on Rails を採用しており、知見の収集と交流を兼ねて、会社の取り組みとして私を含む2人が初めて RubyKaigi に参加しました。

この記事では、会場の様子と印象に残ったセッションを1日1つずつ紹介します。

Day 1

今回の会場は函館で行われました。私たちは函館駅前に宿泊していたのですが、なんと運営の方が路面電車を貸し切ってくださったため、スムーズに会場まで移動することができました!ありがとうございます。

広々とした会場でした

会場は函館 湯の川温泉付近にある「函館サーモン・まるなまホール」でした。3つのトラックに分かれて講演が行われていくのですが、会場間が離れており、移動に苦労しました。

drive.google.com

一日目に最も印象に残ったセッションは「Digits, Digits, and Digits」でした。任意精度演算のパフォーマンスを劇的に改善するというもので、もともと億年オーダーかかると推定されていた計算時間を現実的な時間まで改善するまでの過程が非常にわかりやすく提示されており、聞いていて楽しかったです。

Day 2

TracePoint API でフックを行い、行間に処理を差し込む。あまりにも法外な不変量の破り方。

Day 2で最も印象に残ったのは「Invariants in my own Ruby: some things must never change」でした。Rustlang 製 Ruby 実装 である monoruby の開発を通じて見えてきた JIT コンパイラに関する知見でしたが、 JIT コンパイラが使っている不変量が Ruby では扱いが難しいという話でした。Ruby は書き味の良さを実現するために言語処理系で多大な労力を払っているというのは聞いていましたが、このレベルの大変さは見えていなかったので驚きました。

Drink upに向かう道中であまりにもきれいなので写真を撮ってしまいました。

夜は mybest さんと TOKIUM さんの主催する Drink up にお邪魔させていただきました。道中のレンガ倉庫のたたずまいが非常に美しく、また、おいしい料理をいただきながら Rubist の皆さんと交流ができました。ありがとうございます。 RubyKaigi ではこのようなイベントが毎年多数行われているようですので、参加を検討している方はこのようなイベントも見てみるとより楽しめると思います!

Day 3

docs.google.com

3日目の講演で印象に残ったのは NES (ファミコン)で Ruby を動かすという話でした。ファミコンは メモリが 2kb しかないようですが、そのうえでも Ruby (正確にはそのバイトコード)のサブセットが動くのは驚きですね。 この他にも Ruby を組み込みなど過酷な環境で動かす講演はいくつかあったのですが、みなさんのその熱意はどこから来るのでしょうか……ただただ頭が上がりません。

また、最後には Ruby の生みの親である Matz さんの講演もありました! 新しい Ruby AOT Compiler である Spinel を Claude code とともに作るといった、非常に興味深い話をされていらっしゃいました。

終わりに

いかがでしたでしょうか? RubyKaigi は言語処理系の話が多く、業務に直接活かせる話は少なかったですが、ここで上げた以外にも、いくつか非常に興味深い講演が多く、大変面白く聞かせていただきました!登壇者の皆様、そして運営とスポンサーの皆様、ありがとうございます。

弊社ではバックエンドに Ruby on Rails を採用しており、現在バックエンドのコードベースのリファクタリングやパフォーマンスチューニングを行っています。また、今回に限らず会社の取り組みとしてこのような勉強会への参加を積極的に行っております。興味のある方はぜひご覧ください。

heartbeats-inc.co.jp

HEARTBEATSの開発のこれまでとこれから

はじめに

HEARTBEATS CTOの宮澤です。

HEARTBEATS では、「世界を鳴らす心臓であれ。」を理念に、プロ野球を始めとしたプロスポーツチームや芸能事務所などと提携し、スポーツ選手やアイドルの皆さんなどの写真や動画を使った電子トレカ®コレクションサービス ORICAL® を開発・提供しています。

...と僕は文字通り何百回も面接などで皆さんにお話してきました。

でも、実際のところプロダクトに最初から強い興味を持ってくださるエンジニアの方はほぼいません。皆さんふーんという感じです。
正直、僕も逆の立場だったら同じだと思います。

それよりも知りたいのは、これまでにどのような意思決定がなされて今の開発体制に至ったのかや、どのような課題があり、それをどう解決したいかだと思います。

でも、残念ながら HP やブログを見てもそういった内容は何一つ書かれていない。これでは数多あるスタートアップの中からあえてうちを選ぼうとはならないでしょう。

ありがたいことに、ORICAL® は 2020/6 に最初のリリースをしてから6年近くが経ちました。これだけ長期で同じプロダクトを開発していれば、当初は思いもよらなかったような様々な課題に直面することになります。

このブログでは、これまでの ORICAL® の開発を振り返りつつ、HEARTBEATS の開発チームが今取り組みたい課題について、読者の皆さんにシェアしたいと思います。

ORICAL® 開発前夜

HEARTBEATS では ORICAL® の開発に着手する前、2019年まで whooop! という電子トレカプラットフォームを開発・提供していました。
Web 上で様々なスポーツチームの選手などのトレカが買える EC サイトのようなサービスで、フロントエンドは JavaScript / Vue.js / Nuxt, バックエンドは Go で実装していました。

HEARTBEATS は学生起業のスタートアップであり、東大の五月祭・駒場祭の関係者*1が数人、創業当時から参画していました。
学園祭では Web サイトの構築に JavaScript / Vue.js を使用しており、慣れ親しんだ技術スタックが良いという理由で、フロントエンドについては順当に決まったと思います。

一方で、バックエンドは当時流行っており、使ってみたかったからという理由で Go に決まりました。結果として悪くはない選択だったと思いますが、初期の技術選定という観点ではかなり適当な意思決定だったと思います。
ただ、MVP*2をこれから作るぞというスタートアップの技術選定はどこもこんな感じだと思いますし、その例に弊社も漏れなかっただけとも言えるでしょう。

その結果、何が起きたか。

当たり前のことですが、ほぼ全員が Go の初心者だったので、学習コストの問題から開発速度がそこまで上がりませんでした。また、 Go で Web アプリケーションを作る際のベストプラクティスを把握できていなかったため、設計上の問題(テストが難しいなど)もすぐに表面化しました。

Go はパフォーマンスや可搬性の面でメリットがありますが、これからMVPを作るぞというときにスケーラビリティやマイクロサービス化を気にしても仕方がありません。
本来は何を作るか、いかに速く仮説検証のサイクルを回すかが重要なのに、どう作るかで時間がかかるという本末転倒の状況になっていました。

これが理由ではありませんでしたが、結局 whooop! は PMF*3できずに撤退、ピボットすることになりました。

ORICAL® の開発へ

この状況を打破すべく、より各スポーツチームや芸能事務所などに特化できる電子トレカサービスを2019年の夏に一から開発することになりました。
これが今日の ORICAL® です。

初期の開発の様子。当時は合宿と称して泊まりで皆で開発することもありました。

当然、フルスクラッチからの開発なので、技術選定も再度実施することになりました。

まず、フロントエンドは TypeScript / Vue.js (2系) / Nuxt (2系) で開発することになりました。JavaScript から TypeScript にしたのは、 whooop! を開発する過程で型安全性が開発速度に効くことが分かったからです。
とはいえ、フロントエンド開発はほぼ Vue にするか React にするかだと思うので、ここで大きく主流から外れたり、地雷を踏んだりすることは無いでしょう。

問題はバックエンドです。Go で皆苦労したので、Go ではなくより学習コストが低く開発速度を上げやすい技術スタックにしたいという点では皆一致していましたが、どれか一つに決めるとなったときの決め手に欠けていました。

結局、当時業務委託で入ってもらっていた大学の先輩の、「スタートアップなら Rails っしょ、うちの会社でも使ってるよ」の鶴の一声で Ruby on Rails を使うことに決まりました。

え、そんなノリで安易に技術スタックを決めていいの?と皆さん思われると思います。でも、当時は PMF もしていない、学生社員を入れても10人もいないような開発組織です。そもそも ORICAL® が今後何年続いていくかもわからないのに、スケーラビリティやマイクロサービス化といった要素について真剣に考えるメンバーは誰もいませんでした。

ちなみに、インフラに関しては元々 whooop! では GCP を使っていたのですが*4、これも先ほどの先輩の「うちの会社は AWS 使ってるよ、あと Elastic Beanstalk を使うとランタイムの設定楽でいいよ」の一言でこれに決まりました。

ORICAL® のリリースと成長

ORICAL® の最初のパートナー*5はプロ野球の埼玉西武ライオンズさんに決まりました。そのため、2020年3月のプロ野球開幕にあわせて開発を急ピッチで進めていました。

...とここで勘の良い皆さんはお気づきかもしれませんが、この年はまさに新型コロナウイルスのパンデミックが起きた年です。結局、プロ野球の開幕は3ヶ月遅れ、更に無観客試合という未曾有の状況で幕を開けることになりました。

逆にこの状況が事業面では対面を必要としないデジタルサービスへの注目という点で、開発面では予定通り3月であれば実装が間に合わなかったという点で奏功し、ORICAL®  はトラブル無くリリースされ、順調に成長していきました。

ORICAL® の広告が中継に出ているところを皆で写真に撮っている様子

2020年には1パートナーからスタートした ORICAL® ですが、2021年には3パートナー、2022年には5パートナー、2023年には9パートナー、2024年には14パートナー、2025年には19パートナー、そして2026年3月時点では23パートナーと、まさに飛ぶ鳥を落とす勢いで成長を遂げました。

また、開発組織もリリース時は学生社員を入れて数名だった規模が、現時点では正社員だけで約20名、業務委託や学生インターンを入れると30人強という規模まで大きくなりました。

ORICAL® の開発が抱える課題

このように最初は PMF するかもわからない状態で開発を進めていた ORICAL® ですが、結果として圧倒的な PMF を遂げ、数十万の日次アクティブユーザと数十億円の年商を誇るプロダクトにまで成長しました。

こうなると、開発当初は問題にならなかった課題や、開発が長期に渡るからこそ出てきた課題、想定していなかった課題など、様々な課題が出てきます。

フロントエンドの課題

フロントエンドの課題として最初に表面化したのは、デプロイ作業の煩雑さでした。

先ほども述べたように、インフラはフロントエンド・バックエンドともに Elastic Beanstalk で稼働していました。しかし、パートナーごと、本番/ステージング/開発環境ごとに Elastic Beanstalk の環境が立ち、2024年ごろには少なくとも 30 環境程度を手動で管理する状態になっていました。

こうなると、コードの更新やリリースに伴うデプロイが非常に煩雑になってきます。さらに、 フロントエンドの Nuxt の Server Side Rendering をほぼ活用できておらず、効果に見合わないサーバー代がかかっていることが判明しました。このため、SSRを廃止して静的な配信のみの構成に移行しました。この取り組みの詳細は、 Webフロントエンドで脱SSRして料金を6割節約した話 や 脱SSRによる6割料金削減の裏側 - 株式会社HEARTBEATSエンジニアブログ をご覧ください。

また、当初の予定をはるかに超えて ORICAL® が成功したことによる他の課題も発生しました。

一つ目は、多数の要望の異なるパートナーを単一のコードベースで扱うことによる複雑さが無視できないコストになりつつあるという点です。

現在の ORICAL® のフロントエンドは単一のコードベースであるのにも関わらず、様々な部分で各パートナー固有のコードやコンポーネント、バックエンドの設定に応じたフラグがあり、品質保証のコストやコードの複雑さをじわじわと押し上げています。

二つ目は、機能追加の要望が想定以上のペースであり、それに優先的に対処した結果、コード品質以外の非機能要件の実現に対して問題が出るようになったことです。

代表的な問題として、現在の ORICAL® フロントエンドのバンドルサイズは単調増加しており、現在はトランスパイル後のJSでも 10 MB を超えていることや、対症療法しか手を打つことができていないメモリリークの問題があります。
さらに、テストのカバー範囲が狭く、手動での品質保証に頼った結果、影響範囲の広いアップデートを行うことが構造的に難しくなり、 EOL を迎えているにも関わらず、 Nuxt3 への移行を行うことができていないという問題があります。なお、この問題については現在対応を進めており、また別の機会に記事を書く予定です。

バックエンドの課題

バックエンドには大きく3つの課題があります。これらの課題は順次改善を進めていますが、より速度を上げて対応する必要があります。

1つ目は、初期に明確な設計指針を持たずに開発を進めた結果、コントローラーやモデルが肥大化し、コードの見通しが悪くなっている点です。

例えば、コントローラーは基本的にはデフォルトのアクションで表現できる範囲でのみ使用し、それに収まらないものは別のクラスに分割することで見通しが良くなります(参考)。しかし、ORICAL® のコントローラーではそのようにはなっておらず、一つのコントローラーに互いに関連の薄いエンドポイントがまとめて実装されており、各コントローラーの責務があいまいになってしまっています。

また、モデルにおいても concern の活用やクラス分割が十分にできておらず、肥大化したモデル定義によりコードの可読性に影響を与えています。加えて、モデルではコールバックを多用しているものが一部存在するため、処理の見通しが悪くなっており、結果として意図しない副作用を起こしうるという点で改善が必要です。

2つ目は、(特に開発初期の)効率の悪い実装になっているところがまだ修正されず残っている部分がある点です。

幸いなことに、N+1 やインデックスの最適化といった基本的な問題は当初から気を配って開発を進めてきたため、そこまで問題にはなっていません。しかし、Rails を使用する際には、良い意味でも悪い意味でも Active Record (ORM) を使う際に実際にどのタイミングでどのようなクエリが発行されるのかを意識しなくなりがちです。
また、仮に適切なインデックスを設けるなどの対策を取っても、レコード数やリクエスト数が多い場合にはクエリキャッシュなどの他のパフォーマンス対策を取る必要がある場合もあります。

APMなどを活用しながら、これらのパフォーマンス改善を一層進めていく必要があります。

3つ目は、テストの適切な分割ができておらず、一つ一つのテストケースが肥大化している点です。

テストカバー率こそ高いものの、現状ではリクエストテストに本来モデルテスト等の他のテストで確認すべき内容が詰め込まれており、テストケースが肥大化しています。そのため、特定のメソッドを変更した場合に対応するテストが分かりづらくなってしまい、AIエージェントを使用した開発をする際にも使いづらいものとなってしまっています。

各テストの責務をシンプルかつ明確にするため、テストケースの分割や整理を行う必要があります。

インフラの課題

インフラはCI/CDも整備されており、古いランタイムやアーキテクチャで塩漬けとなっていたりすることもないため、他の領域と比べると問題はそこまで大きくありません。

ただし、バックエンドでは Elastic Beanstalk を使い続けているなど、規模が大きくなり環境数が多くなっている現在では管理コストが課題となっている部分があります。
先行してフロントエンドでは Terraform による IaC 化が完了しており、バックエンドでも IaC 化と ECS への移行を進めていますが、各メンバーの片手間で実施しているのが現状です。専任の担当者を置いて、継続的に対応を行う必要があります。

開発組織の課題

HEARTBEATS の開発組織は当初から職能別*6に分かれていました。
組織規模が小さければ、職能別に分かれていたとしてもお互い全員と常にやりとりするので、連絡漏れや認識ずれといった問題は基本的に生じません。むしろ、職能別に分かれていることでお互いの技術面での強みを生かして開発速度を向上させることができるというメリットもありました。

当時から7年ほど経ち、現在は各チームそれぞれ10人程度の規模となりました。こうなると、当然同じ機能を全員で開発するわけではなく、同時並行で複数の機能を少人数で開発することになります。
しかし、組織構成は以前の職能別組織のままであるため、同じ機能の開発スケジュール管理が各チームで行われていたり、毎回同じメンバーで開発しているわけではないため、担当者間で十分に設計について議論ができていないケースで手戻りが発生したりと、いくつかの課題が出てきています。

今後は職能別ではなく、技術領域を横断した少人数のチームをつくり、同じメンバー同士で機能ごとに開発を担当する機能別組織に移行したいと考えています。とはいえ、単にチームを再編成すれば良いわけではなく、各エンジニアには技術的な強みに加えてプロダクトに関する理解が一層求められることになるので*7、その点も皆に意識してもらいながら移行を進める必要があります。

また、スタートアップならではの課題として、一度実装した機能の廃止を含めた見直しや、機能開発と並行した非機能改善の取り組みが思ったようには進まないという点もあります。

機能は増やせば増やすほど維持するのにもコストがかかるという点は意外に見過ごされがちです。スタートアップで使えるリソースが限られている中で、一度開発した機能を廃止するという決断は他の企業と比べて確かに難しいですが、ユーザからの声も踏まえながら、時には維持するコストに見合わない機能は廃止するという決断をしっかり下す必要があると感じています。

また、必ずしも機能開発と両立しないわけではありませんが、非機能改善が十分でない状況では機能開発にいくらリソースをかけたとしても期待されるだけの効果は出ません。スタートアップでは一度立ち止まって足元を固めるという決断をするのは難しいものですが、これだけ大きくなったからこそ、今後も持続可能な開発体制にしていくためにこうした点にも時間をかけて取り組む必要があります。

おわりに

これまでの HEARTBEATS では、個別の技術的なトピックや課題に焦点をあてた記事はありましたが、これまでの開発の経緯を振り返り、開発組織の課題についてオープンにした記事はありませんでした。

HEARTBEATS は当初学生数名から始まった組織が幸運にも PMF したことで、今や 100 名規模の組織の企業となりました。
もちろん、今回ブログに記した経緯や課題は企業やフェーズによって様々異なるものです。しかし、中には共感していただける方もいると思いますし、これから同じような経験をされる方もいると思います。
この記事を読んで、面白い、参考になったと思っていただけたらうれしいです。

最後に、HEARTBEATS では開発系職種も積極採用中です。この記事に書かれているような課題に興味を持った方、あるいはプロダクトに興味を持った方がいたら、ぜひ気軽にご応募ください。
選考はもちろん僕も担当するので、気になることがあれば直接聞いてもらえればと思います!

*1:こうした経緯があり、今でも現役の学園祭委員のインターンが多数在籍しています

*2:Minimum Viable Product、顧客に価値を提供するための最低限の製品のこと

*3:Product Market Fit、提供する製品が、適切な市場において顧客のニーズを満たし、受け入れられている状態のこと

*4:2017-18年当時としては間違いなくニッチでした

*5:ORICAL® のクライアント企業のこと

*6:フロントエンド・バックエンドなど、担当領域ごとにチームを分けること

*7:AI全盛期の現在、むしろプロダクト理解こそが人間にとって最も重要なのではないかという気がします

写真で振り返る React Tokyo フェス 2026

React Tokyo フェスとは

2026年2月28日に開催されたReact Tokyo フェス2026に参加してきました。
開催主体のReact Tokyoは東京を拠点として活動しているReactコミュニティで、今回のフェスはReact Tokyo初の大型イベントだったようです。
今回のフェスはポスターセッションが主なコンテンツであり、来場者が会場を回遊しながら交流を楽しむことができる、まさに”お祭り”型のイベントとして開催されました。

今回はポスター発表者・運営スタッフとして参加してきましたので、写真で振り返っていきたいと思います。

当日の様子

会場のアクセス

会場は東京都立産業貿易センター浜松町館の5階にあるイベントスペースでした。JR浜松町駅にほど近く、アクセス良好でした。ただビルの中のエレベーターの系統が少し複雑で、迷った方もいたようです。私は運営スタッフの設営で朝早くに行きましたので、それらしい人に付いていったら無事にたどり着けました。

開始前

今回は初めて運営スタッフとしてイベントに参加しました!朝8時に集合してスタッフ用の黄色いTシャツを受け取り、まっさらな会場にポスターやテーブル、椅子などの設営を行いました。

ポスターの様子

ポスター設営の様子。会場の3辺をぐるりと囲むように40枚のポスターが並び、壮観でした。展示したポスターは入口から7番目で、なかなかの好立地。

展示したポスター

今回はReact Native Japanの方々と共同で、React Nativeを紹介するポスター発表を行いました!Reactは普段使っているけれど、React Nativeは知らなかった・難しそうで手が出ていなかったという人や、自分でモバイルアプリを作ってみたいけれど、どの言語を使えばいいのかわからない...という人が多かった印象です。意外と簡単にモバイルアプリを作れることが伝わっていたら嬉しいです。
また、各ポスターの横にコメントボードと付箋、リアクションステッカーが配置されていました。ポスターを見に来てくれた方が付箋でコメントを書いてくれたり、自分が他のポスターを見に行った際には良いと思ったところにステッカーを貼ったりすることができ、参加者同士の交流にとても役立っていたと思います。

気になったポスター 1 縦書き対応のコンポーネントを作る

自分が今まで実装してきた / 利用してきたウェブサイトやアプリは横書きのものばかりだったので、縦書きに対応したCSSのプロパティがあることすら知らず、とても勉強になりました。

気になったポスター 2 React 日本語ドキュメントサイト小ネタ集

Reactの公式ドキュメントを日本語訳されている方のポスターで、翻訳作業における苦労やこだわりなどをお伺いすることができました。最近はAIがある程度精度の良い翻訳をしてくれるようになり、だいぶ手間は減っているようです。
React Nativeには現状英語のドキュメントしかないため、日本語訳があれば日本におけるReact Nativeの裾野も広がるだろうに...と思うこともありますが、翻訳とメンテナンスの大変さを思うと簡単なことではないなと感じました。

ライブコーディングバトル

午後の中会場ではライブコーディングバトルが開催されていました。(大会場のスクリーンにも放映されていました。)4人の参加者がAIを最大限活用してX風の文章投稿プラットフォームをバイブコーディングで作成し、スピードとコード品質で得点を競うという内容で、開発要件に対するアプローチやAIへのプロンプティング、AIが話す語尾にまで参加者の個性が現れており、見ごたえがありました。自分はポスター発表の都合などもあり全て見ることはできなかったのですが、参加者の後ろの椅子からかぶりつきで観戦している人もおり、とても盛り上がっていました。

会場の様子

connpassへの参加登録者数は500人だったようです。会場内には椅子も多くあり、ケータリングを食べたりメインスクリーンで放映されているライブコーディングバトルを観たりしながら休憩することもできました。

ナイトフェス

運営スタッフ枠でナイトフェスにも参加しました。デイフェス中にあまり話せなかったスポンサー企業の方々、他のポスター発表者の方々との交流を楽しむことができました!

まとめ

React Tokyo フェス2026は、来場者が他の参加者と主体的に交流できるようなプログラムが豊富で、まさに誰もがお祭りのように楽しめるイベントでした。 来場者の属性も非エンジニアから経験者まで様々で、ポスター発表者として来場者の方とお話ししても、自分が会場を回遊しても飽きることはありませんでした。 また、今回運営スタッフとして参加したことでカンファレンス運営の裏側を知ることができたのも良い体験になりました。 機会があればまた参加したいと思います!

burikaigi 2026に出席してきました!

エンジニアチームの冬鏡です。1/9-10に富山国際会議場で開かれたburikaigiに出席してきました!

burikaigiの概要と当日の様子

10年以上の歴史がある富山の勉強会です。もともとは.NETとJavaの勉強会だったようですが、規模を拡大するとともに現在は幅広い話題を取り扱っています。

今回は会社のバックエンドエンジニア2人とともに3人で参加しました。

当日は東京の自宅から出発したので、まさかの日の出前からの移動となりました。数年ぶりにここまで早起きをしました……

富山では幸い雪は降っていませんでした。知り合いなどから聞いた情報によると前日と翌日に大雪が降ったようですので、参加する場合は雪の対策をしていくとよいでしょう。

 

会場に入るとburikaigiの旗がお出迎えしてくれます。躍動感のある鰤が期待感を高めてくれます。

 

会場は富山国際会議場の2階でした。富山駅から歩いて10分ほどの場所で、富山城が目の前にあります。1Fでは2日目に成人式が開かれたりと、地元の方にもよく利用されている横での開催となりました。

基本的にはこの2階で並んでいる4部屋で講演を聴いていくことになりますが、企業ブースがあったりask the speakerがあったり、ハンズオンなどが同じ階の別の部屋で開催されています。少し大きめの教室くらいの4部屋なので、カンファレンスなどであるあるの歩き疲れなどはあまり気にならなかったのが良かったです。

昼食には2日を通して駅弁がふるまわれました。スポンサーの皆様ありがとうございます。私たちは時間の都合で参加できませんでしたが、2日目の最後のセッションの後には懇親会で鰤を食べる会もあるため、気になる方は参加してみるとよいでしょう!

興味深かったセッション

特にカテゴリ分けはされていませんでしたが、体感バックエンド5, フロントエンド3, 残り2くらいの構成でした。私はバックエンドエンジニアの2人に他の講演を任せてフロントエンドの話を主に聴いていましたが、かなりニッチなものが多く、フロントエンドエンジニアとしても学びが多い2日間でした。

今回は、セッションの中でも特に印象が残ったものについて振り返っていきます。

基調講演: 2026年のソフトウェアエンジニアリング

claude codeで「t-wadaによるTDDを行って」とプロンプトすると精度が上がるくらい知られている和田氏による基調講演です。2026年一発目の講演ということもあって、2025年を振り返り、向こう1年を展望するような内容でした。

AIによるagentic codingを普段から行っている皆さんが体感する通り、生成されたコードがスケールするにしたがって、ボトルネックが品質保証に移動しており、スケーラブルな品質保証のためにテストの重要性が高まっているということが説かれていました。また、品質保証のために現状のエンジニアが確認すべき点、レバレッジを効かせられる点についても話がありました。

個人的に新しい観点だと思ったのは「AIを前提としたプラクティスの再考」です。いやまあ当たり前なのですが、私はこの部分の理解が甘かったということに気づかされました。特に「AIの理解力は今の人間と大差ないが、変更/更新能力は大幅に上がっているため、多少コード量が増えても理解可能性に寄せたプラクティスの価値が相対的に上がるのでは」という指摘は目から鱗でした。

最後によくある「AIが進歩した世界で学習をする意味」についても「LLMの能力は鍵付き」という標語が大変よくまとまっていると感じました。これは、LLMは潜在的には人間をはるかに超える能力を言語的に持っているが、適切にプロンプトしないとそれらを発揮しないので、適切なプロンプトとコンテキストが必要という状況で、これは事前学習と訓練の方式からしてしばらくは継続すると考えられています。

政治資金の可視化やDXについて

政党と政治家の具体名は出ませんでしたが、ある政党の党首が前にやっていたプロジェクトのデジタル民主主義の話をされていました。特に、このセッションのメインテーマである「政治資金収支報告書」や「選挙運動費用収支報告書」について、県、果ては自治体ごとに紙でフォーマットがばらばらであり、ノウハウを誰も共有しないということについて嘆かれていました。大変ですね……

OSSとして、政治資金を可視化するpolimoneyの紹介がありました。また、最後にブロードリスニングについても少し紹介がありました。

電話APIとAIの話

技術的には電話API SaaSと各種AIプロバイダーのリアルタイム音声モデルを組み合わせてAIに電話に出させる、というだけなのですが、とにかくプレゼンが魅力的でした。

日常的にLLMをしばいてagentic codingをしているエンジニアとしても、一般的な電話口からAIが意思をもってリアルタイムに返答してくるというのはなかなか新鮮な体験でした。こういう面白そうな道具を紹介されると用途がなくても何かつくってみたくなりますね……!

ライブコーディングバトル

一番最後のセッションは「ライブコーディングバトル」と称して、4人のスタッフがライブコーディング(+事前の提出コード)で競い合うというものでした。名前だけ見てあまり期待していなかったのですが、これがかなり面白かったです。

最初は文字列置き換えを3通りで実装したり、乱数シードの探索を行ったりですが、一番最後の「ゲームコードを書いて、自分のコードを勝たせよ」というのが特に面白かったです。ゲームシステムに介入して時間を巻き戻したり、人間に見える画面を上書きして見かけ上の勝利をつかみ取ったり、創意工夫が大変素敵な会でした。エンジニアとしては、テストを創意工夫で突破してくるAIのことを思い出して少し複雑な気持ちにもなりますが、このような会を会社でもやってみたいと思えるような内容でした。

会社としての取り組み・参加とりまとめ役として

今回は会社として初めて県外の大規模カンファレンスに複数人で参加し、私はそのとりまとめを行いました。私たちの会社では、聞いたセッションについてレポートと口頭での報告を行う代わりに、参加費と交通費、宿泊費(そして参加中の出勤扱い)などを出してもらうという交渉を行い、それに基づいてすでにレポートと報告をしています。

今回の取り組みで良かったのはSlackにおけるイベント専用チャンネルの設立です。メモや連絡、文脈の集約としてイベント専用チャンネルは非常に役に立ちました。これは今後も続けていきたいと思います。

一方で、手続きは不慣れだったため、CTOにだいぶ手続きを問い合わせました…ここらへんは次回もっとスムーズに行えるでしょう。また、事前の参加の集約についても、想定以上のペースで枠が埋まっていってしまい、慌てて会社での参加希望者を締め切ったため、これも反省点です。これらについては次回、定点観測したり、ちゃんとスケジュールを組むことで対処したいと思います。

まとめ

想像以上に濃い2日間を過ごすことができました。また、会社としてもこの大規模カンファレンスに参加するための段取りをかなり蓄積できたので、今後このような技術カンファレンスには積極的に参加していければいいなと思います。私個人としては(今年は博士課程というのもあり難しいですが)登壇なども視野に入れて活動していきたいです。