クリーンアーキテクチャで“意味の芯”が定まらず、設計の原点に戻った
クリーンアーキテクチャで“意味の芯”が定まらず、設計の原点に戻った
導入:焦りと挑戦
クリーンアーキテクチャを、ちゃんと使ってみようと思った。
それなりに年数は積んできたつもりでも、技術の世界はどんどん進んでいく。
「今どきはクリーンアーキテクチャでしょ」とか、「責務をちゃんと分けて書かないと後でつらいよ」といった話をあちこちで聞くようになってきて、正直、置いていかれているような焦りも感じていた。
本や記事を読んで、構造の美しさや拡張性の高さに惹かれた。でも、読むだけでは“わかった気”になってしまう。ちゃんと自分の手で組んでみないと、本当のところは分からない。そう思って、小さな機能をクリーンアーキテクチャで組んでみた。
Controller、UseCase、Presenter。レイヤーを分けて、それぞれの責任もそれっぽく整えた。構造としては、ちゃんと守れていたはずだった。
でも──数日後、追加機能を入れようとして、自分の書いたUseCaseを見返したとき、思わず手が止まった。
きっかけ:UseCaseの中に潜む「判断」
public void Execute(LoginRequest request)
{
var user = _repository.FindByEmail(request.Email);
if (user == null || !user.ValidatePassword(request.Password))
{
_presenter.ShowError("認証失敗");
return;
}
var token = _tokenService.Generate(user);
_presenter.ShowSuccess(token);
}
一見、自然なコードだった。でもよく見ると、この一行で「ログインできるかどうか」の判断を、UseCaseの中で直接 if
で判定していた。
if (user == null || !user.ValidatePassword(request.Password))
この判断、「どこで、なにを、なぜ」行うべきものなのか?判断の責任が構造としてどこにも定義されていなかった。
問題点:再利用できない判断ロジック
そして実際、いろんな問題が起きた。
- たとえばこの判断、「ログインできるか?」というルールを、他のユースケースでも使いたくなる場面は多い。
- パスワード再発行のときに、ちゃんと本人かどうか確認したいとき
- ログイン履歴を記録するときに、「ログイン成功かどうか」で分岐したいとき
そのたびにこの if
文をまた書くことになる。使い回せない判断ロジックは、“再利用されない判断”=意味の芯が外に出せていないということだった。
テストもしにくかった。判断だけを確認したくても、UseCaseを丸ごと動かすしかなくて、ちょっとした仕様変更のたびにテストが手間になった。
なにより、「このユースケースはどんな判断をしてるのか?」が構造として語れなかった。
Transfer(判断)の責任を明確にしなかったことで、見た目は正しくても“意味が流れない設計”になっていた。
転換点:STS設計に立ち戻る
昔、言われたことを思い出した
このとき思い出したのが、昔、組み込みの現場でよく言われた言葉だった。
「判断がどこにあるか分からないコードは、あとで必ず自分を苦しめる」
「入力・判断・出力。それが流れていれば、それだけで設計は説明できる」
仕事終わり、飲み屋でビール片手にそんな話をされたのを思い出した。当時は「まあ、制御系はね」と聞き流していたけれど、今はその意味がよく分かる。
意味の芯とは何か
そのとき、改めて「意味の芯」が自分の設計に通っていなかったことに気づいた。入力と出力は誰が見ても明確だ。でも、「なぜそう動くのか」「どう判断しているのか」が、どのレイヤーにも書かれておらず、漂っていた。
判断の流れ。それが自分にとって、設計の中心だったことを思い出した。
構造で語れる設計とは
STS構造図(BOSSあり)
BOSS(判断の責任者)が、Source・Transfer・Sinkを読み出して振る舞いを構成する
各層は以下のように意味を持つ:
- Source(入力層):外部の事実を受け取る
- Transfer(判断層):解釈・判断・意味づけ
- Sink(出力層):外部への作用として出力する
クリーンアーキテクチャとの対応
クリーンアーキテクチャ | STS視点 | 説明 |
---|---|---|
External Interface(Controllerなど) | Source | ユーザーや外部からの入力受付 |
Application(UseCaseなど) | Transfer | 処理の流れ・判断の実行 |
Domain(Entity, DomainService) | BOSS/Transfer | 業務ルールの保持・判断の根拠 |
Presenter(ViewModel, Presenter) | Sink | 出力の整形・UIへの出力 |
Frameworks & Drivers(DB, Webなど) | Source/Sink | 外部との接点(DB読込=Source、書込=Sink) |
結論:「意味の芯」が通る設計へ
自分なりの見直し
そこで、STSの視点に立ち戻って、処理の流れを見直してみた。
- 入力(Source):Controllerにある
- 出力(Sink):Presenterにある
- 判断(Transfer):UseCaseでやっている“つもり”だったが、明確ではなかった
まず、自分が書いていた判断が曖昧なコードを見直した。
public void Execute(LoginRequest request)
{
var user = _repository.FindByEmail(request.Email);
if (user == null || !user.ValidatePassword(request.Password))
{
_presenter.ShowError("認証失敗");
return;
}
var token = _tokenService.Generate(user);
_presenter.ShowSuccess(token);
}
判断が埋もれていて、Transferとして構造的に語れない状態だった。
これを以下のように分離した。
public void Execute(LoginRequest request)
{
var user = _repository.FindByEmail(request.Email);
if (!_authService.CanLogin(user, request.Password))
{
_presenter.ShowError("認証失敗");
return;
}
var token = _tokenService.Generate(user);
_presenter.ShowSuccess(token);
}
public class AuthService
{
public bool CanLogin(User? user, string password)
{
return user != null && user.ValidatePassword(password);
}
}
判断の責任を明確にし、Transferにあたる処理をUseCaseの外に立て直したことで、ようやくコード全体に意味の流れが戻った。責務分離だけでは語れなかった設計に、「語れる構造」が戻ってきた気がした。
クリーンアーキテクチャを使ってみて、自分が考えている以上に「意味を流す設計」は難しいことに気づかされた。
設計の正しさではなく、“意味の芯”をどう通すか。それが、自分にとっていちばん大切にしたい設計の軸なのかもしれない。
世間一般とは違う整理の仕方かもしれないけれど、今の自分にはこの視点が合っていた。そしてそれは、昔 教わった“あの一言”から、ずっと続いていたのかもしれない。
あわせて学びたいコンテンツ
また、今回紹介した内容をより実践的に学びたい方には、以下のUdemy講座もおすすめです。
🎓 Udemyコース(8,800円 → クーポンで割引中)
▶️ AIとC#で極める!クリーンコードの技法(限定クーポン付き)
- C#でクリーンコードと設計力を身につける実践講座
- ChatGPTの活用方法や、伝わるコードの考え方を解説
📘 出版書籍『あきらめない者たち』
▶️ Amazonで見る
- 技術の基礎からやり直すために、なぜ一歩勇気を振り絞れたのか
- 自身の成長と再出発を描いたノンフィクション作品
Discussion