やわらかテック

仕事の話などを書きます。執筆にはAIを使っていません

2026年の目標

新年、あけましておめでとうございます🎍
毎年恒例では全くないのですが、2026 年の目標について記しておこうと思います。 というのも、歳を取るにつれて何となく目の前の仕事や好きなことに取り組んでいるだけで良いんだっけ?という思いが強くなってきました。
また、少し前にエンジニアリングリーダーを読んだのですが、人生の全体のキャリアを考える必要も強く感じていますし、新しくチームの一員に加わった id:msksgm からの刺激もあり、はじめて自主的に目標を設定しました。

経験が少なくゼロから目標を設定するのは難易度が高いため Claude にいい感じにインタビューをしてもらうことで目標を設定しました。 Claude Code を使うために有料プランを使っているので、Sonnet 4.5 がガンガン使えてありがたい。

前提

職業のキャリアを中心に5〜10年後を見据えた計画をしています。
いつになるか分かりませんが、何となく将来的には岐阜に帰って IT の仕事をしたい(リモートワークではなく小さな自分の事業としてやりたい)という気持ちがあります。今はそのための準備をしている期間と捉えており、経験と実績を積むことを重視しています。この投稿では簡単のために期間の概念を吹き飛ばしていますが、別途、Obisidian で管理しているファイルにはマイルストーンの設定なんかもしています。えらい。

業務的なやつ

自分の関心とビジネスが win-win になるように意識してます。

  • アプリケーション品質の向上
    • E2E テストの棚卸し(Flaky テストの撲滅とテストピラミッドのバランシング)
    • リグレッションテストの再設計(初期のものから更新ができていない)
    • テスト戦略の定義・チームでの合意
    • テストガバレッジの確立(品質の数値化を検討)
      • かっこいい言葉を使えば品質のオブザーバービリティ
  • 専門的なテスト知識の獲得
    • JSTQB シラバスの学習
    • JSTQB Foundation Level 資格の取得
    • ソフトウェアテスト技法練習帳に取り組む(過去に少し触ってる)
  • 複雑なドメイン知識と向き合う
    • 医療制度(特に負担額計算領域)の理解
    • 知識をコードに落とし込む経験をより積む(eg: いわゆるDDDの実践的トライ)
  • 歴史あるコードとの向き合い知識の獲得
    • 前職でゴリゴリとコードを書いたが、歴史あるコードを安全に変更する術に弱い
    • レガシーコード改善ガイドを読む(購入済み)
    • 単体テストの考え方/使い方

掛け算の取り組み

  • 高品質なアプリケーションを実現する
    • テスト知識・歴史あるコードとの向き合い方を会得し、改善に取り組む
    • プロダクトリスクを下げて、改善しやすい状態を目指す
  • 複雑なドメイン知識の設計・実装
    • 医療制度をコードに落とし込む設計と実装
    • トレードオフを体感して、経験へと昇華する

個人的なやつ

  • 技術
    • Nvim をもっと効率的に使えるようになる
    • dotfiles を引き続き改善する
      • lua で nvim のプラグインを作れるようにする
      • 自作のプラグインを使って、怠惰な作業を排除する
    • 何かしらのイベントにプロポーサルを出す
    • TypeScript・Kotlin を引き続き学ぶ
      • 個人的に関心のある Zig も触ろうかな...
    • 個人開発のアプリケーションリリース
      • いくらかストックしているアイディアがあるので、実現する
  • 趣味
    • My Soul, Your Beats! をピアノで弾けるようにする
      • ピアノ完全初心者ですが、バイエルを1ヶ月ぐらい練習中
    • 股関節の柔軟を継続して、180度の開脚ができるようにする
    • 引き続きジムに通いコンパウンド種目に取り組む

以上です。 今年もよろしくお願いします。

昔からの道具を使う

この記事は Henry アドベントカレンダー 2025 18日目の投稿です。
昨日の記事は damさんAIが考えた思考を自分の成果物にするプロセスについて でした。本日は少し遊び心も出しながら、私 okbee が普段の生活で使っている昔からの道具たちについて紹介したいと思います。

ちょうど、1年前のアドベントカレンダーではエディタとして Neovim を使い始めた話をしており、Neovim の前身となる Vim や Vi も昔からの道具(ツール)であると言えます。こういった時代を問わず長く使われるものには、その作りや体験・機能美・インターフェースにどこか的を得たものがあり、人を魅了します。
前置きが長くなりましたが、私が使っている昔からの道具たちをご覧ください。

俺の道具達

左から土鍋・鉄瓶・南部鉄器の鉄板(グリル)です。
別に集めているわけではないのですが、気づいた時にはこのメンバーが揃っていました。 それぞれ使っている道具について用途などを紹介していきます。

土鍋(かまどさんプレミアム)

ある日、宿泊先の宿で土鍋で炊いたご飯を頂いたことがあり、その美味しさに感動しました。 炊飯器でご飯を炊くのは楽で早いですが、やはり土鍋で炊いたご飯と比べるとその差は歴然です。また、私の母方の実家では米をつくっており、珍しいハツシモという品種を育てています。祖父・祖母が一生懸命に育てた米を丁寧に頂きたい思いもあって、炊飯器を手放し土鍋を使ってご飯を炊くようになりました。

ご飯が炊けるまでに約1時間(浸水に20分、加熱が12分。蒸らしに30分)の時間がかかりますが、継続して土鍋でご飯を炊いています。 炊飯器を使っていた頃は40分もあれば、ご飯が炊けていたので20分ほどのデグレにはなりますが、アウトプットのクオリティを考えるとすんなりと受け入れられるコストです。
また、炊飯器を手放せて依存関係が減ったのも嬉しいポイント。

鉄瓶(及富あられ 0.5L)

現代の日本人は慢性的な鉄不足だそうです。
鉄不足になると貧血をはじめ皮膚病や鬱病の原因となることもあるのだとか。昔の日本人は鉄製の調理器具を使っていたこともあり、日々、鉄分を合理的に摂取していました。 肌荒れが慢性化したことをきっかけに改善案として鉄分補給のために、鉄瓶を使い始めました。

私は鉄分を多く含む食材(レバーや貝類)に苦手なものが多く、お手軽に鉄分を摂取できる鉄瓶を使っています。 毎朝、鉄瓶で沸かしたお湯を使ってお茶を淹れていますが、冬は白湯のまま飲むこともしばしば。毎朝、起きて鉄瓶でお湯を沸かすという行為が生活にリズムをもたらしてくれています。まぁ冬は寒いので中々、起きれません...

肌荒れは以前と比較すると、かなり改善されましたが、鉄瓶による効果なのかは分かりません。

南部鉄器の鉄板(鉄グリル)

前職でお世話になった上司の家に招待して頂いた際に、南部鉄器の鉄板で焼いた野菜を頂きました。 こちらもあまりの美味しさに感動してしまい、翌日には同じ鉄板を購入していました。ただ焼くだけで野菜・肉が魔法のように美味しくなります。特に旬の野菜を焼いて食べるのが絶品で、夏はナスがおすすめです。油との相性が良く、オリーブオイルを少しつけて焼くと良く仕上がります。

最初はいくらかコゲつくのですが、油ならしを繰り返すことで、かなり使いやすくなっていきます。 鉄の調理器具を育てる感覚は dotfiles を育てる感覚に近いもの (?) があり、開発者にはオススメです。まだ持っていませんが、いずれは鉄の中華鍋を買おうと目論んでいます。

丁寧に暮らそう

現代はタイパが重視される時代である上、我々のような開発者は日々、効率に追われて生きています。 そんな日々が続いていると無機質な暮らしになりがちです。しかし、ご飯を土鍋で炊いてみるだけでも何だか楽しくなります。 もし少しでも興味が湧いたのであれば、ぜひ丁寧な暮らしを取り入れてみてください。

まるで Vim を使っているような...素晴らしい体験が待っています。道具(ツール)にこだわる開発者は多いので、相性は良いんじゃないかな?と勝手に想像してます。ほぼガジェットみたいなもんです。

また、昔からの道具を使うことで、受け継がれてきた技術・文化を応援することにも繋がります。こうした素晴らしいものは次世代にも残していきたいものですね。


少しエモい話になってしまいましたが、以上です。
明日のアドベントカレンダーは yokoyamaさん です。お楽しみに!

イベントソーシングではテーブルを正規化しない理由

最近、イベントソーシングについて調べていて RDB でテーブルの正規化を行なっている実装例があまりないことに驚きました。そして、正規化をしない理由について、言及している情報があまりなかった。

イベントソーシングでは、イベントストアという発生したイベント情報を記録するテーブルを定義するのが主流であり、ペイロードはjsonblob形式で保存されます。 調査前の無知な自分は、がっつり正規化した下図のような構成を想定していました。

---
config:
    theme: forest
---
erDiagram
    models {
        UUID model_id PK
        DATETIME created_at
    }

    model_events {
        UUID model_event_id PK
        UUID model_id FK
        INT event_no
        DATETIME occured_at
    }

    model_event_A_events {
        UUID model_event_id FK "PKも兼ねる"
        UUID model_invoice_id
        STRING hogehoge
    }

    model_event_B_events {
        UUID model_event_id FK "PKも兼ねる"
        UUID model_invoice_id
        STRING foofoo
        STRING barbar
    }

    models ||--o{ model_events : has
    model_events ||--o{ model_event_A_events : has
    model_events ||--o{ model_event_B_events : has

イベントソーシングで扱いたいモデルを表現するテーブル(models)を定義、さらに対応するイベント群の親テーブル(model_events)を定義します。
そして、イベントそれぞれに対応するテーブル(例: model_event_A_events)をイベントの数だけ定義するシンプルな構成です。この構成では、対象となるモデルの実態が存在することがmodelsを見れば分かるのと、イベントの拡張に対して、テーブルを追加するだけで対応できる長所があります。

例: 銀行口座のイベントソーシング

  • accounts
  • account_events
  • (入金)acount_event_deposit_events
  • (引出)acount_event_withdrawal_events

結論

結論としては「正規化をする強い理由がない」に至りました。
個人的には特に理由がなければ RDB を使った方が良いと思いますが、求められる要件・性能によっては NoSQL などの選択はありえます。

観点

比較対象とするイベントストア。
Building an Event Storage | CQRS で紹介されているテーブル定義です。

---
config:
    theme: forest
---
erDiagram
    aggregates {
        UUID id PK
        string type
        int version
        datetime created_at
        datetime updated_at
    }

    events {
        UUID id PK
        UUID aggregate_id FK
        int version
        string type
        json payload
        datetime created_at
    }

    aggregates ||--o{ events : has

一度、発生したイベントは更新しない

イベントソーシングではイベントは一度、発生したら更新・削除することは推奨されません。 先のイベントを取り消したい場合、新たに取り消し用のイベントを発生させます。つまり、データの挿入はあれど、更新(編集・削除)はないため、正規化によるデータ整合性がメリットになり得ません

aggregateseventsでは外部キー制約を持つ点と、他のテーブルは必ずしもイベントソーシングを用いるかは分からないため、RDB を使いつつjsonblobを用いるという方針はしっくりきました。

読み込みが圧倒的に多い

アプリケーション特性にもよりますが、多くの場合、データは書き込み < 読み込みとなります。
イベントソーシングでは、先ほどのような正規化を行うとイベントの数だけJOINが必要となる対象のテーブルが増えるため、パフォーマンスが悪化していきます。マテリアライズド・ビューやスナップショットを活用する方法は考えられますが、いずれも正規化をしない設計にパフォーマンス・コストが劣ることは確定です。

-- 対象モデルのイベントを取得する
SELECT
  models.model_id,
  model_events.occured_at,
  CASE
    WHEN model_event_A_events.model_event_id IS NOT NULL THEN 'A-Event'
    WHEN model_event_B_events.model_event_id IS NOT NULL THEN 'B-Event'
    ELSE 'Unknown'
  END AS event_type,
  model_event_A_events.*,
  model_event_B_events.*
FROM
  models
  JOIN model_events ON models.id = model_events.model_id
  JOIN model_event_A_events ON model_events.id = model_event_A_events.model_event_id
  JOIN model_event_B_events ON model_events.id = model_event_B_events.model_event_id
WHERE
  models.model_id = 'some-uuid-value'
;

正規化をするメリットがない以上、パフォーマンスを悪化させる設計を選ぶ必要はありません。

複雑さとスキーマ定義

データ復元(読み込み)と書き込み時の複雑さは、テーブルのスキーマ定義に依存します。 ペイロードをjsonで扱うと、本当にデータが登録されているのか、期待する型のデータとして復元できるのかを保証することが難しくなります。
アプリケーション側(Repositoryの実装クラスなど)で、読み込み時のフィールド存在チェック・書き込み時のバリデーションなど、複雑さを吸収することになるでしょう。

RDB でスキーマ定義を丁寧に行えば、その限りではありませんが、読み込み時のパフォーマンスを考慮するとイベント単位でテーブルを分割することは避けたいです。しかし、1つのテーブルで複数イベントの構造を表現しようとすると、存在しうるフィールドを全てnullableとして定義する必要があるため、結果的にスキーマの見通しが悪くなります。

event_id event_name version occured_at aggregate_id hoge foo bar piyo
1001 EventA 1 2025-08-31 09:00:00 2001 NULL orange NULL NULL
1002 EventB 2 2025-08-31 09:05:00 2001 100 apple NULL xyz
1003 EventC 3 2025-08-31 09:10:00 2001 NULL foo1 bar1 NULL

多少、アプリケーション側の実装が複雑になってもjsonblobを使う価値はありそうです。

データ分析

jsonblobのフィールドに対してのクエリは、標準 SQL として定義されておらず RDB によってサポートされている構文・機能が異なります。 たとえば、ユーザーがどういった操作を頻繁にしているかを分析したいという要求が考えられそうですが、正規化されていないテーブルを SQL で分析するのは大変です。

とはいえ、必ずしも SQLだけで完結する必要はない(エクスポートして前処理をする)のと、分析用のdbtにデータを同期するといったアプローチも検討できるため、正規化をするかどうかの大きな理由にはなりません。

参考文献

GraphQLのQueryはどの単位で定義すれば良いのか

長らく REST API を活用してきたため、実務で GraphQL の経験がありませんでした。 特に困ったのが Query をどの単位で定義すれば良いのか?という点です。簡単そうな問題に感じますが、執筆前の自分では言語化するのが難しい状態でした。

色々と GraphQL について調べた結果、自分なりに納得できて方針が見えてきたので、備忘録としてこの記事を書いています。

リソースを元に考える

REST API では/users/postsのようにリソース単位でエンドポイントを定義します。 GraphQL でも同じように Query を定義しますが、リソースを元に考えるという観点では REST API と GraphQL に大きな違いはありません。

※ ここでいうリソースというのは、実際にはドメインモデルや DB のレコードに対応するオブジェクトなど

type Query {
  users: [User!]!
}

query GetUsers  {
  users {
    id
    name
  }
}

この時点では、REST API と比較して GraphQL では取得するデータをフィールド単位で取捨選択できる(データ取得のオーバーヘッドを削減できる)という点での優位性しかありません。 自分の考えでは、膨大な数のリクエストが来るような大規模なサービスを除き、あえて GraphQL を採用する大きな理由にはなりません。

ユースケースはクライアントが定義する

GraphQL の強みはqueryを組み合わせて、さまざまなユースケースをクライアントで自由に定義できる点です。 GraphQL では/graphqlという単一のエンドポイントでリクエストを受け付けるため、リクエスト(.query)の書き方を変更するだけで、直接には関連をもたないリソースであっても一度のリクエストで取得することができます。

こういったリソースを組み合わせれば解決するケースでは、専用のQueryをわざわざ定義するのは避けるべきです。

type Query {
  users: [User!]!
  settings: [Setting!]!
}

query GetUsersAndSettings {
  users {
    id
    name
    age
  }

  settings {
    id
    contents {
      enabled
      label
    }
  }
}

REST API の場合、シンプルさは魅力ですが、思いつくアプローチはどれも微妙です。

  • 新たなエンドポイントを定義する: ユースケースが増える度に対応が必要
  • クエリ文字列で指定する: APIの複雑化
  • /usersのレスポンスにsettingsを含める: データのオーバーフェッチ
  • bffを使う: アーキテクチャの複雑化
    • backend for frontend: データ集約を行うレイヤーを挟むアーキテクチャ

GraphQL はこの問題を見事に解決しています。
新たにクライアントで定義したリクエスト(.query)は不要になれば、捨てれば良いです。フィールドに対する Resolver などの拡張は必要ですが、ユースケースの拡張に合わせて必ずしもサーバー側の変更は必要になりません。

一方、GraphQL のトレードオフにはキャッシュ・認証の複雑化、思わぬ負荷(N+1が発生しやすい)がよく挙げられるため、注意が必要です。

複雑なユースケースはどう定義する?

前提 GraphQLはCQRS

GraphQL はデータの取得(Query)と変更(Mutation)が分離されており、いわゆる CQRS(Command Query Responsibility Segregation)のパターンに該当します。ユースケースに合わせてデータの取得方法をクライアントが定義できる点はまさに CQRS です。
しかし、backend で CQRS を扱い際に利用することが多いQuery Serviceほどの柔軟性さはなく、実際にどのような経路でデータが取得されるかは知りません(データとの物理的な距離による制約)。

リソースの組み合わせで解決できないなら、専用Queryを作るしかない

クライアントのリクエストだけで完結できない複雑なユースケースの場合、どうすれば良いでしょうか。 よくあるのは、月間のレポートを表示するといった集計処理が必要になるようなケースです。クライアント側で関連するデータなどを全件、取得して、集計処理を行うのはリソース効率の観点で考えると悪手といえます。

不必要に集計ロジックがフロントに露出してしまう点も微妙...

const query = gql`
  query GetUserActivities {
    users {
      id
      activity: {
        kind
        timestamp
      }
    }
  }
`;
const res = client.request(query);
const aggregated = res.data.users.reduce((user, accum) => { ... })

複雑なユースケースの解決策として、リソースの組み合わせで表現できない・しにくい場合は専用の Query を定義することが考えられます。 重要なのはリソースの組み合わせでは表現できない・しにくい場合のみということ。基本的にはリソースを元にクライアント主導で考えるべきで、新たな Query として切り出すのは最終手段です。 次々に専用 Query として切り出してしまうと、GraphQL の強みを生かせず管理コストが爆増します。

元となるリソースが存在しないユースケース

先ほどの複雑なユースケース(集計処理)は別の捉え方ができます。
集計結果というのはドメインモデルとして定義されているわけでもない、あるデータから変換して作られるデータです。つまり、ある Query の結果として表現されるデータであり、元となるリソースが存在しないため、別のQueryとして切り出せば良いと考えられます。

まとめ

  • 常にリソースを元に考える
  • クライアントでリソースを組み合わせて、さまざまなユースケースを定義する
  • 複雑なユースケースの場合、専用のQueryを定義することを検討する

参考文献

開発環境2025 summer

最近、色々とツール周りを更新したので、自分の開発環境を以下の記事に習って記録しておきます。 生産性への投資は本当に大事。後回しになりがちなので、しっかりと改善していきたい所 ...

blog-dry.com

各種ツールの設定は dotfiles として管理しているので、参考までに。

エディタ

NeovimVSCode(+ vscode-neovim)の二刀流です。
業務でKotlinを書くため、 IntelliJ IDEA も使っていますが、特にこだわりはないので詳細は省略します。

最近はTypeScriptを書くことが多く、ちょっとしたコーディングは Neovim で完結させることが多いですが、メインでは VSCode(+ vscode-neovim)を使っています。 NeoVim の開発効率を取り入れたい一方、Copilot なんかを Neovim に設定してまで使いたいモチベーションがないのと、ペアプロで LiveShare が使えないと困るケースが多いため、VSCode でスムーズな開発ができるようにメインエディタとして整えています。

vscode-neovim と Neovim の設定ファイルは共通化できますが、自分はあえて分けています。
捨てやすさを優先しての選択です。Neovim の設定ファイルはinit.vimになっている一方、vscode-neovimの設定ファイルはinit.luaになっているので、.luaに統一したい ...

(2025/07/19追記: 記事公開後に.luaに更新しました)

ターミナルエミュレーター

iterm2 を長らく愛用していましたが、Ghostty に乗り換えました。
特に iterm2 に不満があったわけではないですが、Zero Configuration Philosophy に惹かれました。
設定がシンプルであればあるほど良いと考えているのですが、実際に設定ファイルを書いてみて、記述量の少なさに驚きました。設定ファイルはプレーンなテキストファイルなので、git で管理できるのも嬉しいです。

自分の環境(M1・M3 Mac)ではかなりサクサクと動いています。軽量を売りにしているのも納得です。

ターミナルマルチプレクサ

相変わらず tmux を使っています。実は本格的に使い出したのは去年の夏頃です。
キーバインドに若干の扱いにくさはありますが、慣れれば問題なかったです。合わせて、開発環境を一発で立ち上げるのに tmuxinator を使っています。

ymlファイルを定義しておけば、指定のウィンドウ・ペインなどをコマンド1つで立ち上げられるのが本当にありがたい。

シェル

zshを使っています。
特にこだわりはないですが、Mac のデフォルトシェルになった頃から bash から乗り換えました。

ランチャー

Raycast を使っています。 今までランチャーを使ってこなかったんですが、生産性が爆上がりしたので、絶対使った方が良いです。 Snipet がめちゃくちゃ便利。テストデータに保険者番号と呼ばれる特定の番号を入力する必要が頻繁にありますが、,hokenと入力すれば、自動で変換してくれます。

バージョン管理

asdf から mise に乗り換えました。 不満は特になかったですが、mise は環境変数・タスクの管理まで可能です。タスクに tmuxinator のコマンドを登録して、入力の手間を省いています。

Docker container launcher

OrbStackを使っています。とにかく速い。UIがシンプルで分かりやすいです。

ウィンドウマネージャー

Rectangle(シンプルで良い)

ブラウザ

Google Chrome(ずっと使ってる。重いのがね ...)

キーボード:

Mac Book 標準のキーボードです。キーボードにこだわりないです。
過去に HHKB と NiZ を試しましたが、しっくりこなかった。どこでも仕事ができるように Mac Book だけで仕事ができるようにしておきたい思いがあります。

モニター

BenQ GW2785TC をずっと使っています。
2年ほど使っていますが、type-c 一本で出力・給電できるのは超便利です。特に不満はないですが、そろそろ買い替えても良いかも。最近はフロントを触ることが多いので、もう少し大きいサイズにしたい気持ちもあります。