-- ドメインにUIの責務が混在している問題 --
AIとの挑戦と理想の設計
「よし、できた!」
私は満足げにコードを眺めていた。入社5年目の私にとって、今回のTodoアプリは特別なプロジェクトだった。
Webアプリケーションを作るためのフレームワーク「React」を使って、タスク管理アプリを作ったのだ。
なぜって?それは、AIを使って「DDDを意識した設計」に挑戦したからだ。
ドメイン駆動設計(Domain-Driven Design)。略してDDD。オブジェクト指向設計の世界では有名な設計手法だ。
最近のWeb開発でも注目されているという記事を読んで、私も取り入れてみたいと思っていた。
でも、DDDの本を読んでも難しくて。そこで思いついたのが、AIの助けを借りることだった。
ChatGPTと対話しながら、React + TypeScriptでTodoアプリを作っていく。AIは私の質問に丁寧に答えてくれた。ドメインモデルがどうとか、クリーンアーキテクチャがどうとか...正直、全ては理解できていないけど、とにかく形にはなった。
師匠との対峙と現実の壁
「師匠、ちょっといいですか?」
私は、開発部の大御所のデスクに向かった。みんなから"師匠"と呼ばれるこの人は、60代後半の豪快な笑い声が特徴的な人物だ。数々の大規模システムのアーキテクトを務め、設計論を第一線で戦わせてきた猛者だ。
「おぉ、どうした?」師匠は、デスクの引き出しからこっそり取り出した梅干しを頬張りながら答えた。今日も二日酔いの定番の光景だ。
「あのー、DDDを使ってTodoアプリを作ってみたんです。
DDDって、いくら本読んでも難しくて諦めてましたが、最近のAIって凄いですよ。
師匠にも見てもらいたくて」
「DDD?」師匠は、しわだらけの目を細めた。「俺DDDってまだ試してないど、興味はあるなー」
私はノートPCを開き、メインのプログラムファイルApp.tsx
を表示した。Reactで作るWebアプリケーションの中心となるファイルだ。ChatGPTと一緒に作り上げたコードだ。
「ほー」師匠は老眼鏡を掛け直し、画面に顔を近づけてスクロールしていく。
App.tsx
以外のファイルも見始めた。
src/
├── domain/ # ビジネスロジックの中核
│ └── todo.ts # Todoの定義とその操作
├── application/ # ユースケース
│ └── todoService.ts
├── infrastructure/ # 技術的な実装
│ └── storage/
│ └── todoRepository.ts
└── ui/ # UI関連
├── components/
│ ├── TodoForm.tsx
│ └── TodoList.tsx
└── App.tsx
// App.tsx
// 状態管理、UI表示、データ永続化など、多くの責務を持つ巨大なコンポーネント
export const App: React.FC = () => {
// 状態管理
const [todos, setTodos] = useState<Todo[]>([])
const [newTodoText, setNewTodoText] = useState('')
const [filter, setFilter] = useState<Filter>('all')
const [editingId, setEditingId] = useState<string | null>(null)
const [editingText, setEditingText] = useState('')
const [error, setError] = useState<string | null>(null)
// LocalStorageとの同期(データ永続化の責務)
useEffect(() => {
try {
const savedTodos = localStorage.getItem('todos')
if (savedTodos) {
// ... データの読み込みと状態の更新 ...
}
} catch (err) {
setError('データの読み込みに失敗しました')
}
}, [])
// Todoの追加処理(ビジネスロジックとUIの責務が混在)
const addTodo = (e: React.FormEvent) => {
e.preventDefault()
if (!newTodoText.trim()) {
setError('タスクの内容を入力してください')
return
}
try {
const newTodo = Todo.create(newTodoText)
setTodos([...todos, newTodo])
setNewTodoText('')
setError(null)
} catch (err) {
// ... エラーハンドリング ...
}
}
// ... 他にも多数の関数(編集、削除、フィルタリングなど)が続く ...
return (
// ... UIの表示処理(JSX) ...
)
}
問題の発見と設計の本質
「ふーん。最近のAIはすげーなー。ここまでできるんだな」
「ですよね!」
「でも、これ本当にDDDなのか?DDD俺わかってないけど、設計としてはひでー内容だぞ」
「え...でも、AIは『DDDの原則に従っています』って...」
師匠は手元のお菓子を口に放り込みながら、不敵な笑みを浮かべた。
「お前、AIに扱われてるぞ。AIの言うことを鵜呑みにしてるだけじゃないのか?」
「...」
私は言葉に詰まった。確かにAIの提案通りにコードを書いていた。でも、その意味は本当に理解していたのだろうか。
「設計の基本ってのは、昔も今も変わらないんだよ。このコードの何が問題か、一緒に見ていくか?」
「お願いします」
問題1:巨大なApp.tsxファイル
師匠は画面を指差しながら、「このApp.tsx
ファイル、長すぎだろ?何行あるんだ?」
「えーと...229行です」
「ひでーな。なぜこんなに長くなったと思う?」
「えっと...色々な機能を入っているからだと思います」
「具体的には?」
「Todoの表示と...追加と...編集と...削除と...あと、データの保存も...」
「そうそう」師匠は机に手をつきながら身を乗り出してきた。
「『と』が多すぎるんだよ。一つのファイルに全部詰め込みすぎだ。これが問題なんだ」
「そもそも、このファイルが何をするためのものなのか、一言で説明できない時点で、設計が破綻してるんだよ」
「あ...」
私は画面を見つめ直した。
確かに、一つのファイルで何もかもをやろうとしている。AIに頼って作ったコードだけど、こんな基本的なことも気づけなかったのか...。
「俺はDDDとかよく知らんが」師匠はお菓子を机に置きながら続けた。
「画面の処理と、その裏でやってる処理は分けた方がいい。そういう基本が、どの時代でも大事なんだよ。DDD読んでないけど、同じことが書いてるはず」
スマートフォンを取り出して、DDDについて調べてみる。
すると、「ドメインロジックの分離」「関心の分離」といった言葉が目に飛び込んできた。
なるほど、師匠の言う通りだ。AIに言われるがままにDDDを取り入れようとしていたけど、その前にもっと大切なことがあったんだ。
問題2:ドメイン層にUIの責務が混在
「ん?」師匠がdomain/todo.ts
を開いた。「ちょっと待てよ」
「はい?」
「ドメインって、業務ロジックを書くところだよな?」
「はい、そうです」
「じゃあ、なんでここにUIの責務があるんだ?」
「えっ?」
師匠は画面を指差した。Todo
クラスの実装を見ると:
// domain/todo.ts
// ドメインエンティティにUIの責務が混在している例
export class Todo {
private events: DomainEvent[] = []
private constructor(
private readonly id: TodoId,
private text: TodoText,
private status: TodoStatus,
private readonly createdAt: Date
) {}
// バリデーションロジックにUIのメッセージが混在
private static validateText(text: string): void {
if (!text.trim()) {
// UIの責務:ユーザーへのメッセージ
throw new TodoValidationError('タスクの内容を入力してください');
}
if (text.length > 100) {
// UIの責務:ユーザーへのメッセージ
throw new TodoValidationError('タスクの内容は100文字以内で入力してください');
}
}
}
問題点: ドメイン層にUIの責務が混在している
「これ、エラーメッセージがUIの責務だろ?『タスクの内容を入力してください』って、ユーザーへのメッセージじゃないか」
「あ...」
私は言葉に詰まった。確かに、エラーメッセージはユーザーへの表示用だ。ドメインの責務は、ビジネスルールの検証だけのはずなのに...。
「ドメインは、ビジネスルールを表現するところだ。例えば、タスクの文字数制限はビジネスルールだから、ドメインで検証するのは正しい。でも、そのエラーメッセージの表現は、UIの責務だろ?」
「はい...」
学びと新たな道のり
「AIは確かに便利だ。でもな、こういう基本的な設計の原則は、自分で理解して判断しないといけないんだよ」
「他にも設計的にヤバいところ、いっぱいあるけどなー」師匠が言う。「まずはこのファイルから、直していこうか」
私は黙って頷いた。最新のDDDやAIの知識も大事だけど、こうして師匠から実践的な設計の考え方を学べることが、なんだか嬉しかった。
師匠からの最後のメッセージ
師匠は立ち上がりかけたが、何か思い出したように振り返った。
「そうそう、AIは便利な道具だ。でもな、人にもAIにも、お前が何をやりたいのか、ちゃんと指示できる力が必要になる。それに、相手の出してきた答えが本当に正しいのか、判断できる目も持っておかないとな」
私の表情を見て、師匠は満足げに立ち上がった。
「明日、このコードをどう直すか一緒に考えよう。今日はもう遅いしな」と言って、デスクの引き出しから梅干しの空き容器を取り出した。
次回予告
第2話「分割の章」、師匠と若手は巨大なApp.tsx
の分割に挑む...。
関連情報
また、今回紹介した内容をより実践的に学びたい方には、以下のUdemy講座もおすすめです。
Udemyコース(8,800円 → クーポンで割引中)
▶️ AI時代でも生き残れるエンジニアへ!AI×React×クリーンコードでシンプルな設計を武器にする実践入門(限定クーポン付き)
▶️ AIとC#で極める!クリーンコードの技法(限定クーポン付き)
- C#でクリーンコードと設計力を身につける実践講座
- ChatGPTの活用方法や、伝わるコードの考え方を解説
出版書籍『あきらめない者たち』
▶️ Amazonで見る
- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのかのノンフィクション作品です。