「状態(フラグ)」を捨てて「事実」で判定する

1. 現場で起きたことと「フラグの違和感」 最初に頭をよぎった設計 感じた違和感 2. どう修正したか? データの定義 判定ロジック 3. この設計から得た学び 「状態(State)」ではなく「データ(事実)」を見る システム開発をしていて、こんなバグに遭遇したことはありませんか? 「処理が完了したときにフラグを戻し忘れて、次回から処理が走らなくなった」 「例外処理でキャッチした際、リセット処理がスキップされて状態がおかしくなった」 このような「フラグ(状態)の更新・リセット漏れ」は、実装時にどれだけ注意していても、コードの複雑化や将来の機能追加によっていつか必ず発生するバグの温床です。 先日、私が開発しているプロジェクトで、この 「フラグ管理の違和感」を、変数名とデータ設計の見直しだけで根本から解決できた事例 がありました。 今回は、実際の現場で何が起こり、どう修正し、そこからどのような設計の学びを得たのかをまとめます。 1. 現場で起きたことと「フラグの違和感」 開発していたのは、 「ユーザーの安否(バッテリー消費や充電器の抜き差しなどのイベント)を監視し、一定時間(例:48時間)活動が検知できない場合に自動でSOSのSMSを送信する」 という機能です。 このとき、一番気をつけなければいけないのが 「SMSの二重送信(送りすぎ)」 の防止でした。 最初に頭をよぎった設計 「一度SMSを送信したら、次にユーザーがスマホを触る(アクティブになる)までは再送を防止したい」と考えたとき、真っ先に思い浮かびがちなのが以下のようなフラグ管理です。 SMSを送信したら、isSmsSent = true にする。 ユーザーの活動を検知したら、isSmsSent = false にリセットする。 感じた違和感 一見シンプルですが、この「フラグをパタパタ切り替える設計」には、以下のような不穏な未来が目に見えていました。 「いつ、どのタイミングでこのフラグをリセット(あるいは更新)すべきなのか」をあちこちの処理で気にしなければならない。 将来、別のユースケースや例外処理が追加されたとき、 「フラグのリセット漏れによるバグ」 を引き起こす予感がする。 数ヶ月後にコードを見返したとき、「このフラグってどの状態を指してるんだっけ?」と認知のコストがかかる。 「フラグを使わずに、もっと自然な形でキレイに判定できる方法はないだろうか?」と考えました。 2. どう修正したか? フラグで「送信済み / 未送信」という状態を管理する方法は避け、 「最後に活動を検知した時刻(lastActiveTime)」と「実際にSMSを送信したシステム時刻(lastSentTime)」という、2つの「事実(時刻)」を比較する設計 に辿り着きました。 これにより、判定はシンプルな 「不等式(時間の新旧比較)」 へと進化します。 データの定義 データクラスには、フラグではなく「最後に送信した実際のシステム時刻」を持たせます。 data class AlertConfig( val id: String = UUID.randomUUID().toString(), val thresholdHours: Int, val targetContactIds: List<String>, // 💡 この設定で最後にSMSを送信した「実際のシステム時刻」 val lastSentTime: Long? = null ) 判定ロジック 「最終送信時刻が、最終活動検知時刻よりも新しければ、このセッションでは送信済み(ロック)」と判断します。 ...

2026年7月17日 · 1 分 · 奥田 智紘

AI時代に必要とされるエンジニアになるには、まず設計力を

AI の設計力・実装力は非常に高い。ただし、そのパフォーマンスは外部要因に左右される AIは魔法ではない AIの性能は設計品質に比例する AI時代だからこそ設計力が重要になる AI時代に求められるエンジニア おわりに 私は最近、Android Studio をバージョンアップしました。 AI 開発機能がさらに強化され、ますます AI 時代が進んでいることを実感しました。 一方で、対話型の AI だけで、アイデアの着想からアプリのリリースまでを、わずか 5 日で達成するという経験もしました。 この二つの出来事から、一つの考えに至りました。 重要なのは、AIをどう活用するかではなく、AIが力を発揮できる土台を作ることではないか。 生成AIの登場以来、多くの企業が「AIを使って開発効率を向上させる方法」を模索しています。 しかし、Android Studio をはじめとする開発ツール自体も、AI を活用した開発効率化を急速に進めています。 だからこそ、これから開発者にとって重要になるのは 「AIをどう使うか」 ではなく、 「AIが高いパフォーマンスを発揮できる設計」 を行うことではないかと考えました。 AI の設計力・実装力は非常に高い。ただし、そのパフォーマンスは外部要因に左右される 私はこれまで、様々な設計やアーキテクチャについて学んできました。 先日、わずか 5 日でアプリをリリースできたのも、技術的な知見だけではなく、設計を意識して開発を進められたことが大きな要因だったと考えています。 責任を適切に分離して設計すると、巨大なクラスではなく、小さく役割の明確なクラスへ分割できます。 すると、そのクラス単位で AI に相談できるようになります。 一般的に、対象となるコードや前提が複雑になるほど、AI は意図を正確に理解することが難しくなる傾向があります。 一方で、責任が明確に分離されたコードは、AI が扱う前提もシンプルになります。 その結果、AI はより高い精度で設計や実装を支援できるようになります。 つまり、エンジニアの設計品質が高いほど、AI が本来の性能を発揮しやすくなるのです。 AIは魔法ではない AI はコーディングだけでなく、設計案の提案やリファクタリングの支援も得意です。 しかし、コード全体を完全に理解し、人間が暗黙の前提として持っている知識まで正確に把握できるわけではありません。 例えば、責任が適切に分離されているコードであれば、 この UseCase を改善してください。 この Repository をリファクタリングしてください。 このクラスのテストを書いてください。 といったように、小さな責任単位で AI に依頼できます。 一方で、責任が混在した巨大なクラスでは、 UI 通信 データベース ビジネスロジック が一つのクラスに集まっていることも少なくありません。 ...

2026年7月12日 · 1 分 · 奥田 智紘

ソフトウェア設計をしていたら仕様の違和感に気が付いた話

違和感のある仕様 違和感のある仕様通りに実装した場合 現時点の動作が同じなら、前者でも後者でもいいのではないか? 解決策 まとめ ソフトウェア設計をしていたら、仕様の違和感に気が付いて、仕様を変更した話を書きたいと思います。 違和感のある仕様 ある画面 C は、画面 A と画面 B という二つの画面から遷移可能な画面でした。 画面 C には、戻るボタンがありました。 戻るボタンをタップすると、 「画面 A から遷移した場合は A に戻り、画面 B から遷移した場合は B に戻る」 という仕様でした。 この、何の変哲もない普通の仕様に、実は違和感が潜んでいました。 【補足】 今回の話は、非常に些細な点をとりあげます。 そのため、この話で出てくる例自体は、実際にはほぼ無害です。 しかし、簡単な例だからこそ、 どのようにソフトウェアが壊れていくのかを 誰が読んでもわかる例になっています。 それを知るきっかけとなるように、この記事を残します。 違和感のある仕様通りに実装した場合 Android で画面遷移を実装する場合は、 Navigation Component というライブラリを使用することが一般的です。 Navigation には、 一つ前の画面に戻る 場合には、 NavController.navigateUp() という関数が使えます。 一方で、 指定した画面まで戻る 場合には、 NavController.popBackStack(route = /* 戻りたい画面 */, inclusive = false) という関数が使えます。 仕様では、「画面 A から遷移した場合は A に戻り…」とあるので、後者の関数を使って実装します。 前者の関数を使用して実装しても、現時点では同じ動作になりますが、将来的には同じとは限りません。 例えば、 A -> D -> C という画面遷移になった場合には、 ...

2026年6月10日 · 1 分 · 奥田 智紘

演繹(えんえき)法から考える設計の本質

演繹法とは 私たちは日常的に演繹している 問題は前提が曖昧なときに起こる 設計とは何をしているのか 演繹法と設計の関係 まとめ ソフトウェア設計について考えていると、「演繹法」という言葉に出会うことがあります。 しかし、演繹法と聞くと何か特別な思考法のように感じるかもしれません。 私自身、演繹法について改めて考えてみた結果、 「普段当たり前にやっていることに名前が付いているだけではないか?」 と感じました。 この記事では、演繹法とソフトウェア設計の関係について考えてみます。 演繹法とは 演繹法とは、 一般的なルール(前提)から、個別の結論を導く考え方 です。 有名な例として、 人間はみな死ぬ ソクラテスは人間である したがってソクラテスは死ぬ というものがあります。 重要なのは、 前提が正しければ、結論も正しい という点です。 私たちは日常的に演繹している 実はソフトウェア開発者は、日常的に演繹法を使っています。 例えば、次のようなインターフェースがあったとします。 interface PaymentRepository { suspend fun save(payment: Payment) } このコードを見たとき、多くの開発者は自然に次のように考えるはずです。 save は保存処理である 保存したデータは後で取得できるはず 保存したデータが勝手に壊れてはいけないはず これはすべて、 save は保存する ↓ だから○○であるべき という演繹です。 特別なことをしているわけではありません。 普段の設計レビューやコードレビューで当たり前に行っている思考です。 問題は前提が曖昧なときに起こる ただし、演繹法には重要なポイントがあります。 それは、 前提が明確であること です。 例えば、次のようなインターフェースを考えます。 interface PaymentRepository { suspend fun delete(id: Int) } これを見た多くの人は、 delete は削除処理である 削除したデータは取得できなくなるはず と考えるでしょう。 しかし、実際には仕様として、 論理削除を行う というルールが存在するかもしれません。 この場合、 delete は物理削除である という前提を、利用者側が勝手に補っていたことになります。 つまり、問題は演繹法そのものではなく、 前提が曖昧であること ...

2026年6月10日 · 1 分 · 奥田 智紘

UI 状態の分割アイデア

前置き 結論 実装例 UI 状態の定義 UI 状態の内部に保持する各データクラスの定義 各 StateFlow の定義 combine 関数で UI 状態を生成する ポイント 4 つの UI 状態に制限したこと どんなプロパティでも UI 状態に定義した 4 つのプロパティにだいたい収まる まとめ 前置き Jetpack Compose では、一つの画面で、 UI 状態を一つだけ定義しておくことが一般的かと思います。 画面が複雑になっていき、 UI のプロパティが増えていくと、 combine 関数で Flow を合成できなくなることがあります。 これは、 combine 関数の引数が最大でも 5 であるため、それよりたくさんの Flow を直接 combine することができないためです。 そのような場合の一つの対応案のここに残します。 結論 UI 状態のプロパティを以下の 4 つのプロパティにまとめます (制限します) 。 UI ローカル状態 フォーム入力状態 派生 UI 状態 非同期取得データ これらをすべて StateFlow で定義し、データの変更を監視します。 データの変更が検出されるたびに、 combine 関数で StateFlow 内の実データを UI 状態のプロパティにセットします。 ...

2026年6月9日 · 3 分 · 奥田 智紘

Kotlin では null を避けるべきか?実はすごく便利だった

今回のケース ふと気付いたこと Kotlinは「nullを排除する言語」ではない 今回は nullable の方が自然だった sealed class を使うべきケース 学び Kotlin を書いていると、「null をできるだけ使わない」という考え方をよく目にします。 私自身も、状態を表現するときは sealed class を積極的に使うようにしています。 しかし、ある画面の選択状態を実装しているときに、「本当に nullable を避けるべきなのか?」と考える機会がありました。 今回のケース 支払先を選択する画面があり、選択状態を以下のように表現していました。 sealed interface SelectionState { data object None : SelectionState data class Selected(val id: Int) : SelectionState } 未選択なら None、選択済みなら Selected(id) です。 一見すると型安全で分かりやすそうです。 しかし UI 側では、 when (val selectionState = uiState.selectionState) { is SelectionState.Selected -> { onClickSave(selectionState.id) } SelectionState.None -> Unit } のような分岐が必要になります。 さらに、UIState の中から選択された ID を取得したい場面でも、毎回 when が必要になります。 ふと気付いたこと 今回の状態は、 未選択 選択済み(IDあり) の2パターンしかありません。 これを別の形で書くと、 Int? と同じ情報量です。 ...

2026年6月6日 · 1 分 · 奥田 智紘

型で保証できるものとできないもの

普段私は Kotlin の型システムを活用して、コンパイル時に不正な状態を表現できない設計を心がけています。 例えば、 sealed class で状態を表現する value class で ID をラップする null を型で排除する といった方法です。 しかし、あるとき「存在する PaymentId しか関数に渡せないようにできないか?」と考えました。 結論から言うと、これは型だけでは保証できません。 なぜなら、 fun observePayment(id: PaymentId) という関数に渡された ID が実際にデータベースに存在するかどうかは、コンパイル時には分からないからです。 型で保証できるのは、 null ではない 状態の種類 値の構造 不変条件 など、コンパイル時に判定できる性質 (コンパイル時に確定する状態) です。 一方で、 DB にレコードが存在する ファイルが存在する API が応答する 他ユーザーによって削除されていない といったものは実行時の状態に依存するため、型だけでは保証できません。 当たり前と言えば当たり前なのですが、今回再確認したことは 型で保証できるのは「コンパイル時に確定できること」 実行時の状態に依存することは、実行時チェックで保証する ということでした。 例えば「一覧画面から選択された ID なので存在するはず」という前提がある場合でも、その後、別の画面からそのデータが削除されている可能性もあるため、 ID が存在していない可能性もあります。 その場合は、 Repository や UseCase で requireNotNull() を使ったり、独自例外を投げたりして不整合を検知することになります。 型安全な設計を考える際は、「これはコンパイル時に分かる情報か?それとも実行時にしか分からない情報か?」を意識すると、どこまでを型で表現すべきか判断しやすくなります。

2026年6月3日 · 1 分 · 奥田 智紘

「起きたこと」で名前を付けるか、「意図」で名前を付けるか

結論 UI → ViewModel は「起きたこと」 ViewModel → UI は「意図」 実際に違和感が生まれた例 「クリックされたもの」ではなく「やりたいこと」に着目する すべてを意図ベースにする必要はない まとめ アプリ開発をしていると、 OnClickPayment OnClickDelete OpenPaymentEdit ShowDeleteDialog のように、イベントや関数の名前をどう付けるべきか迷うことがあります。 特に ViewModel を使った設計では、 「起きたこと」を名前にするべきか 「やりたいこと(意図)」を名前にするべきか で悩むことがよくあります。 結論 私は次のように考えています。 UI → ViewModel は「起きたこと」 ViewModel → UI は「意図」 で名前を付けると整理しやすくなります。 ただし、この方針は絶対ではありません。 この方針を破ったほうが自然な名前になる場合も多々あることに注意してください。 UI → ViewModel は「起きたこと」 例えば Compose の画面から ViewModel にイベントを伝える場合です。 viewModel.onClickPayment() viewModel.onClickDelete() viewModel.onTextChanged(text) ここで ViewModel に伝えたいのは、 「ユーザーが何をしたか」 です。 まだ ViewModel は、その結果として何を行うかを決めていません。 そのため、 fun onClickPayment() fun onClickDelete() fun onTextChanged(text: String) のような名前が自然です。 ViewModel → UI は「意図」 一方で、ViewModel から UI に送るイベントは少し性質が異なります。 ...

2026年6月2日 · 2 分 · 奥田 智紘

UiState にドメインモデルをどこまで持たせるべきか考えた

結論 なぜそう考えたのか ID は比較的「安定した概念」 本当に危険なのは「変化しやすいドメインモデル」 一方、ID は変化しにくい むしろ UI専用 ID を増やすデメリットもある 最近は「全部分離」が正義ではないと感じる まとめ Jetpack Compose で画面実装をしていると、よく悩むのが、 UiState にどこまでドメインモデルを持たせるべきか UI専用モデルをどこまで作るべきか という問題です。 最近、自分は Id の扱いについて考える機会がありました。 きっかけ たとえば、以下のような UiState があるとします。 data class UiState( val id: Id = Id.Unassigned, val name: String = "", ) { sealed interface Id { data object Unassigned : Id data class Assigned(val value: Int) : Id } } 一方で、ドメインモデル側にも、ほぼ同じ定義があります。 sealed interface PaymentId { data object Unassigned : PaymentId data class Assigned(val value: Int) : PaymentId } さらに、今後ほかの画面でも、 PaymentEditUiState.Id PaymentDetailUiState.Id PaymentListItemUiState.Id のような、似た ID 型が増えていく可能性がありました。 ...

2026年5月26日 · 2 分 · 奥田 智紘

リポジトリがドメインに依存しすぎた話

題材 リポジトリがドメインに依存するのはありか? ドメインを知りすぎた実装 ドメインを知りすぎない実装 今回は、リポジトリがドメインに依存しすぎた話を書きたいと思います。 題材 題材は、口座振替管理アプリで、ローカルの DB にデータを保存する場合の話です。 支払情報編集画面という画面があり、この画面では、データの新規登録と既存データの更新が可能になっています。 この「新規登録」処理と「更新」処理を行う際に、リポジトリはどこまでドメインを知っていていいのか?という話になります。 リポジトリがドメインに依存するのはありか? まず、クリーンアーキテクチャ的には、リポジトリがドメインに依存することは間違ってはいません。 ただし、どこまで知っていてよいのかは検討の余地があるというのが今回の学びです。 ドメインを知りすぎた実装 まずは、ドメインを知りすぎた良くない実装を紹介します。 以下は、支払情報を表す Payment オブジェクトです。 sealed interface Payment { val name: PaymentName val payerId: PayerId data class Persisted( val id: PaymentId, override val name: PaymentName, override val payerId: PayerId = PayerId.NONE, ) : Payment data class InMemory( override val name: PaymentName, override val payerId: PayerId = PayerId.NONE, ) : Payment } 編集の場合は、すでにデータが永続化されているため、 Persisted オブジェクトで表現します。永続化済みのデータは ID を持っています。 新規登録の場合は、 ID を持っていないため InMemory オブジェクトで表現します。 リポジトリ側には、この Payment オブジェクトが渡され、このオブジェクトの実態が Persisted なのか、 InMemory なのかによって、新規作成か、更新かを分けています。 ...

2026年4月2日 · 1 分 · 奥田 智紘

Jetpack Compose Navigation のパラメータ指定の罠

すぐに思いつく実装 「新規作成 or 編集」を型で表現する なぜエラーになるのか 正しい設計:Navigation はプリミティブ型を渡す 重要なポイント Navigationの責務 UI の責務 一覧画面から「新規作成」と「編集」の両方に遷移するケースは、アプリ開発でよくあります。 例えば以下のような仕様です: リストのアイテムをタップ → 編集画面へ 「追加」ボタンをタップ → 新規作成画面へ 画面UIは同じ(内部の動作だけ異なる) すぐに思いつく実装 すぐに思いつく実装方法は以下ではないでしょうか? Navigation の定義は以下の通り NavHost( navController = navController, startDestination = startDestination, modifier = modifier, ) { // リスト画面 composable<ItemList> { PayeeListScreen( onClickAdd = { navController.navigate(RegisterItem(null)) }, onClickItem = { itemId -> navController.navigate(RegisterItem(itemId)) } ) } // 登録・編集画面 composable<RegisterItem> { backStackEntry -> val registerItem: RegisterItem = backStackEntry.toRoute() RegisterItemScreen( onClickNavigateUp = { navController.navigateUp() }, itemId = registerItem.id ) } Destination の定義は以下の通り ...

2026年3月22日 · 1 分 · 奥田 智紘

ワイヤーフレームからドメイン設計が進んだ話

はじめに ドメイン設計だけでは埋まらない違和感 ワイヤーフレームを作って起きた変化 1. ユーザーの行動が具体化される 2. 必要なデータと制約が見えてくる 3. ドメイン設計の「抜け」が露出する ワイヤーフレームは見た目のためではない まとめ おわりに はじめに 最近、アプリ開発の中で「ワイヤーフレーム」を使い始めたところ、思いがけずドメイン設計が大きく前進しました。 ワイヤーフレームとは、 Figma などを使って作る UI デザインの一種です。 ただし、色やフォントサイズなどの細かい UI にはこだわりません。 ボタンや入力欄であることがわかれば良いという程度のラフな UI デザインのことを言います。 もともとドメイン駆動で設計を進めていたのですが、どうしても「抜け」や「曖昧さ」が残る感覚があり、手応えが弱い状態が続いていました。 しかし、ワイヤーフレームを作り始めたことで、その状況が一変しました。 この記事では、 ワイヤーフレームがどのようにドメイン設計を補完し、加速させたのかを整理してみます。 ドメイン設計だけでは埋まらない違和感 ドメイン設計を進めていると、以下のような状態に陥ることがありました。 ユースケースはある程度整理されている エンティティや値オブジェクトも定義できている しかし「本当にこれで使えるのか?」という違和感がある つまり、構造はあるが、実感がない状態です。 このとき不足していたのは、「ユーザーがどう操作するか」という視点でした。 ワイヤーフレームを作って起きた変化 試しにワイヤーフレームを作り始めたところ、明確な変化がありました。 1. ユーザーの行動が具体化される ワイヤーフレームでは「画面上で何をするか」を考える必要があります。 どこで入力するのか どの順番で操作するのか 何を確定とみなすのか これにより、ユースケースが単なる文章ではなく、操作として具体化されました。 2. 必要なデータと制約が見えてくる 操作を具体的にすると、自然と以下が見えてきます。 この画面では何のデータが必要か この操作はどんな条件で許されるのか ここで重要なのが、 不変条件(インバリアント) です。 例えば、口座振替管理アプリなら、 支払い元は必ず 1 つである 振替は「元 → 先」の関係を持つ 不正な組み合わせは作れない など、これらはドメイン設計だけでも定義できますが、 ワイヤーフレーム上で「操作」として考えることで、一気に現実味を帯びます。 3. ドメイン設計の「抜け」が露出する ワイヤーフレームを作る中で、次のような気づきが頻発しました。 この状態、どうやって作る? この操作、どのユースケースに対応する? このデータ、どこに属する? つまり、ドメイン設計で曖昧だった部分が強制的に表に出てくるのです。 ...

2026年3月18日 · 1 分 · 奥田 智紘

DDDで「集約はIDで参照する」とはどういう意味か

オブジェクト参照の場合 Order User アプリケーションサービス ID参照の場合 Order User アプリケーションサービス 違いを整理 オブジェクト参照 ID参照 もう一つ重要な違い:イベント駆動に拡張できる Application Service イベントの定義 EventBusの簡易実装 イベントハンドラ ハンドラ登録 イベントの流れ まとめ ドメイン駆動設計(DDD)では、次のようなルールをよく目にします。 集約は他の集約を IDで参照する しかし、初めてこのルールを見たときに疑問が浮かびます。 ID参照にしても結局 Repository.find(id) で取得できるのでは? それなら普通のオブジェクト参照とあまり変わらないのでは? この記事では、このルールの意味を 実際のコードで比較しながら説明します。 オブジェクト参照の場合 まず、他の集約を オブジェクトとして直接持つ設計を見てみます。 Order class Order( val id: OrderId, val user: User ) { fun place() { // 注文処理 user.upgradeToPremium() } } tips : place という単語には、 注文を出す / 発注する という意味があります。 User class User( val id: UserId, var plan: Plan ) { fun upgradeToPremium() { plan = Plan.PREMIUM } } アプリケーションサービス fun placeOrder(orderId: OrderId) { val order = orderRepository.find(orderId) order.place() orderRepository.save(order) } ここで起きていることを図にすると次のようになります。 ...

2026年3月12日 · 2 分 · 奥田 智紘

一つの集約にまとめるべきか、別の集約に分けるべきかの判断基準(DDD)

1. 不変条件を同時に守る必要があるか(最重要) 例 別集約になる例 2. ライフサイクルが一緒か 例 別集約の例 3. 集約は他の集約をIDで参照する 4. 同時に更新されるか 例 5. 集約は小さく保つ まとめ ドメイン駆動設計(DDD)で設計をしていると、次のような疑問に必ずぶつかります。 このエンティティは同じ集約に入れるべきか? それとも別の集約として切り離すべきか? 集約の境界は、DDDにおいて非常に重要な設計判断です。 この記事では、実務でよく使われる判断基準を整理します。 1. 不変条件を同時に守る必要があるか(最重要) 最も重要な基準は 不変条件(Invariant) です。 「このルールは常に同時に成立している必要があるか?」 YESなら 同じ集約 NOなら 別集約 例 注文と注文明細 Order └ OrderItem 不変条件 注文の合計金額 = 各注文金額の合計 注文明細の数量 ≥ 1 数量が変われば、注文全体の合計金額は変わります。 つまり、これらは 常に同時に成立している必要があります。 そのため Order と OrderItem は 同じ集約になります。 別集約になる例 Order Inventory (在庫) ルール 注文が確定したら在庫を減らす この処理は 注文確定 在庫更新 の2つの処理に分かれます。 これらは 同時トランザクションでなくても問題ないため Order集約 Inventory集約 と分けます。 2. ライフサイクルが一緒か 次に考えるのは ライフサイクルです。 「一緒に生まれて一緒に消えるか?」 YES → 同じ集約 NO → 別集約 例 Order └ OrderItem OrderItem は ...

2026年3月12日 · 1 分 · 奥田 智紘

ドメイン設計テンプレート

このドキュメントは、DDD(ドメイン駆動設計)に基づいて、ソフトウェアを設計するためのテンプレートです。 上から順番にテンプレートを埋めていくことで、 DDD に基づいたドメイン周りの設計がスムーズに行えるように作っています。 1. システム概要 システムの目的 対象ユーザー 解決したい課題 2. ユースケース ユースケース一覧 ユースケース詳細 【例】振替関係を作成する 入力 処理概要 出力 3. ユビキタス言語 概念 候補とその言葉のイメージ 賛否の意見 言葉の決定とその採用理由 ユビキタス言語辞書 4. エンティティ候補 5. 不変条件(Invariant) 6. 状態遷移 【例】TransferRelation 状態 状態遷移 7. 集約設計 集約一覧 集約詳細 【例】TransferRelation 属性 不変条件 ドメインメソッド 8. ドメインサービス サービス一覧 サービス詳細 【例】TransferService 9. リポジトリ 10. ユースケース設計 UseCase一覧 UseCase詳細 【例】CreateTransferRelationUseCase 処理 1. システム概要 システムの目的 このシステムは何を解決するものか。 【例】 フリーランスの固定費を管理する 口座振替の関係を可視化する 対象ユーザー このシステムのユーザー。 【例】 個人 フリーランス 企業 解決したい課題 【例】 固定費の支払い関係が分かりにくい 口座振替の全体構造を把握できない 2. ユースケース ユーザーがシステムで行う操作を列挙する。 ...

2026年3月11日 · 2 分 · 奥田 智紘

ドメイン設計テンプレート(補強版)

このドキュメントは、 ドメイン設計テンプレート に以下の項目を加えた補強版となっています。 4.境界コンテキスト 5.コンテキストマップ 7.値オブジェクト 12.ドメインイベント 15.集約境界図 16.不変条件の責任 1. システム概要 システムの目的 対象ユーザー 解決したい課題 2. ユースケース ユースケース一覧 ユースケース詳細 【例】振替関係を作成する 入力 処理概要 出力 3. ユビキタス言語 概念 候補とその言葉のイメージ 賛否の意見 言葉の決定とその採用理由 ユビキタス言語辞書 4. 境界コンテキスト 5. コンテキストマップ 6. エンティティ候補 7. 値オブジェクト 8. 不変条件(Invariant) 9. 状態遷移 【例】TransferRelation 状態 状態遷移 10. 集約設計 集約一覧 集約詳細 【例】TransferRelation 属性 不変条件 ドメインメソッド 11. ドメインサービス サービス一覧 サービス詳細 【例】TransferService 12. ドメインイベント イベント詳細 【例】TransferRelationCreated 発生タイミング ペイロード 13. リポジトリ 14. ユースケース設計 UseCase一覧 UseCase詳細 【例】CreateTransferRelationUseCase 処理 15. 集約境界図(Aggregate Boundary) 【例】TransferRelation 集約名 / Aggregate Root 内部エンティティ 値オブジェクト 集約境界 外部参照 集約ルール 16. 不変条件の責任(Invariant Responsibility) 不変条件一覧 不変条件の責任 振替日は1〜31 同じ口座 + サービスの振替関係は1つだけ 振替関係は必ず口座を持つ 振替関係は必ずサービスを持つ 不変条件の配置ルール 例 1. システム概要 システムの目的 このシステムは何を解決するものか。 ...

2026年3月11日 · 3 分 · 奥田 智紘

DDDにおける「集約」「ドメインサービス」「ユースケース」の違い

DDDにおける「集約」「ドメインサービス」「ユースケース」の違い まず結論 集約(Aggregate) ドメインサービス ユースケース(Application Service) ドメインロジックをどこに置くか 状態を持つドメインサービスはどうなるか ケース1:新しいドメイン概念が生まれた ケース2:一時的な処理状態 判断のための質問 よくある失敗 まとめ DDDにおける「集約」「ドメインサービス」「ユースケース」の違い DDD(ドメイン駆動設計)を学び始めると、多くの人が次の疑問にぶつかります。 集約とドメインサービスは何が違うのか ユースケースはどこまで責任を持つのか ドメインロジックはどこに書くべきなのか この記事では、この3つの概念を整理しながら、ドメインロジックをどこに置くべきかの判断基準を解説します。 まず結論 3つの役割は次のように整理できます。 集約(Aggregate) ドメインの状態を持つ 不変条件を守る ドメインモデルの中心 ドメインサービス(Domain Service) エンティティに属さないドメインロジック 複数の集約にまたがる処理 ユースケース(Use Case / Application Service) ユーザー操作を実現する手順 ドメインモデルを組み合わせる 依存関係は次のようになります。 UI ↓ UseCase ↓ DomainService ↓ Aggregate 重要なルールは 内側の層は外側を知らないこと です。 集約(Aggregate) 集約は 状態と不変条件を守るドメインモデル です。 例として「口座振替」を考えます。 振替関係には次のルールがあります。 振替日は 1〜31 「振替元 + 振替先」が同じ振替関係は 1 つだけ (重複不可) このルールを守る主体が TransferRelation です。 class TransferRelation( val accountId: AccountId, val serviceId: ServiceId, private var paymentDay: PaymentDay ) { fun changePaymentDay(newDay: PaymentDay) { paymentDay = newDay } fun stop() { // 状態変更 } } 【集約の特徴】 ...

2026年3月10日 · 2 分 · 奥田 智紘

DDDでドメインを設計するときの流れ

DDDでドメインを設計するときの流れ ドメイン設計の全体の流れ この記事の前提 1. ユースケースを書き出す 2. ドメイン用語を整理する(ユビキタス言語) 3. エンティティ候補を出す 4. 不変条件を書く 5. 状態遷移を整理する 6. 集約(Aggregate)を決める 7. エンティティの責務を設計する setterが問題になる理由 ドメインメソッドは「操作」を表す 重要な考え方 8. ドメインサービスを定義する 9. リポジトリを定義する 10. 最後にUIを作る まとめ この流れで設計する際のテンプレート DDDでドメインを設計するときの流れ ― 実践的な設計手順 ― ドメイン駆動設計(DDD)を学び始めると、多くの人が次の疑問を持ちます。 ドメイン設計は何から始めればいいのか 実際の開発ではどのような手順で設計するのか DDDの本では概念の説明が多く、「実際の設計手順」がはっきりしないことがあります。 この記事では、実務で使える ドメイン設計の具体的な手順 を整理して紹介します。 ドメイン設計の全体の流れ DDDでドメインを設計する場合、次の順序で進めると整理しやすくなります。 ユースケースを書き出す ドメイン用語を整理する(ユビキタス言語) エンティティ候補を出す 不変条件を書く 状態遷移を整理する 集約(Aggregate)を決める エンティティの責務を設計する 必要ならドメインサービスを作る リポジトリを定義する 最後にUIを設計する 重要なのは UIから設計を始めないこと です。 DDDでは、基本的には、 ユースケース → ドメイン → UI という順序で設計します。 実際には、一度でバシッと決められるわけではないため、ドメインを設計しているときに、ユースケースに戻るなど、各プロセス間を何度も行ったり来たりします。 この記事の前提 この記事では、口座振替管理アプリの開発を想定して、設計の流れを説明してきます。 振替元と振替先をユーザーが紐づけるメモアプリを想像してください。 1. ユースケースを書き出す 最初に、システムでユーザーが行う操作を書き出します。 ポイントは UIではなく行動を書くこと です。 ...

2026年3月10日 · 2 分 · 奥田 智紘

リファクタリングには二種類ある

リファクタリングには二種類ある ― 構造を変えるものと、概念を変えるもの まず整理:構造と概念の違い 構造の変更 概念の変更 なぜこの区別が重要なのか? なぜ通常は「構造 → 概念」なのか? ① 変更の安全性 ② デバッグのしやすさ ③ 複雑性の増幅 実務での使い分け(私の優先順位) 第1段階:可視化 第2段階:構造整理 第3段階:責務の明確化 第4段階:概念強化 まとめ リファクタリングには二種類ある ― 構造を変えるものと、概念を変えるもの 私は最近の失敗から、大きな学びを得ました。 それは、 リファクタリングには二種類ある ということです。 構造 を変えるリファクタリング 概念 を変えるリファクタリング この区別を意識していなかったことが、バグの原因でした。 この記事では、自分なりに整理した実務的な優先順位と共にまとめます。 まず整理:構造と概念の違い 構造の変更 関数抽出 共通化 重複削除 名前変更 ネスト解消 責務分離 振る舞いは変えない(意味は同じ) これは「コードの形」を整える作業です。 概念の変更 Boolean → sealed class nullable → 非null設計 エラー型明確化 状態遷移の型化 エンティティ再設計 ドメインモデル再定義 責務の再解釈 プログラムが表現する「意味」が変わる これは「コードが何を表現しているか」を変える作業です。 なぜこの区別が重要なのか? 私は「成功」という概念を Boolean で扱っていました。 しかし実際には: 作成成功 更新成功 失敗 という複数の意味がありました。 Boolean に押し込めたことで、 意味の差が見えなくなり、無意識に共通化してしまった のです。 ...

2026年3月4日 · 1 分 · 奥田 智紘

Boolean に潰された「状態」

Boolean に潰された「成功」 ― リファクタリングで気づいた抽象化の罠 はじめに 元のコード リファクタリングでやったこと 最初は「うっかり」だと思った 本当の原因 抽象化の粒度が粗すぎた 型で守る設計 学び 終わりに Boolean に潰された「成功」 ― リファクタリングで気づいた抽象化の罠 はじめに ViewModel の保存処理をリファクタリングしていたとき、 私は一度コードを壊しました。 原因は単純な「うっかりミス」に見えました。 しかし振り返ってみると、問題はもっと深いところにありました。 それは、 「成功」という概念を Boolean に潰してしまったこと でした。 この記事では、その過程と学びを書きます。 元のコード 保存処理は、新規作成と更新で分岐していました。 private fun saveData() { viewModelScope.launch { val isExistingItem: Boolean = (取得) if (destId == 0) { // 新規作成 val resultSuccess = directDebitDefRepo.createDestination(...) if (resultSuccess) { // フォームの初期化 _formInputState.update { FormInputState() } showSuccess() } else { showFailure() } } else { // 更新 val resultSuccess = directDebitDefRepo.updateDestination(...) if (resultSuccess) { showSuccess() } else { showFailure() } } } } ポイントはここです。 ...

2026年3月4日 · 2 分 · 奥田 智紘

Aggregate Root とは

Aggregate Root とは はじめに Aggregate(集約)とは Aggregate Root(集約ルート)とは 具体例(イメージ) 注文ドメインの例 なぜ Aggregate Root が必要なのか ① 整合性を守るため ② トランザクション境界を明確にするため ③ 依存関係をシンプルにするため 設計時の注意点 ① Aggregate を大きくしすぎない ② 他 Aggregate を直接参照しない ③ Repository は Aggregate Root 単位 よくある誤解 まとめ Aggregate Root とは はじめに DDD(Domain-Driven Design)の文脈で頻繁に登場する Aggregate Root(集約ルート) について解説していきます。 本記事では、 Aggregate / Aggregate Root とは何か なぜこの概念が必要なのか 設計時の注意点 を整理します。 Aggregate(集約)とは Aggregate とは、 関連する複数の Entity / Value Object を 一つのまとまり(整合性の境界) として扱う DDD の設計単位です。 重要なのは、 Aggregate は「常に一貫した状態を保つべき単位」 という点です。 ...

2026年2月12日 · 2 分 · 奥田 智紘

SQL の条件を固定しない

SQL の条件を固定しない はじめに 改善前の実装 DAO にドメインの意味が入り込んでいた 問題意識 改善方針 改善後の実装 DAO:検索軸だけを知る Repository / UseCase:意味を与える この設計で得られたメリット 1. SQL / DAO の数が増えにくくなった 2. DAO の責務が明確になった 3. ドメインの変更に強くなった おわりに SQL の条件を固定しない DAO の責務を整理して、ドメインに意味を寄せる設計改善 はじめに Android アプリ開発において、 「とりあえず動く」状態から一段上の設計に進もうとすると、 ViewModel・UseCase・Repository・DAO の責務の境界で悩むことが多い。 今回は、 SQL で条件を固定していた実装を見直し 条件をパラメータ化して SQL の数を減らし その「意味」を Domain / UseCase 側で与える という設計改善を行ったので、その考え方をまとめる。 改善前の実装 DAO にドメインの意味が入り込んでいた もともと DAO には、次のような Query が定義されていた。 @Query("SELECT * FROM transfer_item WHERE isSourceItem = 1") fun observeSources(): Flow<List<TransferItemEntity>> この実装はシンプルで分かりやすいが、次の問題を抱えていた。 DAO が「Source(振替元)」というドメインの意味を知っている 条件が増えるたびに SQL / DAO メソッドが増える 将来的に Query が爆発する兆候がある 問題意識 ここで違和感を覚えたのは、次の点だった。 ...

2026年2月11日 · 2 分 · 奥田 智紘

「サービスクラス」とは何か?

「サービスクラス」とは何か? 「サービス」という言葉の語源と基本ニュアンス Web / サーバーサイドでの Service クラスの起源 なぜ「サービスクラス」が問題視されるようになったのか DDD における「Service」との分岐 UseCase は何に対するアンチテーゼなのか Web エンジニアが「サービスクラス」と言うときの意味 1. 中立的な意味 2. 否定的な意味(設計議論) まとめ 「サービスクラス」とは何か? ──語源・ニュアンス・UseCaseとの違いを整理する 設計の議論をしていると、「それ、サービスクラスじゃない?」という言葉が出てくることがある。 一方で、Web エンジニアの中には「Service クラス」は普通に使う用語だ、という人も多い。 この「サービスクラス」という言葉は、厳密な定義がなく、文脈によって意味が変わるため、混乱を生みやすい。 この記事では、語源と歴史をたどりながら、 なぜ「サービス」と呼ばれるのか Web ではどのように使われてきたのか なぜ設計議論では否定的に使われることがあるのか UseCase と何が違うのか を整理する。 「サービス」という言葉の語源と基本ニュアンス 英語の service は、 奉仕 提供 役務 といった意味を持つ。 ソフトウェア設計においては、そこから転じて、 「他のオブジェクトのために処理を提供するもの」 という、かなり広い意味で使われるようになった。 この時点では、「サービス」はあくまで 役割を説明するための便利な言葉であり、設計上の厳密な概念ではない。 Web / サーバーサイドでの Service クラスの起源 「サービスクラス」という言葉が広く使われるようになったのは、 Java EE や Spring に代表される レイヤードアーキテクチャの文脈である。 典型的な構成は以下のようなものだ。 Controller ↓ Service ↓ Repository (DAO) ここでの Service クラスは、 Controller から呼ばれる 複数の Repository を組み合わせる トランザクション境界になる 業務ロジックを書く場所 という役割を担っていた。 ...

2026年2月11日 · 1 分 · 奥田 智紘

ViewModel が肥大化する理由

ViewModel が肥大化する理由 よくある誤解:ViewModel は「中継役」だから重くなる ViewModel が肥大化する本当の理由 「UI の判断」と「業務の判断」は別物 UseCase が登場する理由 ViewModel と UseCase の境界線 「ViewModel から処理がほとんど消えそう」問題 まとめ ViewModel が肥大化する理由 Android アプリを作っていると、ある日ふと気づきます。 ViewModel、でかくなりすぎじゃない? State の定義、Flow の合成、エラーハンドリング、変換ロジック、画面固有の分岐……。 気づけば 1 ファイルに数百行。しかも「どこを直すと何が壊れるのか分からない」状態。 この記事では、なぜ ViewModel は肥大化しやすいのか、そして それを防ぐための本質的な境界の引き方 について整理します。 よくある誤解:ViewModel は「中継役」だから重くなる よく言われる説明に、こんなものがあります。 ViewModel は UI と Domain の橋渡しだから Flow や State を扱うから 非同期処理が集まりやすいから どれも一理ありますが、本当の理由ではありません。 Flow があるから肥大化するのではなく、 ViewModel が 本来持つべきでない判断 を持ち始めたときに肥大化します。 ViewModel が肥大化する本当の理由 結論から言うと理由はシンプルです。 「何をするか」を ViewModel が決め始めるから 本来の ViewModel の役割は、次の 2 つです。 UI からのイベントを受け取る UI が描画しやすい State に変換して公開する ところが実際には、次のような責務が入り込みがちです。 ...

2026年2月9日 · 1 分 · 奥田 智紘

Domain と UseCase の違いを整理してみる

Domain と UseCase の違いを整理してみる 一言で言うと何が違うのか Domain とは何か Domain は「意味」と「ルール」の集合体 Domain の例 Domain がやらないこと UseCase とは何か UseCase は「動詞の層」 UseCase の例 Domain と UseCase の関係 なぜ UseCase が分かりにくいのか UseCase を作るべきタイミング Entity → Domain 変換はどこでやるべきか Domain と UseCase を分ける最大のポイント よくあるアンチパターン おわりに Domain と UseCase の違いを整理してみる Clean Architecture や DDD を学んでいると、ほぼ確実に次の疑問にぶつかります。 Domain と UseCase の違いがよく分からない どこまでが Domain で、どこからが UseCase なのか曖昧 UseCase を作ろうとすると、何を書けばいいのか分からない 私自身、このあたりで何度も立ち止まりました。 この記事では、実装経験を通して整理できた Domain と UseCase の違い を、自分なりの言葉でまとめてみます。 一言で言うと何が違うのか まず、かなり大胆に要約します。 Domain →「この世界では、何が正しく、何が成り立つか」を表す UseCase →「その正しさを、どういう手順・文脈で使うか」を表す ...

2026年2月9日 · 2 分 · 奥田 智紘

Domain レイヤーかどうかを判断する基準

Domain レイヤーかどうかを判断する基準 Domain レイヤーとは何か(簡単に) 判断基準:ユーザーがそれを意識するか? 例1:保存処理は Domain か? ケースA:保存先をユーザーが意識しない場合 ケースB:保存先をユーザーが意識的に選択する場合 「ユーザーが意識する」という基準の使いどころ UseCase との関係 注意点:すべてを Domain に入れない 迷ったときの最終チェック おわりに Domain レイヤーかどうかを判断する基準 ―「ユーザーが意識するか?」という視点― 設計をしていると、次のような悩みにぶつかることがあります。 この処理は Domain レイヤーに置くべきか? UseCase なのか、それとも Data レイヤーの責務なのか? そもそも Domain って何を置く場所なのか? とくに Clean Architecture や DDD を学び始めた頃は、 「正解の置き場所」を探そうとして、かえって混乱しがちです。 この記事では、私が設計を考える際に ひとつの判断基準 として使っている考え方を紹介します。 Domain レイヤーとは何か(簡単に) Domain レイヤーは、ざっくり言うと アプリが扱う 概念 その概念に関する ルール 「それは正しいかどうか」の判断 を表す層です。 技術的な詳細(DB、API、ライブラリなど)からは距離を置き、 アプリの意味そのもの を表現する場所だと考えています。 判断基準:ユーザーがそれを意識するか? 私が Domain レイヤーかどうかを判断する際に使っている基準は、次の問いです。 「ユーザーは、その機能や概念を意識してアプリを操作するか?」 この問いに YES なら、 その概念や処理は Domain レイヤー(または UseCase)に属する可能性が高いです。 NO なら、 それは Data レイヤーや Infrastructure に閉じ込めるべきものだと考えます。 ...

2026年2月8日 · 1 分 · 奥田 智紘

なぜクリーンアーキテクチャはドメインを守るのか

なぜクリーンアーキテクチャはドメインを守るのか はじめに 不変条件とは何か 例 不変条件は「判断」そのものである 不変条件が曖昧だと、設計は壊れる 不変条件をドメインに閉じ込める、という判断 なぜドメインなのか 「ドメインを守る」とは、何を守っているのか UI やインフラに不変条件を置くと何が起きるか まとめ おすすめの関連書籍(無料) なぜクリーンアーキテクチャはドメインを守るのか クリーンアーキテクチャはドメインを守っているが、その本質は「判断」を守ることである はじめに 「クリーンアーキテクチャでは、ドメインを守ることが重要だ」 この説明を、これまで何度も目にしてきましたし、自分でも何となく理解しているつもりでした。 UI から独立させるため フレームワークに依存させないため テストしやすくするため どれも間違ってはいません。 ただ、実務で設計に向き合えば向き合うほど、 それで結局、何が一番大事なのか? という疑問が残りました。 最近、その答えが少しはっきりしてきました。 クリーンアーキテクチャの本質は、 「判断をどこに閉じ込めるか?」を決める構造である という考え方です。 どの判断は UI に任せてよいのか どの判断はユースケースで行うべきか どの判断は、絶対に外に漏らしてはいけないのか この「判断の置き場所」を曖昧にしたまま設計すると、 責務が混ざり 修正のたびに迷いが生まれ 変更に弱い構造 になっていきます。 では、 絶対に外に出してはいけない判断とは何なのか? その答えを整理するための言葉が、不変条件 でした。 (不変条件という言葉は、英語では invariant という言葉でよく使用されます) この記事では、 クリーンアーキテクチャを「判断の置き場所」という視点で捉え直し その中で不変条件がどんな役割を持つのか なぜ不変条件をドメインに閉じ込めるのか を、実務の感覚に近い形で説明します。 不変条件とは何か 不変条件とは、 どれだけ仕様や実装が変わっても、絶対に破ってはいけない前提 です。 UI が変わっても API が変わっても DB が変わっても これが壊れたら、そのシステムは成立しない そういう条件を指します。 例 ログインしていないユーザーは、保護された操作をできない 残高は常に 0 以上である 同じ支払いは二重に確定しない これらは ...

2026年1月15日 · 1 分 · 奥田 智紘

クリーンアーキテクチャの本質をわかりやすく解説

アーキテクチャとは 依存関係とは 物理的な依存関係 意味的な依存関係 ドメインとは ドメイン以外 同心円状の図の意味 意味的な依存関係とクリーンアーキテクチャ 具体例 依存関係の逆転が必要なケース 依存関係を逆転させる具体的な方法 おすすめの記事 おすすめの本(無料) クリーンアーキテクチャという言葉はよく聞くけれど、「わかったようでわからない」という人が多いのではないでしょうか? クリーンアーキテクチャを解説した記事は、探せばすぐに出てきますが、あまり本質について語られている記事は少ないように感じます。 そこで、この記事でそれを簡潔に語っていこうと思います。 アーキテクチャとは まず、そもそも「アーキテクチャ」とは何でしょうか?簡潔に説明したいので、どんどん答えを書いていきます。 目的 壊れにくいソフトウェアを作る チーム内で統一した方針でソフトウェアを作る 実現方法 部品 (※1) 同士の依存関係 (※2) を整える 部品が果たす役割・責務を明確にする (※1) 「部品って具体的に何?」と思われた方は、一旦、クラスだと思っていただければ大丈夫です。 (※2) 後述します。 めちゃくちゃシンプルに書くとこうなると思います。 依存関係とは クリーンアーキテクチャの本質を知るためには、このセクションがめちゃくちゃ大事です!! 「そんなの知ってるわ」という方も、クリーンアーキテクチャについての理解が「モヤッ」としている方は読んでください。 依存関係には、二種類の依存関係があります。 物理的な依存関係 まずは、物理的な依存関係について説明します。これは、多くの人が最初に思い浮かぶ依存関係の方だと思われます。 すなわち、「どの部品 (クラスなど) がどの部品のことを知っているか」という関係性のことです。 (このドキュメントのサンプルコードは Kotlin で記述させていただきます。ご了承ください。) class A { val b: B } class B { // ... } 上記のサンプルでは、クラス A がクラス B を知っているので、これを「 A が B に依存している」と言います。 これが、物理的な依存関係です。 (超ざっくり) 意味的な依存関係 次に、意味的な依存関係について説明します。ここで、クリーンアーキテクチャの記事でよく出てくる同心円状の図を見て下さい。 (探せばすぐに出てくるのでここには載せません。笑) ...

2026年1月14日 · 2 分 · 奥田 智紘