3/13(水)に Product Engineer Night で喋ります! #PdENight

オフラインの勉強会です。ぜひ来てね!

product-engineer.connpass.com

(って言っても、ありがたいことに定員オーバーしていて、抽選待ちの状態です。でもよかったら申し込んでくれると嬉しいです!)

仮説検証ループをすばやく回し続ける

僕は、スタートアップで新規プロダクトの開発を担当しています。そのプロダクトを立ち上げるときに「仮説検証のループをすばやく回し続けられるように!」と、いろんな決断をしながら作ってきました。立ち上がってしばらくたつ今もスピードを落とさずに仮説を検証し続けている状態です。↓いまこんな状態

  • 毎週何かしら動くものをステークホルダーに見せて意見をもらっている
  • トランクベースの開発で、毎日本番環境にデプロイしている(ときには5,6回)
  • フィーチャーフラグを使って、リリースをコントロールしている
  • ユーザーの声が届いてすぐに改善をリリースした
  • 機能追加だけでなく、立ち上げ時には後回しにしていた改善も同時に進めている

このままスピードを落とさずにどんどん仮説検証ループを回していきたいなと思っています。

そのための決断と、その背後にある考え

そんな状態にしていくために、立ち上げのときに僕らがどんな決断をしてきたかを紹介します。

  • 自動テストじゃなくて、手動テストを中心にして開発を進めることにした
  • けど、CIのパイプラインは組んで、One Click Deploymentはできるようにした
  • AWSのインフラはすべてTerraformでIaCできるようにした
  • BFFとしてGraphQLを使うことにした、けど、使い方は限定することにした
  • ORMはリッチなものではなく、SQLが見える薄いものにした
  • などなど

また、どんな考えがあって、そういった決断をしてきたのかという、その背後にある考えについて、お話ししようと思ってます。タイトルは「新規プロダクトの仮説検証ループをすばやく回し続けるためのプロダクトエンジニアリング」です!

ということで、会場でお会いできるのを楽しみにしています!がんばるぞー!

git-replay を最低限の使い方で触ってみた

git-replay というコマンドが追加されたみたいなので触ってみた。とは言っても、自分はあんまり凝ったことはやらないので、細かいところまでは踏み込まずに最低限の使い方ができたらいいなってくらいの気持ちで触った。

github.blog

この記事には、こんな風に書いてある↓

git replay exists to address these challenges. It offers an alternative to git rebase that, in addition to being far more performant:

  • Can operate in bare repositories.
  • Can rebase branches other than the currently checked-out one (in non-bare repositories).
  • Can operate over multiple branches simultaneously. and much more.
  • git rebase よりもずっとパフォーマンスがいい
  • ベアリポジトリに対しても実行できる
  • 今チェックアウトしているブランチ以外のブランチをリベースできる
  • 複数のブランチを同時にリベースできる

ベアリポジトリ?

ベアリポジトリってなんだろう?と思って調べた。

実際のファイル(作業ディレクトリ)を持たずに .git の中身だけを持ってるリポジトリのことをベアリポジトリって呼ぶのか。へー。

普段の開発中に使ってるやつは作業ディレクトリを持ったノンベアリポジトリ。ベアリポジトリは、GitHubとかサーバーみたいに作業ディレクトリが必要ないところで使う用っぽい。

じゃ「ベアリポジトリに対しても実行できる」は僕は気にしなくて良さそう。

自分が git replay を使うときの目的

「複数のブランチを同時にリベースできる」ってのも、僕はあんまり使わなさそう。だから「replayだとそういうのもできるんだな」って頭に入れておくくらいでいいか。

「パフォーマンスがいい」も、これまで git rebase 使っててパフォーマンスが気になったことないし。「今チェックアウトしているブランチ以外のブランチをリベースできる」もあんまりやりたいと思ったことないな・・・。

なので、自分が git replay を使うときの目的は、・・・「ちょっと違う使い方をできる git rebase」くらいだな。

使ってみる

気が楽になったところで公式のドキュメントを眺めながら使ってみる。まだ、Experimentalなんだね。ふむふむ。

Git - git-replay Documentation

適当なツリーを準備してみよう。

準備

❯ mkdir try-replay
❯ cd try-replay

❯ git init
Initialized empty Git repository in (...)/try-replay/.git/

❯ git commit --allow-empty -m "A"
[main (root-commit) 2221dba] A

❯ git commit --allow-empty -m "B"
[main 3db0749] B

❯ git commit --allow-empty -m "C"
[main 967b63d] C

❯ git commit --allow-empty -m "D"
[main cadb435] D

❯ git switch -c hello 3db0749
Switched to a new branch 'hello'

❯ git commit --allow-empty -m "E"
[hello 996c85c] E

❯ git commit --allow-empty -m "F"
[hello 12fc98d] F

❯ git ls
* 12fc98d (HEAD -> hello) F
* 996c85c E
| * cadb435 (main) D
| * 967b63d C
|/
* 3db0749 B
* 2221dba A

こんな感じでいいか。ちなみに git lslog simple みたいな気持ちで、この記事を書くためにエイリアス登録しておいた。

❯ git config --list | grep alias.ls
alias.ls=log --graph --all --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s'

使ってみる

--onto でターゲットのブランチやコミットを指定して、その後ろにリプレイしたいレンジを指定するのか。いま、コミットツリーはこうなってるから

❯ git ls
* 12fc98d (HEAD -> hello) F
* 996c85c E
| * cadb435 (main) D
| * 967b63d C
|/
* 3db0749 B
* 2221dba A

mainをターゲットにして、helloをそこにつけかえてみよう。

❯ git replay --onto main main..hello
update refs/heads/hello 3de3e36e676090cca31a8ef1852f70c7584b8491 12fc98d2fc133b4cf40f1d0c525ee74a535dce17

ツリーは更新されずに、結果だけがアウトプットに出てくるってドキュメントに書いてあったので納得。いちおうツリーを確認しておこう。

❯ git ls
* 12fc98d (HEAD -> hello) F
* 996c85c E
| * cadb435 (main) D
| * 967b63d C
|/
* 3db0749 B
* 2221dba A

変わってない。でさっきのアウトプットを見たところ 3de3e3 のコミットが作成されているはずだから、確認してみようか。

❯ git branch sample 3de3e3

❯ git ls
* 3de3e36 (sample) F
* 1758427 E
* cadb435 (main) D
* 967b63d C
| * 12fc98d (HEAD -> hello) F
| * 996c85c E
|/
* 3db0749 B
* 2221dba A

想像してたとおりになった。

git update-ref --stdin とつなげてみようか

アウトプットは git update-ref --stdin に渡せるよって書いてあるのでやってみよう

❯ git replay --onto main main..hello | git update-ref --stdin

❯ git ls
* d6bd066 (HEAD -> hello) F
* eedec2f E
| * 3de3e36 (sample) F
| * 1758427 E
|/
* cadb435 (main) D
* 967b63d C
* 3db0749 B
* 2221dba A

ふむふむ。そうなるよね。

どう使うかなぁ?

Experimentalってこともあるけど、今のところは git rebase を使いそう。ただ、git rebaseHEAD を移動しちゃうから、それが嫌だなってときは git replay もいいのかな。

僕は使わないだろうなと思ったから触れなかったけど --onto の代わりに --advance ってオプションもあって、そっちでターゲットを指定すると、アウトプットではターゲットブランチが移動する出力になるみたい。さっきの例でいくと hello じゃなくて main ブランチが移動する感じ。

おもしろかった

Gitは、ちょこちょこ新しいコマンドがでてくるから、40歳を過ぎて「新しいものわざわざ覚えなくても、自分がいちど覚えて手に馴染んでいるものがあるからそれでなんとかなるやん」って状態をちょこっと手放そうとしてみる頭の体操にちょうどいいな。

GitHubのMerge Queueとは何か?それと、認識しておきたいこと

同僚に「GitHubのMerge Queueってあんまり知らないんだけど、どう思う?」って聞かれて「あー。僕もあれよく分かってないんだよね」って返事をして、ちょうどいい機会なので見てみた

見てみた感想としては、いくつか気をつけておきたい点があるけど、チームの開発の進め方にうまくはまれば便利な機能だな、という感じ(なんでもそうか・・・)

Merge Queueって?

2023年の7月にGAになったGitHubの機能

プルリクエストをマージするときに「マージ先のブランチ(ベースブランチ)の最新の変更を取り込んでからChecks(つまりCI)を実行して、それが成功したらマージしといて!」ってお願いできる便利機能。名前のとおりQueueになっているので複数のプルリクエストからenqueueできて前から順番に処理してくれる

そうは言われても最初に説明を見た僕は「???」状態だった。「なんでこんな機能が欲しいんだろう?」って。数ヶ月後の僕はもう忘れてそうだから、未来の自分に向けて記事を書いておくことにした

参照

↓利用可能なリポジトリに制限があるので注意

pull request マージ キューは、Organization が所有するパブリック リポジトリ、または GitHub Enterprise Cloud を使っている Organization が所有するプライベート リポジトリで使うことができます。

-- マージキューの管理 - GitHub Docs より

この記事で書くこと・書かないこと

この記事では「Merge Queueとは何か?」について書く。「Merge Queueの設定方法」については書かない

どうしてMerge Queueが欲しいんだろう?

Merge Queueを理解するためには、それが必要となった経緯を理解すると分かりやすそうなので、こういう流れかなと自分が思うものを書いてみる。具体的に考えられるように対象としてmainブランチを考えるけど、別にmainブランチじゃなくてもいい

直接プッシュをしないようにしたい

まずはじめに、mainブランチには直接変更をプッシュしたくない。プルリクエストを必須にしてレビューしたものだけをマージしたい

だから、mainブランチにブランチプロテクションを設定して「直接プッシュせずにプルリクエストだけで更新する(Require a pull request before merging)」という条件をつける

これで、mainブランチの更新にはプルリクエストが必須になる。あんしん

Checksに成功したときだけマージしたい

次に、プルリクエストをマージするときには、コンパイルやリンターや自動テストなどが成功したものだけをマージするようにしたい

だから、ブランチプロテクションに「特定のChecksが成功していないとマージできない(Require status checks to pass before merging )」という条件をつける

これで、例えば自動テストのChecksが成功していないとマージできないようになる。あんしん

最新のベースブランチに対してChecksを実行したい

プルリクエストではChecksが成功していたのに、mainにマージしたら失敗する場合がある。ベースブランチであるmain側が更新されているのに、それを取り込まずにChecksを実行している場合だ

だから、ブランチプロテクションに「ベースブランチが最新じゃないとマージできない(Require branches to be up to date before merging)」という条件をつける

これで、最新のベースブランチへの変更に対してChecksが成功していないとマージできなくなる。あんしん

Checksの完了を待ちたくない

ベースブランチの更新を必須にしたことで安心にはなったのだけど、ちょっとめんどくさいことが発生する

誰か別の人がプルリクエストをマージしてmainブランチが更新されている場合は、それを取り込んで、もういちどChecksを流さないといけないのだ。それはそう

でも、そうなると開発者はChecksの実行を待たないといけない。5分程度の時間だったとしても開発者は他の作業には集中できない。しょうがないからTwitterを見始めたりする(?)

だから、Auto Mergeを利用する

これで、「Enable auto-merge」ボタンを押せば「Checksが成功したらマージしておいてね」とGitHubにお願いしたことになり、開発者はChecksが成功するのを待たずに別の作業を始められる。やったね

複数のプルリクエストでもAuto Mergeしたい

Auto Mergeのおかげで待たなくてよくなったのだけど、複数のプルリクエストが出ている場合には結局開発者によるアクションが必要になる。全部のプルリクエストで「Enable auto-merge」を押したとして、最初の1つがマージされたらそれ以外のプルリクエストでは結局「Update Branch」ボタンを押さないといけない

だから、Merge Queueを利用する(やっとたどりついた!!)

Merge Queueを利用すると「最新のベースブランチに対してプルリクエストの変更を適用してからChecksを実行して、それが成功したらマージしといて!」とお願いしたことになる。しかもそれをQueueとして順番に実行できるので、前から順番に A B C のようにQueueに入ると、BのChecksは main<-A<-B に対して実行される

これで、複数のプルリクエストがある状態でもボタンを押して、そのあとは忘れて次の開発に入れるぞ!やったー!

どんなときに嬉しいか?

ということでどんなときにMerge Queueが嬉しいかなぁって考えてみると、1つのブランチに対してたくさんのプルリクエストが作られる場合かな

モノレポの場合だと特にそうかもしれない。Update Branchの取り合いになりそうよね。そんなときにMerge Queueを使えば、順番にマージされていくので便利

1つのブランチに対してプルリクエストが同時に数個しか作られないなら、Auto Mergeで十分かもしれない

Merge Queueで認識しておきたいポイント

Merge Queueを使うときには、この2点を認識しておきたいなと思った

  • Merge Queue用のワークフロー設定が必要
  • Checksがプルリクエストと共通

Merge Queue用のワークフロー設定が必要

Merge Queueにキューが積まれると、そのプルリクエストの変更をベースブランチにマージした一時的なブランチが作られて、そこに対してワークフローが実行される。そして、Checksが成功するとベースブランチの最新はそのコミットを指すようになる

このときに発生するイベントは、merge_group イベントの checks_requested アクティビティなので、次のような条件になる

on:
  merge_group:
    types: [checks_requested]
    branches: [main]

基本的には on: pull_request と同じジョブを実行すればいいかなと思う

注意しておきたいのはプルリクエストのときとは環境が少し違うという点。たとえば↓は on: pull_request に対する情報

github.ref=refs/pull/9/merge
github.sha=f6dd1d19260fdf954b055f4b66359ca2f593bc6c
github.base_ref=main
github.head_ref=hello

https://github.com/bufferings-hello/hello-merge-queue/pull/9#issuecomment-1936890826

これが、 on: merge_group だと↓こうなる

github.ref=refs/heads/gh-readonly-queue/main/pr-9-585e0bea0e4a1d10ce8ba48e5a6fa9615ee6553e
github.sha=f60ed60e01d9162d835e522f6a2d70bc2661bbdc
github.base_ref=
github.head_ref=

https://github.com/bufferings-hello/hello-merge-queue/pull/9#issuecomment-1936890935

ref を利用しているジョブがある場合は注意が必要だね

Checksの条件がプルリクエストと共通

上記のようにMerge Queueに対しては別のワークフローが実行されることになるが、このワークフローが成功したとみなされるのは「プルリクエストに対して設定されているChecks条件」と同じ条件が満たされた場合になっている

on: pull_request と同じジョブを実行している場合は特に問題ないが、↓の記事のように「Merge Queueの場合は別のジョブを実行したい」という場合には注意が必要となる(なかひこくん、とても参考になりましたありがとうございます)

hackerslab.aktsk.jp

この記事では「違う内容のジョブを同じ名前で実行する」という方法で解決している

それ以外にも↓こんな方法でも対応できる。最後にステータスをチェックするジョブを用意しておく方法。モノレポの場合は変更された内容によって実行されるジョブが違ったりするから、こういうのを最後に置いておくと便利

  job_status:
    runs-on: ubuntu-latest
    needs: [job_a, job_b, job_c]
    if: always()
    steps:
      - if: >-
          contains(needs.*.result, 'failure')
          || contains(needs.*.result, 'cancelled')
        run: exit 1
      - run: echo "ステータスチェックOK"

https://github.com/bufferings-hello/hello-merge-queue/blob/f60ed60e01d9162d835e522f6a2d70bc2661bbdc/.github/workflows/build.yml#L99-L108

GitHub側が、Merge Queue用に別のChecks設定を入れられるようにしてくれても良さそうだが、これは想像だけだけど、もし僕がGitHubの開発者だったとしても「まずはプルリクエストと同じ条件を使うようにしておく」という判断をするだろうなと思う。Merge Queue用の設定追加は、めっちゃ使われるようになってから考えるだろうなと

Merge Queueで注意しておきたいポイント

Merge Queueは、ここまで見てきたような特徴を持っているので、注意しておきたいポイントがある。それは「ワークフローの実行時間が増える」ということ。つまりコストも増える

↓こちらの記事に、実際に使ってみての感想として、そう書かれている

30 Days of Merge Queue

どういうことかというと、これまでは on: pull_request -> on: push の2回実行されていたのが、 on: pull_request -> on: merge_group -> on: push と、3回実行されるようになるということ

ただ、ランチには行ける(ビルドを待たずに別の開発を始められる)ので、そっちの効果の方が大きいだろうなとは思う

Merge Queueをどんなふうに使おう?

ということで最後に「Merge Queueをどんなふうに使おう?」って考えてみる

単純に使うなら

単純に使うならMerge Queueでも、プルリクエストのときと同じジョブを実行すればいい

Checksが成功したら勝手にマージされるので待たなくていい

でも、待ち時間は2倍になる

ちょっとでも短くしようと思ったら

さっきのなかひこくんの記事みたいに、プルリクエストのときには時間のかかるジョブを実行せずに、Merge Queueのときだけ実行するようにするというのはありだなと思う

そうすれば、プルリクエストのときはさくっとChecksの実行結果が見えて、だけど、mainにマージするときには例えばe2eテストまで成功している、という状況が作れる

pushも考えてみる?

main ブランチに on: push で重めのジョブを実行している場合、それを on: merge_group だけで実行することも可能かもしれない。というのもさっき書いたように↓

そのプルリクエストの変更をベースブランチにマージした一時的なブランチが作られて、そこに対してワークフローが実行される。そして、Checksが成功するとベースブランチの最新はそのコミットを指すようになる

on: merge_group の対象が、最後にはベースブランチのコミットになるから

これはつまり「Checksが成功したらそれをmainの最新コミットにする。失敗したらそのコミットは捨てる」という動き

ただ、これはGitHubがドキュメントに書いているわけじゃなくて、僕がこんな感じで github.sha が同じことを確認しただけなので、変わる可能性もある

on: merge_group の場合

github.ref=refs/heads/gh-readonly-queue/main/pr-9-585e0bea0e4a1d10ce8ba48e5a6fa9615ee6553e
github.sha=f60ed60e01d9162d835e522f6a2d70bc2661bbdc
github.base_ref=
github.head_ref=

https://github.com/bufferings-hello/hello-merge-queue/pull/9#issuecomment-1936890935

on: push の場合

github.ref=refs/heads/main
github.sha=f60ed60e01d9162d835e522f6a2d70bc2661bbdc
github.base_ref=
github.head_ref=

https://github.com/bufferings-hello/hello-merge-queue/pull/9#issuecomment-1936891096

長くなった

長くなったけど、もしあなたのチームで「そっちのプルリクエストのCIが通ったら、こっちのプルリクエストのUpdate Branchを押しますね」というセリフが聞こえているならMerge Queueは役に立つかもしれない

Cloudflare WorkersにBun+Honoのアプリをデプロイするハローワールドをやった

ほんとうにハローワールドをやっただけなので、それ以上の情報はないよ!

いっこあるとしたら「Cloudflare WorkersのCLIであるwrangler がBig Surでは動かなくて、Sonomaにしたら動いたよ」ってことくらい

全然知らない技術

この記事を読んで、そういえばHono [ほのー] を触ってみたいんだったなぁと思って(HonoはJSランタイム用の高速なウェブアプリケーションフレームワーク)

Next.js + Hono + AWS でイベント駆動なプロフィール交換アプリを開発!

Honoのドキュメントを眺めて

https://hono.dev/

JSのランタイムはNode.jsをいつも使ってるから、Bun [ばん]かDeno [でぃーの]を触ってみようかなと思って、どっちも触ったことがないからどっちでもよかったんだけど、なんとなくさっきの記事もそうだったからBunでやってみることにして

https://bun.sh/

せっかくなのでCloudflare Workersも使ってみたいなと思った

https://workers.cloudflare.com/

だから、Honoのドキュメントにある、このGetting Startedをやることにした

https://hono.dev/getting-started/cloudflare-workers

Cloudflare Workers

そもそもアカウントを持っていないので、そこから

https://workers.cloudflare.com/

のStart Buildingボタンを押してまずはブラウザからデプロイしてみた

最初のデプロイはなぜか動かなかった。デプロイを押したら「サインアップしてね」って出てきたのでそこでサインアップしたからかな?2回目のデプロイの方は動いた

デプロイはしたものの、Cloudflare Workersのことを全然知らないので、ここを読んで分かった気分になった

入門Cloudflare Workers

CPU時間が10msまでっての面白いな。この記事中では有料プランでも50msってなってるけど、2023年の9月に料金体系が変わって、有料プランだとCPU時間30sまでいけるようになったみたい

New Workers pricing — never pay to wait on I/O again

デプロイしたあとしばらく放っておいたら、ヨーロッパあたりから200アクセスくらいきてて、なんだろうなこれと思った。まぁ、ハローワールドだし、無料枠だし特に気にしない

そんな感じで、ひととおり管理画面を眺めて、雰囲気を楽しんでからデプロイしたアプリを1回目のも2回目のも削除した

じゃCloudflare Workersにデプロイできるようになったので、次はBunをインストールしよう

Bun

Bunをインストールした

https://bun.sh/

まったく何も分かってないけど、npm気分で使えそうな雰囲気

はい、次はいよいよHonoだな

Hono

このドキュメントでBunのタブを選択して、指示にしたがうだけ

https://hono.dev/getting-started/cloudflare-workers

プロジェクトを作って、bun i でパッケージをダウンロードするところまではよかったのだけど、bun run dev が動かない。あれー?

Error: write EPIPE みたいなエラーが出る。何事もなく進むのもつまらないなーと思ってたのでちょうどよかった

package.json には "dev": "wrangler dev src/index.ts", って書いてあるのでCloudflareのCLIであるwranglerがなんか変なんだろうな

と思って調べていったら、こんなIssueを見つけた

このIssueはクローズされていて、問題は解決しているように見える。はて・・・

僕のmacOSのバージョンはBig Surなのだけど、Big Sur以降に対応してるよって書いてある

On macOS, we now target macOS 11 "Big Sur" when building. This means any version of macOS 11 or higher (Big Sur, Monterey, Ventura, Sonoma) should be supported.

じゃあ、なんで動かないのかなー?と思いながら読み進めていったら

I saw this comment #3432 (comment) about Big Sur being supported. I was on 11.1 and that didn't work for me, upgraded to macOS 14 and now it works.

Big Sur動かんかったけどSonomaに上げたら動いたよ。って書いてあった。これかな

Sonomaにバージョンアップ

ちょうどいい機会だしSonomaに上げることにした。しばらく休憩

動いた

Sonomaにしたら、bun run dev がほんとにうごいた。わーい。からのデプロイ

ヽ(=´▽`=)ノ

満足した

Honoは、ざっとドキュメントを読んだ感じ、触りやすそうなインターフェースだなと思った。自分はFasitfy触ってるから、そんなに戸惑うことはなさそう

BunやDenoは、まだまだ何にも分かってないので、これを機会にちょこちょこ触っておこうかな。TSやESMがすっと動くのは良いー

Cloudflare Workersは、サクサクできるしColdStartもないし、制限内に収まるならいい選択肢だなーって思った

今日はこれで満足したので、晩ごはん何にするか考えながら、小説でも読んで過ごすことにする。楽しかった!!!

チームトポロジーを疑ったりしながら

とても雑記。

チームトポロジーを読んだ。買ったときにはさらっと読んでたので、今回はゆっくり読んだ。

読み直そうと思った理由

以前に読んだときは「まぁ、そうだな。わかる」くらいの気持ちだったんだけど、それから2年くらい経って、チームトポロジーの用語が、少なくとも僕の周りでは、普通に交わされるようになってるなぁと感じたから、読み直そうと思った。

用語をある程度理解しておくと、相手の言っていることがよりよく理解できそうだなと思って。

読み直してみてどうだった?

まぁ、そうだな。わかる。という気持ち。(おい!)

それから、用語の整理もできた。4つのチームタイプと3つのインタラクションモード。これで、周りの会話もよく理解できそう。

そして、とてもよかった。これは、自分が成長したってことかな。

DevOpsにピンときてない

そもそも、前段にあるDevOps自体には、自分はピンときていない。これまで開発と運用を分けてたことがほとんどないから。

コードを壁の向こうになげてリリースしてもらう?何を言ってるんだろう?どうしてそんな大変なことをするんだろう?って気持ち。

つまり、たぶん自分にとってはDevOpsは普通のことなんだと思う。

チームを中心としたアプローチ

そして、チームを中心としたアプローチも、真横で見てきた。及部さんや川口さんが近くにいたからね。

彼らの活動を見たり、話を聞いたりしていくなかで「まず、チームがある」というのも、自分にとっては自然な考え方になっていた。

まぁ、そうだな。わかる

何が言いたいかっていうと、DevOpsやチーム中心のアプローチが自然な環境で育ってきたので、チームトポロジーで説明されている「ストリームアラインドチーム」は、僕も、僕の周りの人たちも、ずっと考えているチームのことだなと思った。

だから「まぁ、そうだな。わかる」と感じた。

全体を見ること

ところで、DevOpsや、フルスタックや、フルサイクル。そんな風に、より広い範囲を体験して、実感をもとにフィードバックループをすばやくまわし続けると、よりよいプロダクトづくりができる。

それはとてもよく分かる。

とはいえつらい

とはいえ、とても広い範囲を見るのは、とてもつらい。こういうのを、認知負荷が高いというのかな。

だから、プロダクトを小さくしなきゃ!というのもとてもよくわかる。大きいと手に負えない。

組織づくり

また話がとぶけど、組織の形がプロダクトに影響を与える、というコンウェイの法則は、これまでの経験から実感している。

チームの境界はコミュニケーションの境界。そこには自然にスキマができてしまう。

インタラクションの方法

だから、チーム間のスキマを超えるために、どのようなインタラクションを設計するか、も、組織づくりの一環だよなと思っている。

それによって、プロダクトやチーム(プロダクトとしてのチームかな)が影響を受ける。

境界づくり

組織づくりには、そんな風に「どうスキマを超えるか」という役割がある。それと同時に「どうスキマをつくるか」ということでもあるよなと思っている。

それは、プロダクトの境界に対してだけでなく、いろんな認知の境界に対して、そうなのかなと。

認知の境界を作ってくれて「ここまで気にしてたらいいんだよ。あとは隣のチームに任せてね」と言ってくれる。そういう役割。

それによって、認知負荷が許容範囲内に抑えられて、チームはフルサイクルのすばやいフィードバックループをまわせるようになる。

変化し続ける

この本には、その組織のスキマ超えのための要素と、上手なスキマづくりの方法が書いてある。

そして、そういった適切なスキマやインタラクションは、時間の流れとともに変わっていくということも書かれている。

だから、静的な設計をして終わりではなく、その目的やビジョンを見続けて、変化し続ける必要がある。

とてもよかった

全部が4つのチームタイプや、3つのインタラクションモードに当てはまるとは、僕はあまり思っていない。そんなにシンプルではないんじゃないかと疑っている。

でも、考える時間をとって、よく考えてみたら、実はそこにたどり着くのかもしれないなとも思っている。

それ以外ない、と言い切ってくれるというのは、とてもありがたい。そこから始められるから。そういう点で、とてもいいなと思った。

狙いを考えられて楽しい

僕自身は、組織を設計することはあまりないんじゃないかと思っている。

でも、僕の周りの人たちは、組織を設計し続けているので、それがチームトポロジーを基準として、そこに沿っているか、沿っていないか。それらの狙いはどこにあるのか。

そういったことを考えられるので楽しい。

そろそろ新大阪につくからこれくらいで!

Gitの自分用メモ:トラッキングせずにブランチを作りたい。カレントブランチのポインタを移動させたい。

気分転換といえばGit。ということでGitを触ってた。

僕がGitでよくやる操作があって、それは複数のコマンドを叩いてるんだけど、簡単にできるものが用意されてるだろうなぁとずっと思っていて、やっと調べることにした。自分用メモ。

トラッキングせずにブランチを作りたい

リモートブランチをトラッキングするのは、あまり好きではないので、切り離してブランチを作りたくて、こうしてた↓

  1. git switch -d origin/<リモートブランチ名> でDETACHED HEADの状態にする
  2. git switch -c <ローカルブランチ名> でカレントコミットから作成するとリモートブランチをトラッキングしない

手元に fastify のリポジトリがあったので、このリポジトリで試すことにする。

❯ git switch -d origin/next
Previous HEAD position was 4415c2fb chore: add github sponsor (#5293)
HEAD is now at c30992b5 feat: align fastify.hasRoute to fmw.hasRoute (#5102)

❯ git switch -c sample
Switched to a new branch 'sample'

❯ git branch -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample c30992b5 feat: align fastify.hasRoute to fmw.hasRoute (#5102)

こんなふうにして、トラッキングしないローカルブランチを作ってた。

もしくはこうしてた。

  1. git switch -c <ローカルブランチ名> -d origin/<リモートブランチ名> でトラッキングした状態になる
  2. git branch --unset-upstream でトラッキングを外す

さっきのブランチを消しておく(これ以降もやってるけどもう書かない)

❯ git switch main && git branch -D sample
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Deleted branch sample (was c30992b5).

リモートブランチからローカルブランチを作る。

❯ git switch -c sample origin/next
branch 'sample' set up to track 'origin/next'.
Switched to a new branch 'sample'

❯ git branch -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample c30992b5 [origin/next] feat: align fastify.hasRoute to fmw.hasRoute (#5102)

↑この時点では origin/next をトラッキングしてる。

❯ git branch --unset-upstream

❯ git branch -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample c30992b5 feat: align fastify.hasRoute to fmw.hasRoute (#5102)

これでトラッキングが外れた。

調べた

--no-track オプションがあった。もっとはやくに調べるべきだったよね。それはそう。

https://git-scm.com/docs/git-switch#Documentation/git-switch.txt---no-track

--no-track を使ってみよう。

❯ git switch --no-track -c sample origin/next
Switched to a new branch 'sample'

❯ git branch -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample c30992b5 feat: align fastify.hasRoute to fmw.hasRoute (#5102)

便利!エイリアスを登録した。いまさらだけど switch 自体も登録した。

❯ git config --global alias.sw switch

❯ git config --global alias.swn 'switch --no-track'

❯ git config --list | grep 'alias\.sw'
alias.sw=switch
alias.swn=switch --no-track

こんな感じで使える。便利!

❯ git swn -c sample origin/next
Switched to a new branch 'sample'

❯ git branch -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample c30992b5 feat: align fastify.hasRoute to fmw.hasRoute (#5102)

よし。これでいこ。

ついでに git branch のエイリアスも作っといた。スイッチせずにブランチを作りたいとき用。

❯ git config --global alias.br branch

❯ git config --global alias.brn 'branch --no-track'

❯ git config --list | grep 'alias\.br'
alias.br=branch
alias.brn=branch --no-track

よし。

と書きながら思ったんだけど、ブランチを指定するんじゃなくてコミットを指定したら最初からトラッキングされないね。

❯ git switch -c sample c30992b5
Switched to a new branch 'sample'

❯ git branch -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample c30992b5 feat: align fastify.hasRoute to fmw.hasRoute (#5102)

まぁ、ブランチからできたら便利なので、--no-track 使っていこう。

カレントブランチのポインタを移動させたい

カレントブランチの名前をそのままにして、別のコミットを指すように移動したいなってときも結構あって、こうしてた。

  1. git switch -d <移動先のコミット> で移動先のコミットにDETACHED HEAD状態で移動する
  2. git branch -D <ローカルブランチ> でローカルブランチを削除
  3. git switch -c <ローカルブランチ> でローカルブランチを作成
# 準備。このsampleブランチが別のコミットを指すようにしたい
❯ git swn -c sample origin/next
Switched to a new branch 'sample'

# 移動先のコミットに移動(コミットハッシュは適当に選んだ)
❯ git sw -d 79042e60
HEAD is now at 79042e60 fix: ensure `onListen` hooks are called when they should be (#5273)

# ローカルブランチを削除
❯ git br -D sample
Deleted branch sample (was c30992b5).

# ローカルブランチを作成
❯ git sw -c sample
Switched to a new branch 'sample'

❯ git br -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample 79042e60 fix: ensure `onListen` hooks are called when they should be (#5273)

どうしてこんな面倒なことを、これまで調べようとしなかったのか。

調べた

git reset --hard でできるのかー。

❯ git swn -c sample origin/next
Switched to a new branch 'sample'

❯ git reset --hard 79042e60
HEAD is now at 79042e60 fix: ensure `onListen` hooks are called when they should be (#5273)

❯ git br -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample 79042e60 fix: ensure `onListen` hooks are called when they should be (#5273)

うーん。でも、git reset --hard って作業がふっとぶ可能性があって苦手なんだよなぁ。まだコミットしてない変更があったら止まってほしいな・・・。と思ってたら、git switch-C オプションがあった。force createかー。見てみよっと。

https://git-scm.com/docs/git-switch#Documentation/git-switch.txt--Cltnew-branchgt

❯ git swn -c sample origin/next
Switched to a new branch 'sample'

❯ git sw -C sample 79042e60
Reset branch 'sample'

❯ git br -vv
  main   4415c2fb [origin/main] chore: add github sponsor (#5293)
* sample 79042e60 fix: ensure `onListen` hooks are called when they should be (#5273)

ふむふむ。いい動き。カレントブランチでも問題なく移動できてる。

で、肝心の、コミットしていない変更がある場合はどうなるんだろう?

❯ git swn -c sample origin/next
Switched to a new branch 'sample'echo "sample" >> README.md

❯ git status
On branch sample
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

これで README.md に変更がある状態になった。移動してみよう。

❯ git sw -C sample 79042e60
error: Your local changes to the following files would be overwritten by checkout:
        README.md
Please commit your changes or stash them before you switch branches.
Aborting

わー。止まってくれたー。やったー!

まとめ

自分の環境だと、こうなった。

  • トラッキングせずにブランチを作りたい → git swn -c <ローカルブランチ名> <リモートブランチ名>
  • カレントブランチのポインタを移動させたい → git sw -C <ローカルブランチ名> <移動先>

結論 → git switch はいいぞ。

DDDの実装にはあまり興味がなくなっている

以前は、DDDでどう実装したらいいかなぁって考えてたんだけど、最近は、そういうことへの興味があまりなくなっている。エンティティや値オブジェクト、集約やリポジトリなど、そのあたりにあまり興味がない。ヘキサゴナルアーキテクチャなども、そんなに考えなくなった。

TypeScriptを使うことが多いので、型でしっかり守るとかカプセル化するとか、そのあたりがどっちでもいっかという気持ちになっていることが影響してるとは思う。TypeScriptでクラスを使おうとはあまり思わないし。BrandedTypeみたいなのを使ってまで型で守ろうとは思わない。

じゃあ何に興味があるんだっけ?って考えてみると、トランザクション境界とユビキタス言語かな。

トランザクション境界

トランザクションの境界を作って、DB(RDBMS)を小さく保ちたいと思っている。DBが大きくなると、すぐに複雑になっていく感じがする。

だから、トランザクション境界を作ってDBを小さく保てば、そこに対するサービス(アプリケーション)も小さく保てるかなと。サービスが小さければその中でSQLの力を活かした素朴なコードを書いても、そこまで複雑にはならないんじゃないかなという気持ち。

トランザクション境界があると、それを超えた整合性の伝搬には非同期処理を使う必要があるなど、別の複雑さを持ち込んでしまうけど、DBが一手に担ってくれてた複雑さをどう分散させるか、そのあたりをうまくやることに今は興味がある。

ユビキタス言語

ユビキタス言語は、とても意識している。PdMが喋る言葉から、ドキュメントはもちろん、プログラムの中まで同じ言葉を使うようにしている。

たとえば「薬品」はプログラムの中でもそのまま「Yakuhin」となっている。バックエンドで変数名などに漢字を使うのは予期せぬ問題にぶつかりそうだから、ローマ字にしてる。ちなみに、フロントエンドでは日本語コンポーネントを使ってる。ぱっと見て理解できる漢字のすばらしさよ。

ある日PdMが「医薬品がどうこう」と言って、エンジニアが「お?」ってなって「薬品と医薬品は使い分けています?」「あぁ、そう言われてみればそうですね!」みたいな話をして、僕らの中では「薬品」と「医薬品」が別のものとして認識された。もし「薬品」がコードの中で「Medicine」と書かれていたら、エンジニアは医薬品と薬品の違いに気づかなかったと思う。

認識の違いを拾いたいので、会話からコードまで同じ言葉を使うようにしている。そんな風に、ユビキタス言語はとても意識している。

小さく保つ、名前にこだわる、それで複雑さに立ち向かう

という感じで、今は、影響範囲を小さく保つことと、名前にこだわることに興味を持っている。いちどに気にする範囲を小さく保って、レイヤーを分けて、意識しておくべきロジックは凝集させて、変数や関数やモジュールの名前にこだわる。1つの関数をできるだけ小さくする。依存の向きは一方向にする。イミュータブルにする。

なんかそんな普通のことを考えている。DDDでドメインの複雑さに立ち向かうことに興味があって、実装の方は別にいい感じでいいかなという気持ちになっている。

来年にはまた実装が気になってるかもしれないけどー。今はそんな気持ち。