日記マン

動画広告プロダクトしてます。Go, Kubernetesが好きです。

Go言語による「ネット広告」ビジネスに対するドメイン駆動設計の具体例

ネットに転がっているドメインモデリングの実例は少ない。
というのも、その性質上、扱うドメインに対し答えがバラバラで一般化できるものではないから。
というかそもそも外部公開できないような、競合と差別化要因となるロジックだったりするから。
だからこそ、DDDの解説は概念の域にとどまるものが多く、実践的なイメージは習得しづらい。

前回のエントリで、ドメイン駆動設計とはドメインに特化した型の設計と書いた。

i101330.hatenablog.com

次は、その具体例を、
「ネット広告」をドメインに、
Go言語による型の実装とともに考察してみる。

  • 同じURLだけど、「タグURL」と「リファラURL」は違う
  • タグURLの洞察, リファラURLの洞察 とGoのコード例
  • 異なる文脈で異なる要件を持った、メンタルモデルの違いをコード上で表現する

異なるURL 「タグURL」と「リファラURL」

配信システムや計測システムなどのエンドポイントを表すタグURL、
タグのサーバにリクエストしたリクエスト元Webページを表すリファラURL、

「広告 タグ」だとか、「計測 リファラ」だとか、
そういう感じでググると出てくるネット広告ビジネスで生まれたモデル(概念)たち。

事実上のデータとしては同じURL文字列だからといって、
プリミティブな文字列型のまま扱ったり、
URLという名前の汎用的な型で一括りにこれらを扱うような設計をしてしまうと凝集性の低いものが仕上がる。

// URLという名前は汎用的な印象を与え、凝集性の低いファットな実装になっていくことが予想できる
type URL string

前述にあるようにタグURLとリファラURLは要件が違ってくる。
これはネット広告ビジネスに対し理解を深めていくほどはっきりとわかってくる。

// TagURL は配信や計測を行うためにWebページや外部事業者のシステムに組み込む
type TagURL string

// ReferrerURL はリクエスト先のページのURL情報
type ReferrerURL string
そもそも 「計測」や「配信」といった境界づけられたコンテキストの理想的なマッピングができそうならば、  
それぞれのコンテキストで同じ「URL」という名前で、異なる中身のモデリングになる。  
そう言った意味でも、コンテキストマッピングによるサブドメイン分割はとても有用な手法だと思う。  

タグURLの洞察

他社の連携先事業者から渡されたタグURLのマクロ(プレースホルダとなる特殊文字)を動的に置換するようなビジネスロジックがある。
Googleのアドサーバ Google Ad Manager のドキュメントにもそのような機能の説明がある。
https://support.google.com/admanager/answer/2376981?hl=ja

// ConvertMacro はタグのURL文字列のプレースホルダを動的に置換する。
func (url TagURL) ConvertMacro(macros ...MacroCode) TagURL {
    base := url.String()

    for _, macro := range macros {
        // macro key-value
        k, v := macro.ForMacro()
        // replace
        base = strings.Replace(base, k, v)
    }

    return TagURL(base)
}

// MacroCode はマクロ置換が可能な符号であることを定義したインターフェイス。
type MacroCode interface {
    ForMacro() (key string, value string)
}

TagURL 型は、まさしくマクロ置換のロジックを持っている。
DBテーブルではStringとして管理していても、プログラム上ではしっかりタグURLとして振る舞う。

リファラURLの洞察

広告配信サーバの要件として、
どのページからのアクセスか?というリファラ情報はとても重要なバリューオブジェクトだったりする。
広告掲載先の詳細な調査や、ターゲティング精度の向上などに、
リファラという情報を活用していくのがネット広告ビジネスの重要な価値創出につながっていく。

ページ情報としてクエリ文字列は不要な場合の、データクレンジングのような前処理のロジックが TrimQuery() である。

// TrimQuery はクエリ文字列を除外する。
func (url ReferrerURL) TrimQuery() ReferrerURL {
    if !url.IsValid() {
        return url
    }

    base := url.String()
    pos := strings.Index(base, "?")
    if pos == -1 {
        return url
    }

    // trim
    return ReferrerURL(base[:pos])
}

文脈も要件も異なる、メンタルモデルが違うことをコードで表現する

ネット広告ビジネスにおいて、実態はURL文字列であっても、
タグURLとリファラURLは異なる文脈 のときに扱われ、
それぞれが 独自のビジネス解決のためのドメインロジック を持っている。

むしろ、 エンジニアらしい先入観で同じURLという意識 を勝手に作り上げていただけで、
ビジネスサイドのチームメイトは明確にタグとリファラを別物だと意識 していたのかもしれない。
そのために、ビジネスサイドとも対話を繰り返しビジネス上のメンタルモデルを導出しコードに落としていく。

まとめ

「ネット広告」というドメインにおいて、同じURL文字列であるタグURLとリファラURLを、
それぞれドメイン特化した型として設計し、
異なる文脈でそれぞれが独自のロジックでビジネスに立ち向かう具体的な例をGoのコードとともに示した。
type 演算子でプリミティブな文字列に適切な名前を付与し、それぞれにドメインロジックを実装した。

この一例は 他のドメインには通用しない、本当に「ネット広告」に特化した設計 ということ。
ドメイン駆動設計の本質であるとともに、一般化できないゆえに実例に基づいた文献は少ない。
さまざまな業界で、さまざまなドメインに立ち向かっているソフトウェアエンジニアにとって、
本人が取り組むドメインはググっても実装イメージまではあまりキャッチアップできない。
サービスのドメインこそそのサービスの独自の関心ごとであり複雑さの本質になりうる。

ビジネスの理解を深め、ときには ビジネスの常識を疑ってかかり、そこに新しい価値の可能性を発見 するためにも、
ドメインモデルへの深い洞察 をするというアプローチ。
この姿勢自体は異なるドメインを相手にできる共通の手法だといえると思う。

DDDに触れたばかりの人が、概念への理解にとどまらず、
実際の実装のイメージを少しでも掴んでいただけたらな、と思う。

また、ネット広告は、ビジネス要件と技術要件が共に高度である稀有なドメイン
様々なステークホルダーの要求が入り組んだ複雑なドメインロジック、
トラフィックに耐えうるインフラとビッグデータ基盤の設計・運用。
とてもやりがいのある分野だと思うので、興味ある方いらしたらぜひお声かけください。