💡

クリーンアーキテクチャで“意味の芯”が定まらず、設計の原点に戻った

に公開

クリーンアーキテクチャで“意味の芯”が定まらず、設計の原点に戻った

導入:焦りと挑戦

クリーンアーキテクチャを、ちゃんと使ってみようと思った。

それなりに年数は積んできたつもりでも、技術の世界はどんどん進んでいく。
「今どきはクリーンアーキテクチャでしょ」とか、「責務をちゃんと分けて書かないと後でつらいよ」といった話をあちこちで聞くようになってきて、正直、置いていかれているような焦りも感じていた。

本や記事を読んで、構造の美しさや拡張性の高さに惹かれた。でも、読むだけでは“わかった気”になってしまう。ちゃんと自分の手で組んでみないと、本当のところは分からない。そう思って、小さな機能をクリーンアーキテクチャで組んでみた。

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