くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログです

システム刷新という話 第3話

minmaの開発組織の話

突然ですが、

みなさんはサッカーは好きですか?
私は好きです。
観るのも、
やるのも、
どちらも好きです。

週末はつい試合を観てしまいますし、
ワールドカップの年になると、寝不足になります。
そして2026年。 またワールドカップがやってきます。

そんなタイミングもあって、
今回は少し変わった形で、
私たちの話を書いてみようと思いました。

こんにちは。
エンジニアリングマネージャーのユジンです。

技術の話をそのまま説明するのではなく、
サッカーに例えて、
開発組織の話をしてみようと思います。

まず、この図を見てください。
一見するとフォーメーション図ですが、
実はこれは組織図です。
実際に、2026年の組織変更を検討した際に
私が社内共有で使っていた資料そのものです。

今日はその
「ある一試合」の話から始めたいと思います。

実はその試合、
今の開発組織の状況と、驚くほどよく似ていたんです。


第1部

1.キックオフ前

キックオフ10分前。

ナイトゲームのスタジアムは、まだ少しひんやりしていた。
ライトに照らされた芝は薄く湿っていて、踏むたびに小さな音がする。

観客席はゆっくりと埋まり始め、
ざわざわとした声が、波のように広がっていく。

オレンジ色のユニフォームを着た選手たちが、
ピッチの中央に集まった。

今日も、いつも通り。

誰かがストレッチをしている。
誰かがスパイクの紐を結び直している。
誰かは無言で空を見上げている。

やがて全員が肩を組み、スクラムを組む。

今日も、いつも通り。

同じフォーメーション。
同じメンバー。
同じ戦術。

最近の成績は2勝2敗。 悪くはない。 でも、突き抜けてもいない。

"いこう"

短い一言。

それだけで十分だった。

2.前半

ホイッスル。

試合開始。

立ち上がりは悪くなかった。
パスもつながる。
足も軽い。

"今日はいけるかもしれない"
そんな空気が、確かにあった。

でも、10分を過ぎたあたりから、
少しずつ違和感が出てくる。

相手のプレスが速い。
ボールを受けた瞬間、もう寄せられている。

奪われる。
追いかける。
また守る。
気づけば、自陣に押し込まれていた。

攻撃の時間がほとんどない。
ずっと走っているのに、なぜか前に進めない。

前半18分。最初の失点。
ゴールネットが揺れる。

その音だけが、
やけに大きく聞こえた。

一瞬、誰も声を出さない。
"まだ1点だ。"

誰かが手を叩く。
無理やり前を向く。

再開。
でも流れは変わらない。

前半30分、 2失点目!

足が重くなってきた。
呼吸が荒い。
それでも、まだ心は折れていなかった。

前半終了間際。
やっと訪れたチャンス。
左サイドを抜け出し、クロス。
ダイレクトシュート。
ゴールキーパーの指先に触れ、ポストをかすめて外れる。

"うわっ…!"

スタジアム全体が息を飲む。
入っていれば、流れは変わったかもしれない。
でも、入らなかった。

それでも、そのワンプレーが妙に希望に見えた。
"いけるかもしれない"

根拠はない。
でも、そう思いたかった。

そして
追加点。
0 : 3

スコアボードの数字が、
冷たく光る。 3点差。

苦しい。
でも、まだ終わった気はしなかった。
"1点返せば、流れは変わる"

誰もが、
そう信じてロッカールームへ向かった。

3.ハーフタイム

汗が引いて、急に寒くなる。
床に座り込み、うつむく選手。
無言で水を飲み続ける選手。

監督がホワイトボードを出す。
"攻撃の枚数、増やす。"
"全員、前に出る。"
"ゴールを取りにいくしかない。"

冷静な分析というより、決断だった。

3点差。

その現実が、判断を急がせた。
リスクがあることは、みんな分かっていた。

でも、他に手が思いつかなかった。

後半

ラインが一気に上がる。
守備のはずの選手も前へ。
ボールを奪ったら、とにかくロングボール。

つなぐ余裕はない。
ただ前へ。
ただ遠くへ。
祈るようなパス。
でも、そのほとんどは相手に跳ね返される。

その瞬間、後ろはがら空き。
サイドバックが全力で戻る。
また戻る。

何度もスプリント。
肺が焼ける。
ボランチは迷子だった。
攻めるのか、
守るのか。

中途半端な位置を走り回るだけ。
フォーメーションなんて、もうない。
ただ混乱だけがあった。

そして失点。

0 : 4

さらに失点。

0 : 5

足をつって倒れる選手。
交代!

入った若手は、まだこのスピードについていけない。
パスがずれる。
タイミングが合わない。
そこを突かれる。

カウンター。

失点。

0 : 6

最後の数分は、誰も覚えていない。
ただ時間が早く過ぎてほしいと思っていた。

ホイッスル。

試合終了。

歓声は相手側だけ。

オレンジの選手たちは、
ただ静かに歩き出す。
誰も目を合わせない。
誰も何も言わない。

しばらくして、
誰かがぽつりと呟いた。

"悪いけど、6点で済んでよかったな。"

その言葉が、
妙に現実的だった。

悔しいというより、
ただ疲れていた。

完全な敗戦だった。

その夜。

スタジアムを出ても、
誰もすぐには帰らなかった。

ロッカールームの前で、
スパイクを脱ぎながら、ただ黙って座っている。

6失点。

完敗。

今日もいつもの通り、
走った。
声も出した。
気持ちも切らさなかった。

それでも勝てなかった。

なぜだろう。

個人の努力が足りなかったのか。
気合が足りなかったのか。

たぶん、
違う。

フォーメーションが崩れていた。
役割が曖昧だった。
守備と攻撃を同時にやろうとして、
どちらも中途半端になっていた。

つまり

"戦い方"そのものが間違っていた。

ここまで読んで、
"なんのサッカー小説だ?"と思った方もいるかもしれません。

でも実は、これはサッカーの話ではありません。

これは、
いま私たちの開発組織で実際に起きていた出来事を、
サッカーに置き換えて書いただけの話です。

技術負債の対応と、
現行サービスの運用と、
新機能の開発を、
同じメンバーで、同時に、全部やろうとしていた。

その結果が

0 : 6

でした。。。


第2部

組織を作り直した理由 ー Conway’s Law と minma の設計思想

前回は、サッカーの試合に例えて
minmaがなぜ押し込まれ続けたのかを書きました。

第2部では、

なぜ負けたのか。
どうチームを組み直したのか。
そして、これからどう戦うのか。

実際に私たちが行った組織設計の話をします。

ここからは、テックブログとして、少し真面目な内容です。

問題の本質は"技術"ではなかった

最初に結論から言います。

当時の課題は、

  • 言語選定
  • フレームワーク
  • クラウド構成
  • CI/CDの自動化

といった技術的な選択ではありませんでした。 もちろん、改善余地はありました。 けれど、それは本質ではなかった。 本質はもっと手前。

市場の変化に対して、組織が追いつけなくなっていたこと。

市場は止まらない

minma は15年以上続くサービスです。

その間に、

  • ユーザーの行動は変わり
  • デバイスは変わり
  • 期待される体験は変わり
  • 競争環境も変わり

市場は常に変化します。

プロダクトも、成長とともに複雑になります。

けれど、組織構造は一度固まると、
簡単には変わりません。

その結果、

市場は動いているのに、
内部の意思決定と開発構造は、
ゆっくりになっていく。

このズレが、徐々に効いてきます

そして気づいたときには、
「押し込まれ続ける試合」になっている。

当時の構造

当時のminmaは、以下を同時に抱えていました。

  • 技術負債の返済
  • 新規機能開発
  • 既存運用
  • 障害対応
  • インフラ改善
  • エンジニア採用・育成

そしてこれらを、

同じメンバーが、同時に、全部やっていた。

つまり、
全員 = 刷新 + 運用 + 障害 + 改善 + 採用 + 育成

一見、効率的に見えます。

全員が全部できる。
柔軟で、強そうに見える。

でも、強度が上がると崩れます。

  • コンテキストスイッチが増える
  • 専門性が育たない
  • 設計が後回しになる
  • 火消しが優先される
  • 技術負債が積み上がる
  • 育成の時間が削られる

特に育成は、
緊急ではないが重要な仕事です。

緊急対応が続く組織では、
真っ先に削られる。

その結果、
未来の戦力が育たない。

努力の問題ではありません。

構造的に、市場適応力が落ちる設計でした。

Conway’s Law

ここで重要なのが、
私がずっと信じている法則です。

Conway’s Law

システムを設計する組織は、
その組織のコミュニケーション構造を反映した設計を生み出してしまう

組織が曖昧であれば、

  • モノリス化
  • 責任不明コード
  • 依存の肥大化
  • 技術負債の蓄積

が自然に起きます。

組織が責任単位で分離されていれば、

  • API境界は明確になり
  • サービスは疎結合になり
  • 変更は容易になり

つまり、

アーキテクチャは市場適応力の鏡であり、
その根本は組織設計にある。

組織がモノリスなら、
どんなマイクロサービスも、やがてモノリスに戻る。

技術刷新ではなく、適応構造への刷新

今回私たちが目指したのは、

一度きりの「技術刷新」ではありません。

市場の変化を読み、
継続的に構造を調整できる組織にすること。

そのための設計原則はシンプルでした。

  • トレードオフしない
  • 役割を混在させない
  • 責任を固定する

新しい組織構造

刷新チーム   → 未来を作る(攻撃)
即応チーム   → 今日を守る(守備)
SRE     → 基盤最適化(制御)
QA      → 品質保証(リスク管理)

ポイントは「役割固定」です。

コンテキスト混在を排除し、
責任境界を明確にする。

これにより、

組織分離 → 責任明確 → サービス分離 → 疎結合化

が自然に起きます。

そしてそれが、
市場の変化に適応できる構造を作ります。

現在地

誤解してほしくないのは、
これまでのやり方が間違いだったわけではない、
ということです。

その時点では最適だった。
けれど、
環境が変われば、最適も変わる。

私たちはまだ完成していません

  • 技術負債は残っています
  • 障害もゼロではありません
  • 課題は山積みです

ただ、

以前は「耐える構造」でした。
今は「適応できる構造」に変わりつつあります。

ここが、大きな違いです。

そして、ここで終わりではありません。

今の組織も、
やがてまた変わります。

いまは最適化されたチーム構造かもしれません。

けれど、市場が変われば、
プロダクトが変われば、
組織の規模が変われば、
最適もまた変わります。

最後に、

もしこの記事を読んで、
「この組織設計、ちょっと面白そうだな」
と思った方。

私たちは、
万能な選手を求めているわけではありません。

ポジションを持ち、
その責任に集中できる人を求めています

minmaでは
入団テスト(採用)を常時開催中です。

フォワードも、
ボランチも、
センターバックも、
サイドバックとGKも、

市場は止まりません。

だから私たちも、止まりません。


次回予告

次回は、

各チームの具体的なミッション

実際の取り組み
連携方法を紹介します。

より実践的な内容になる予定です。

システム刷新という話 第2話

キッチンとホールのあいだで(バックエンドとフロントエンドのあいだで)

こんにちは。エンジニアリングマネージャーのユジンです。

今回は、minma がいま抱えている課題について、 少しお話ししてみたいと思います。

minma では、月に一度、業績共有会があります。
その中で、ときどき「くらしのマーケット」のシステムの状況について、
全社の前で共有する機会があります。

ただ、会場にはエンジニアではない方が、8割ほどいらっしゃいます。
そのため、技術の話をそのまま説明しても、なかなか伝わりにくく、
「ちゃんと伝えられなかったな」と感じることが、何度かありました。

そこで今回は、そのときの反省も踏まえて、
「技術の話」をそのまま伝えるのではなく、
小さな物語として、いま起きている課題を共有してみようと思います。

少しでも、現在 minma が直面している状況や、
その難しさを感じていただければ幸いです。


キッチンとホールのあいだで

看板は控えめ、席も少なく、
ただ「料理がおいしい」という評判だけで、
毎年少しずつ客が増えていった。

一年目、二年目、三年目、十年目。
気づけば、店は毎年のように売上を伸ばし、
新しいメニューが増え、
新しいスタッフが増え、
店は静かに“成長”していた。

だが、成長はいつも、少し遅れて問題を連れてくる。


1. 多すぎる言葉、足りない言葉

ある日の昼下がり。
ひとりの客が、穏やかな声で尋ねた。

"すみません。今日のおすすめは、何ですか?"

ホールスタッフはキッチンに向かい、そう伝えた。

しばらくして、キッチンの奥から、分厚い紙束が差し出された。

そこには、料理名と値段だけでなく、

  • レシピ、
  • 原材料の産地、
  • 原価、
  • 調理の工程、
  • 栄養成分、
  • 仕入先の情報まで、

びっしりと書かれていた。

ホールスタッフは紙をめくりながら、少し困った顔をした。

'お客さんが知りたいのは、
「Minmaハンバーグ、1,200円」だけなんだけどな'

別の日には、逆のことも起きた。

キッチンから返ってきたのは、短い一言だけ。

"Minma-209"(料理IDだけが返ってきた)

'料理の名前は?'
'デザートある?'
'飲みものある?'

ホールとキッチンを、何度も何度も往復する。

情報は、多すぎたり(OverFetch)、少なすぎたり(UnderFetch)が
いつのまにか、この店の日常になっていた。

店は成長していた。
けれど、店のコントロールは、少しずつ、むずかしくなっていた。


2. 彼が来てから

そんな頃、一人のホールスタッフがやってきた。

特別に派手なところはなかった。
けれど、彼は、よく“見る”人だった。

客の表情を見て、
どこで迷い、
何を知りたがり、
いつ決めようとしているのかを、静かに読み取っていた。

"おすすめは?"

そう聞かれると、彼はキッチンに行き、こう伝えた。

"料理名と、値段と、写真と、ひと言コメントだけください。 "
"それで、十分です。"

返ってきた一枚の紙を手に、
彼は笑顔で席に戻る。

"本日のおすすめは、Minmaハンバーグ、1,200円です。"
"今日はソースを少し改良していますよ。"

客はすぐにうなずき、注文した。

店の流れは、見違えるほどよくなった。

彼は、
多すぎる情報をそっと削り、
足りない情報を先回りして拾い、
いつも“ちょうどいい形”に整えていた。

店長は、彼の背中を見ながら思った。

'この人がいれば、店は回るなぁー'

けれど同時に、別の思いも浮かんでいた。

'でも、こんな人を、
これから先も、何人も見つけられるだろうか'


3. 人だけでは回らなくなって

店は、さらに成長した。

新しい店舗ができ、
新しいメニューが増え、
アプリ注文が始まり、
テイクアウトも増えた。

質問の種類は、日に日に増えていった。

だが――

彼のような人は、ほとんどいなかった。
教えても、同じ動きはなかなか再現できない。
忙しくなると、説明はばらつき、
店舗ごとに答えが変わってしまう。

ある日、新人がキッチンに言った。

"全部ください!"

また、分厚い紙束が戻ってくる。

別の日、別の新人は、

"えっと Minma-209?"

また、長い往復が始まる。

店長は、夜の片付けをしながら、静かにつぶやいた。

"これは、人の問題じゃないな。"
"仕組みの問題だ"


4. 小さなカウンター

その年、店は、小さな決断をした。

ホールとキッチンのあいだに、
小さな“モダンなカウンター”を置くことにした。

それは人ではない。
けれど、こんな役割を持たせた。

  • 客の質問ごとに、必要な情報を定義する。
  • キッチンには、決まった形式で聞く。
  • 料理ごとに、最適な説明を用意する。
  • 誰が使っても、同じ答えが返るようにする。

ホールスタッフは、まずそのカウンターに聞く。

"ランチ画面用のおすすめをください"

カウンターはキッチンに伝える。

"料理名・写真・値段・コメント形式でお願いします"

キッチンは、それに合わせて返す。

新人でも、迷わなくなった。
ベテランでも、翻訳に悩まなくてよくなった。

優秀な人の仕事が、仕組みとして、そっと残された瞬間だった。

店は、また少し、うまく回り始めた。


5. 橋が、城になりかけた頃

しばらくのあいだ、すべては順調に見えた。

品質は安定し、
教育は楽になり、
店舗が増えても、店は回った。

けれど、成長は、また新しい問いを連れてきていた。

メニューが増え、
キャンペーンが増え、
画面が増え、
条件が増え、
例外が増えた。

そのたびに、誰かが言った。

"ここで少し計算しましょう "
"在庫チェックもここで"
"キッチン側は変えにくいので、こっちで吸収しましょう"

いつのまにか、
カウンターは、ただの橋ではなくなっていた。

データを組み立て、
条件を持ち、
キャッシュを持ち、
画面の事情を、深く知りすぎていた。

ある日、キッチンの人が、ぽつりとつぶやいた。

'最近、この店、'
'キッチンより、カウンターが一番忙しそうですね。'


6. 主役は、誰だったのか

問題が起きると、皆が迷った。

'これはキッチン?'
'それともカウンター?'
'ホールの使い方?'

境界は、少しずつ曖昧になっていった。

閉店後、静かな店内で、
店長は椅子に腰かけ、ゆっくりと考えた。

そして、静かに言った。

'ただし――'
'仕組みは、放っておくと、'
'いつのまにか“主役”になってしまう。'

'本当の主役は、'
'キッチンであり、'
'料理であり、'
'客なんだ。'

少し笑って、こう続けた。

'カウンターは、'
'あくまで“橋”でいい。'
'城になってはいけない。'

店長は、しばらく何も言わずに、店を見回した。

いまのカウンターは、橋のままだろうか。
役割は、ちゃんと定義できているだろうか。
キッチンは、本業に集中できているだろうか。
ホールは、シンプルに客と向き合えているだろうか。

答えは、まだ、出ていない。

けれど。

課題を感じ、
どうすればいいのかを考え続けている限り、
この店は、きっと、大丈夫だろう。

そう思いながら、
店長は、明日のメニュー表をそっと閉じた。

こうしてその店は、今日も成長を続けている。
課題を感じながら、
どうすればいいのかを、考え続けながら。


登場単語 / システム対応表

物語の単語 システム
お客 ユーザー
注文・質問 画面操作 / ユースケース
ホールスタッフ フロントエンド
優秀なホールスタッフ 良いフロント設計者 / BFF設計者
キッチン / シェフ バックエンド / ドメイン
料理 ドメインデータ / ビジネス結果
分厚い紙束 OverFetch
IDだけの返答 UnderFetch
小さなモダンカウンター BFF(Backend For Frontend)
城のように育ったカウンター 肥大化したBFF
店長 アーキテクチャ
成長する店 成長するプロダクト・組織

最後に、

長い内容をここまで読んでいただき、ありがとうございました。

minmaでは、成長に伴う複雑さと正面から向き合い、
システムのレイヤごとの役割を明確にしながら、
段階的に改善を進めていく方針を大切にしています。

この物語が、
アーキテクチャや組織づくりを考えるひとつのヒントになれば幸いです。

そして次は、
この「橋」をどう定義し直そうとしているのか。
そして、それを支えるminmaのテック組織について書いてみたいと思います。

システム刷新という話

「システム刷新」という言葉を、
皆さんはどんな場面で聞いたことがあるでしょうか?

はじめまして。
私は、みんなのマーケット株式会社(以下、minma)で、エンジニアリングマネージャーをしています。
ちょうど一年前、この場所にやってきました。

minmaでは、約15年にわたって「くらしのマーケット」というサービスを運営しています。
約400を超えるサービスカテゴリ。
それぞれの暮らしのすぐそばで、今日も静かに使われています。

長く続くサービスには、必ず時間が積もります。
ユーザーが増え、店舗が増え、
その一つひとつに応えるように、システムも姿を変えてきました。

開発のやり方が変わり、
運用の考え方が変わり、
使う技術も、時代とともに移り変わっていく。

その変化に、minmaのエンジニアたちは、
壊さないように、止めないように、
静かに、誠実に向き合い続けてきました。

一方で、社会もまた、15年の間に大きく変わりました。
気候、価値観、働き方。
それに合わせて、会社も、人も、少しずつ形を変えてきたのだと思います。

けれど、変化が長く続くと、
ほんの小さな遅れや、わずかな判断のズレが、
気づかないうちに積み重なっていきます。

システムは、「今」に追いつけないまま、
その場ごとの判断で、なんとか応え続けてきました。

良かれと思って重ねた対応は、
いつしかシステムの中心に、大きく重たい"核"をつくります。

小さな変更でも、中心が揺れる。
大きな変更には、長い時間が必要になる。
気づけば、動く速さは、少しずつ落ちていました。

もちろん、立ち止まっていたわけではありません。
これまでに、何度もリニューアルを行ってきました。
小さなものも、大きなものも。

そのたびに、手応えはありました。
「良くなった」
「これで大丈夫だ」

それでも、時間が経つと、
また同じような問題が、似た場所から顔を出す。

この感覚に、私は既視感を覚えました。

それは、ダイエットに似ています。

一時的に運動量を増やせば、体は変わります。
数値も、見た目も、確かに結果は出る。

けれど、生活そのものが変わらなければ、
時間とともに、元の状態へと戻っていきます。

そしてまた、
同じ決意をして、
同じことを繰り返す。

終わりのない宿題のようです。

システムも、同じなのだと思います。
バージョンアップや技術刷新は、確かに必要です。
けれど、それだけでは体質は変わらない。

ダイエットが続くのは、
運動や食事だけでなく、
暮らしそのものが変わったときです。

無理をしなくても続くこと。 止めなくていいこと。

システム刷新も、
一度きりのイベントではなく、
続いていく前提であるべきだと、私は考えています。

健全な文化があり、
その中で自然に選ばれる設計があり、
少しずつ、しかし確実に、前へ進んでいく。

このブログでは、
minmaのシステム刷新について、
成功だけでなく、迷いや違和感も含めて、
エッセイのように綴っていこうと思います。

エンジニアだけでなく、
デザイナー、プランナー、ディレクター
ものづくりに関わるすべての人に、
どこか引っかかる一文が残れば、それで十分です。

もし、
よければ、一緒に歩いてください。
システム刷新という、長く静かな旅を。

次は、minmaのシステムが抱えている課題を、
もう少し物語として紹介してみようと思います。

Redash の出力結果を Chrome 拡張機能で Markdown にコピーできるようにした話

こんにちは、エンジニアの yuma です。

突然ですが、皆さんは Redash を使っていますか?弊社では本番データを参照する際に Redash を使っており、エンジニアはもちろん、エンジニアでない方もデータ分析に活用しています。

redash.io

Redash では豊富なビジュアライゼーションが提供されていますが、データ分析には有用な一方でエンジニア間でデータを共有するには少々不便でした。

そこで、Redash の出力結果を Markdown のテーブルでコピーできるようにする Chrome 拡張機能を作成しました。拡張機能自体を公開することはできませんが、 Chrome 拡張機能の開発や実装時のポイントを共有します。

なお、スクリーンショットを含む本記事で掲載しているデータは、全てデモ用のダミーデータです。

モチベーション

弊社では、原則エンジニアを含めて本番データの参照には Redash を利用しています。本番データの変更は特定のエンジニアのみが実行権限を持ち、どのような変更であっても GitLab 上で変更内容を起票、適切な承認フローを通した上で変更を依頼します。

その際、正しい変更が行われたかどうかを確認するために、変更前のデータと変更後の想定データを記入し、レビューを受ける必要があります。エンジニアは Redash でクエリを実行して変更前のデータを取得しますが、 Redash の出力結果はコピペがしづらく、GitLab へのデータの貼り付けが手間になっていました。

ワークアラウンド的な方法もあるようですが、頻度の高い作業であるため、せっかくならワンボタンでコピペできるようにしたい!と思い、Chrome 拡張機能を作成することにしました。

あまりコピペしやすい形式とはいえない...

構成

簡易的な図ですが、以下のような構成で開発しました。

表示しているページに任意のスクリプトを挿入できる Content scripts でコピーボタンの挿入やボタン押下時のテーブル取得を行い、バックグラウンドで実行される Service worker にメッセージを送信します。

Content scripts では、 Redash のテーブル表示を CSV ライクな文字列配列に変換し、 Service worker へメッセージを送ります。 Markdown 形式への変換は Service worker で実行し、 Content scripts は返却された Markdown 文字列をクリップボードにコピーする、という流れです。

より詳しくみていきましょう。

Content scripts

Content scripts は、表示しているページに任意のスクリプトを挿入できる機能です。挿入されたページの DOM にアクセスできるため、ページ上の要素を取得・操作が可能です。

developer.chrome.com

今回は、 Redash のクエリ実行画面(/queries)にコピー用のボタンを挿入します。コピー用のボタンは React コンポーネントとして実装し、ReactDOM で root.render() することで Redash の画面上に描画しています。以下の「Copy as Markdown」ボタンが、 Chrome 拡張機能で描画されたコンポーネントです。

ただし、 Redash は SPA で構築されており、ページ遷移時にリロードが発生しないため Content scripts が再実行されません。また遷移後のクエリ実行画面でも、ボタン部分を含め頻繁に表示が変化するため、単純にボタンを挿入するコードだけでは DOM の再構築によってボタンが消えてしまいます。

このような DOM の変更を監視・コンポーネントを再描画するために、 MutationObserver を利用しました。

MutationObserver

MutationObserver は、 DOM の変更を監視することができる WebAPI です。特定の DOM の変更をその子孫も含めて監視することができ、その変更をコールバック関数に通知します。

developer.mozilla.org

変更の内容は MutationRecord というクラスインスタンスで通知され、この辺りに少々癖がある印象でしたが、フレームワークなしでも DOM の変更を監視して扱えるのは非常に便利でした。

今回は、クエリ実行画面の下部バー DOM が追加されたタイミングで、コピー用ボタンのコンポーネントを描画しました。クエリ実行画面の遷移や、クエリを実行した際の再描画でもボタンが表示されるようになりました。

  const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      // MutationRecord.addedNodes で、追加されたノードを取得できる
      mutation.addedNodes.forEach((node) => {
        if (node.hoge() === "fuga")) {
          // ... 中略 ...
          // div を挿入して React コンポーネントを描画
          const rootDom = document.createElement("div");
          bottomController.insertBefore(
            rootDom,
            bottomController.lastElementChild
          );
          const root = createRoot(rootDom);
          root.render(createElement(CopyAsMarkdown));
        }
      });
    }
  });

Service workers

Service workers は、ページ内で動作するスクリプトとは別にバックグラウンドで実行されるスクリプトです。

各ページから発火されるイベントのハンドラを定義することで様々なイベントを監視できるほか、ページに挿入した Content scripts からのメッセージを受けることもできます。今回はテーブルのデータをペイロードとして受け取り、 Markdown 形式に変換するメッセージハンドラを実装しました。

chrome.runtime.onMessage.addListener((message, _, sendResponse) => {
  if (message.type === "RRC_MARKDOWN_COPY") {
    const csvAsArray = (message as Messages["CopyMarkdown"]).data;
    sendResponse(markdownTable(csvAsArray));
  }
  return;
});

メッセージの送信も以下のようにシンプルな API で実現できます。

const execCopyMessage = (data: Csv, callback: (response: string) => void) => {
  const message = {
    type: "RRC_MARKDOWN_COPY",
    data,
  };
  return chrome.runtime.sendMessage(message, callback);
};

manifest.json

manifest.json は Chrome 拡張機能本体の設定ファイルです。配布時に重要になるような、拡張機能の名称や説明・アイコンなどはあまり気にせず、実装に必要な部分だけ設定しました。

ポイントとなる部分だけを抜粋して解説します。

content_scripts / background

実行したい Content scripts , Service workers のスクリプトを指定します。

Content scripts には、実行したいページの URL を正規表現や独自のパターン表現で設定できます。

  "content_scripts": [
    {
      "js": [
        "content.js"
      ],
      "matches": [
        "https://<Redash サービスのホスト>/queries/*"
      ]
    }
  ],
  "background": {
    "service_worker": "worker.js",
  },

permissions

Chrome 拡張機能向け API や一部のスクリプトのためには、ユーザーからの権限許可が必要です。 permissions に必要な権限を記述することで、 Chrome 拡張機能からユーザーに権限の許可を求めることができます。

今回はクリップボードへの書き込み権限が必要なため、 clipboardWrite を必須の権限に追加しました。

  "permissions": [
    "clipboardWrite"
  ]

補足: CRXJS Vite Plugin について

弊社では TypeScript を開発言語に採用しています。Chrome 拡張機能の開発でも TypeScript を使うことにしました。

「Chrome 拡張 TypeScript」などで検索すると、 CRXJS という Vite プラグインがヒットすると思いますが、今回はこちらの採用は見送りました。*1

crxjs.dev

CRXJS が対応しているのは Vite 2、ベータバージョンでも Vite 3 までのため、最新のバージョンとは大きく離れてしまっています。今回の開発内容で得られる恩恵は manifest.json の型付け程度で重要度も低いことから、CRXJS を使わなくても特段問題にはなりませんでした。

まとめ

今回は、 Chrome 拡張機能を開発する中でのポイントをいくつかご紹介しました。

ストアでの公開・配布を考慮すると、安定度やメンテナンス性などをさらに考える必要がありそうですが、個人用や社内ツールとして簡単に使う上ではそこまでハードルは高くないように思います。皆さんも、身近なツールでかゆいところに手が届かないようなときには、ぜひ自分用の Chrome 拡張機能を作ってみてください!

最後に

みんなのマーケットでは、くらしのマーケットのサービス開発を一緒に盛り上げてくれるエンジニアを募集しています!

詳しくは、こちらをご覧ください。

*1:正確にはプロトタイプの段階では CRXJS を採用していましたが、 React コンポーネント化に際して Vite のバージョンが問題となってしまったため、CRXJS を採用しない方針に変更しました。

エンジニアが出店者が集まるイベントに参加してみた

バックエンドエンジニアのあべです。

エンジニアが、サミットと呼ばれる出店者イベントに参加した感想をまとめてみました。

動画も含めて紹介していますので、ぜひご覧いただければと思います。

サミットとは

くらしのマーケットに出店している方々(以下、出店者)が、参加する出店者コミュニティのイベントです。

東京・大阪・名古屋・福岡など全国各地で開催されており、毎回多くの出店者が参加しています。

基本的には、出店者が企画・運営をしており、くらしのマーケットで活躍する出店者の発表やグループワークを通じて、サービスページの作り方や集客方法などお互いに情報共有して教え合う場となっています。

サミットの動画↓

www.youtube.com

サミットに参加した理由

「くらしのマーケットアワード」に参加したことがきっかけです。

「くらしのマーケットアワード」とは、年に一度開催されるくらしのマーケットでお客様からの支持が特に高かった店舗を表彰する式典です。

私は、2021年の「くらしのマーケットアワード」に初めて参加して、アワードの盛り上がりに衝撃を受けました。

くらしのマーケットで活躍する出店者の方々が受賞・表彰されて、涙を流しながら喜ぶ姿、またそれを称え合う姿に、素晴らしいプロダクトを開発できていることに感動しました。

翌年に、サミットが開催されるということで、実際に出店者の方々とお話をしてみたいと思ったのが参加の理由です。

2023年のくらしのマーケットアワードの動画↓

www.youtube.com

くらしのマーケットアワード2023の特集記事↓

https://curama.jp/featured/curama-awards/list/

エンジニアが実際に参加してみて

開発した内容についての率直なフィードバックを受けることができる

「この機能が便利になった」「この機能に助けられています」など意見をもらえました。

自分が開発したサービスが、利用者に価値を与えていることを実感できることはとてもやりがいになります。フィードバックの中には、自分が直接開発に関わっていない機能もあるので、それは社内に持ち帰り、担当エンジニアに伝えたりしています。

また、くらしのマーケット一本で仕事をしている出店者からの期待の声を聞くと、責任感も湧きました。

お話をさせていただいている中で、「こういうことに困っている」「こういう悩みがある」など、出店者が抱える根本的な問題を考えるきっかけにもなっています。

話を聞いて視座を高めることによって、開発するときに利用者の立場を想像することにも役立つと考えており、貴重なコミュニケーションです。

出店者のサービスページの作り込みや研究がスゴイ

出店者が、集客のためにサービスページの研究に日々取り組んでいることに驚きました。

活躍している出店者の方々は、写真や動画の工夫をはじめとして、文章の細部までこだわって作成しています。

普段、開発しているエンジニアでも気づかないようなポイントにフォーカスしていて、こちらも勉強することがたくさんあります。

また、出店者の方は、普段自分たちが利用している店舗管理画面の変化だけではなく、ユーザー(お客さん)が利用する画面での細かな変化に気づいて対策する出店者の方もいて、研究がスゴイと感じています。

出店者の方々のポジティブな変化を知ることができる

去年初めてサミットに参加して、まだくらしのマーケットに登録したばかりだった出店者の方が、1年後にはサミット運営をして口コミ数や売上が何倍にもなっていて、成功を感じることができました。

くらしのマーケットを利用して、努力を続けて成功している出店者の方を見ることができるのもサミットならではだと思います。

サミットで自己紹介をするあべ(緊張気味)

出店者のグループワークに参加しちゃうあべ

最後に

エンジニアは、どうしても開発や技術にフォーカスして、プロダクトから興味が離れてしまいがちです。(すみません、偏見かもしれないです。)

ただ、エンジニアにとって、開発したプロダクトを利用しているユーザーと直接コミュニケーションをとるのは、プロダクトが利用者に与える価値を身近に感じることができます。

手を挙げれば、エンジニアでもくらしのマーケットアワードやサミットに参加できるチャンスがあるのは、良い環境だと思います。 たまには、そういうことに参加してみちゃうエンジニアがいても、良いのではないでしょうか。

最後にみんなのマーケットでは、くらしのマーケットのサービス開発を一緒に盛り上げてくれるエンジニアを募集しています! 詳しくは、こちらをご覧ください。