💎

Domain層の命名、「手段」になっていませんか? はい、僕はそうしてしまってました🙋

に公開

はじめに

実務でプロダクトの検索機能の精度改善を担当することになったときの話です。

キーワード検索では限界があったため、技術選定の結果、ベクトル検索(Vector Search) を導入することが決まりました。私は意気揚々とクリーンアーキテクチャの Domain層 に以下のファイルを作成しました。

domain/repository/vector_search.go

「ベクトル検索をする機能だから、VectorSearch」。その時は何の疑いも持ちませんでしたが、これはクリーンアーキテクチャにおけるDomain層の役割を見失った命名でした。

なぜなら、「ベクトル」はあくまで 技術的な手段(How)であり、ビジネスが達成したいこと(What) ではないからです。

この記事で得られること

  • クリーンアーキテクチャのDomain層における命名の考え方
  • 技術駆動の命名を避けるリファクタリングの実例
  • 「手段」と「目的」を区別するための判断基準

対象読者

  • クリーンアーキテクチャを実践している、または学んでいる方
  • コードレビューで「この命名、なんか違和感あるな」と感じたことがある方

1. なぜ技術駆動の命名が問題なのか?

Domain層の役割を振り返る

クリーンアーキテクチャにおいて、Domain層は ビジネスロジックの中心 です。この層は、データベースがMySQLかPostgreSQLか、通信がRESTかgRPCか、検索がベクトルかキーワードか——そういった技術的な詳細を知るべきではありません。

Domain層に技術的な詳細が漏れ出すと、以下の問題が発生します:

  1. 技術変更のたびにDomain層を修正する必要が生じる
  2. ビジネスの意図がコードから読み取りにくくなる
  3. テストがInterface層の実装に依存しやすくなる

具体的に何が起こりうるか?

VectorSearch という名前をつけた時点では問題なく動きます。しかし、将来こんなことが起こるかもしれません:

  • ベクトル不要の新しい検索技術がリリースされた
  • チームがElasticSearchのBM25(キーワード検索)に戻す判断をした
  • ハイブリッド検索(ベクトル+キーワード)に移行した
  • 社内で開発した独自のセマンティック検索エンジンを導入した

このとき VectorSearch という名前は、もはやコードの実態を表していません。名前を変更するか、嘘の名前のまま運用するかの二択を迫られます。

名前が嘘になる——これはコードベースにとって静かな負債です。


2. Before: 技術駆動の命名(vector_search) 😰

最初は「ベクトル検索機能を作る」という意識が強く、技術名がそのままファイル名やインターフェース名になっていました。

📂 domain/repository/vector_search.go

Domain層なのに「ベクトル(Vector)」という実装手段が漏れ出しています。

package repository

// VectorSearch
// 🙅 問題点:
//   - "Vector" という技術詳細がインターフェース名に含まれている
//   - 技術が変われば名前が嘘になる
type VectorSearch interface {
    SearchByVector(vector []float32) ([]string, error)
}

📂 interface/bigquery/vertex_ai_vector_search.go

実装側はこれで問題ありませんが、インターフェースが技術寄りなので、依存関係が綺麗に切れているようで実は「頭の中」で結合してしまっています。

package bigquery

type VertexAIVectorSearch struct {
    client *vertexai.Client
}

func (r *VertexAIVectorSearch) SearchByVector(vector []float32) ([]string, error) {
    // ... Vertex AIを叩く処理
    return results, nil
}

この設計の問題点まとめ

観点 問題
命名 Domain層に技術用語(Vector)が露出
引数 []float32 というベクトル形式を知っている必要がある
変更耐性 検索技術を変えるとインターフェース名も変更が必要
テスト モックを作る際もベクトル形式を意識する必要がある

3. Feedback & Refactoring

コードレビューで以下のフィードバックを受けました。

「[nits] 命名は修正したい。『ベクトル検索』にすると将来的な拡張性とかも踏まえて微妙じゃね?
そうなった時にクリーンアーキテクチャ的にどうするべきかな?domainの責務でどうするのが良いかな?」

💡 このフィードバックのポイント

クリーンアーキテクチャの原則に立ち返ると、Domain層のインターフェースは 「何を達成したいか」 を表現すべきであり、 「どうやって達成するか」 は隠蔽されるべきです。

責務 命名の観点
Domain層 ビジネスロジック What(何をしたいか)
Interface層 技術的な実装 How(どうやるか)

この視点で見ると、VectorSearch は明らかに「How」の命名になっていました。

そこで、 「何を使って検索するか」ではなく「どんな検索をしたいか」 に焦点を当ててリネームしました。


🎉🎉 4. After: 概念的な命名(semantic_search) 🎉🎉

📂 domain/repository/semantic_search.go

「意味検索(Semantic Search)」という 目的 を表す名前に変更しました。

package repository

import "context"

// SemanticSearch
// 👍 改善点:
//   - "Semantic"(意味的)という「やりたいこと」を表現
//   - 実装技術(ベクトル、キーワード、ハイブリッド等)に依存しない
//   - 技術が変わってもインターフェース名を変更する必要がない
//   - 引数も "Text" に抽象化し、ベクトル化は実装側の責任とする
type SemanticSearch interface {
    Search(ctx context.Context, queryText string) ([]string, error)
}

📂 interface/bigquery/semantic_search.go

実装側で「テキスト → ベクトル」の変換を行うようにしました。これでDomain層は「ベクトル」という技術を知らなくて済みます。

package bigquery

import "context"

type SemanticSearchImpl struct {
    client   *bigquery.Client
    embedder *openai.Client // 埋め込み用クライアント
}

func (s *SemanticSearchImpl) Search(ctx context.Context, queryText string) ([]string, error) {
    // 1. ここでテキストをベクトルに変換(Domain層には隠蔽)
    vector, err := s.embedder.Embed(ctx, queryText)
    if err != nil {
        return nil, err
    }
    
    // 2. ベクトルで検索を実行
    return s.client.VectorSearch(ctx, vector)
}

この設計の改善点まとめ

観点 改善後
命名 Domain層は「意味検索」という目的のみを知っている
引数 string(検索クエリ)というビジネス的な型
変更耐性 ベクトル→キーワード→ハイブリッドと変わってもインターフェースは不変
テスト モックは単純に文字列を受け取って結果を返すだけ

5. 他にもある「技術駆動命名」のパターン 🔍

この問題は VectorSearch に限った話ではありません。実務でよく見かける技術駆動の命名パターンを挙げてみます。

🙅 技術駆動(How) 👍 目的駆動(What) 理由
HTTPClient ExternalAPIGateway HTTPは通信手段。gRPCに変わる可能性
SQLRepository ArticleRepository SQLはストレージ技術。NoSQLに変わる可能性
RedisCache SessionStore Redisはキャッシュ実装。Memcachedに変わる可能性
S3FileStorage AssetStorage S3はAWSのサービス。GCSに変わる可能性
KafkaPublisher EventPublisher Kafkaはメッセージング技術。RabbitMQに変わる可能性

もちろん、Interface層の実装クラス名には VertexAIVectorSearchS3AssetStorage のような技術名を含めて問題ありません。重要なのは、Domain層のインターフェースに技術名を漏らさないことです。


6. 命名時のチェックリスト 📋

Domain層で命名する際、以下の質問を自分に投げかけてみてください。

チェック1: 技術が変わっても名前は有効か?

🙅 VectorSearch
   → ベクトル技術に依存。ハイブリッド検索に変わったら?

👍 SemanticSearch
   → 意味検索という目的を表現。技術は問わない

チェック2: ビジネス用語で説明できるか?

🙅 「このインターフェースはベクトルで検索します」
   → 技術の説明になっている

👍 「このインターフェースは意味的に類似したコンテンツを検索します」
   → ビジネス価値の説明になっている

チェック3: 5年後も同じ名前で通用するか?

技術トレンドは移り変わります。5年前に「最新」だった技術が今は「レガシー」と呼ばれることも珍しくありません。

  • 5年前: 「全文検索といえばElasticsearch」
  • 現在: 「セマンティック検索といえばベクトルDB」
  • 5年後: ???

目的を表す名前は、技術トレンドに左右されません。

チェック4: 新しいチームメンバーに説明しやすいか?

🙅 「VectorSearchは、テキストをembeddingに変換してから
    ベクトル空間でコサイン類似度を計算して...」

👍 「SemanticSearchは、ユーザーの検索意図に近いコンテンツを探します。
    内部的にはベクトル検索を使っていますが、それはInterface層の話です」

まとめ

Before → After

Before After
命名 VectorSearch(手段) SemanticSearch(目的)
引数 []float32(ベクトル) string(検索クエリ)
技術依存 Domain層がベクトル技術を知っている Domain層は検索の目的だけを知っている

覚えておきたいこと

  1. Domain層は「What」、Interface層は「How」
  2. 技術名がDomain層に現れたら、それは改善したい気持ち
  3. 「5年後も通用する名前か?」を自問する

もちろん SemanticSearch が唯一の正解とは限りません。ドメインによっては ArticleSearchSimilarContentFinder など、より具体的なビジネス用語を使う方が適切な場合もあります。

重要なのは、「手段(How)」ではなく「目的(What)」で命名するというプロセスを意識すること です。

この視点を持つだけで、コードレビューでの議論の質が変わり、変更に強い設計ができるようになります。


参考

Discussion