GolangでDDDのサンプルコード書いてみた

DDD(ドメイン駆動開発)についての所感

まず初めにDDDについて個人的な所感を書いてみます。

ネット上で調べているとDDDを実践すべきかについては賛否両論があるのを見かけます。もちろん全ての現場で取り入れる必要はないとは思いますが、個人的な意見としてはわざわざ否定するものではないと思っています。

というのも、ドメイン駆動といってますが、ドメインという用語自体は抽象な概念で、「こうあるべきだ!」という強い制約を課しているわけではありません。つまり、ドメインにしたがえば綺麗に開発できるぞ!」と言っているわけではなく、単純に「開発しやすいモデリングがされたもの = ドメインなのです。自分なりに言い換えるとドメイン = 適切にモジュール化されたもの」と捉えています。

オブジェクト指向は車のタイヤ・ライトとか"物"ごとにクラス分割すると良いという指針を示している部分もありましたが、ドメイン駆動はそう言った意味合いではないと思います。

つまり、DDDが言いたいことは、"綺麗に分割して開発しよう" & "DDDで述べている概念を取り入れると分割しやすいよ"ということにとどまると思います。厳密にドメインモデリングの仕方や実装方法にまで言及しているわけではありません(だからこそドメインって何ってよく言われるんじゃないかと思います)。DDDは賛否あっても、「綺麗なコード書きたい、モジュール化したい」に特に反対はないですよね。

開発を進めて知見がたまらないとドメインモデリングが難しいのは、そもそも「適切なモジュール分割」が分からないからであり、開発を進めると、このコードはよく触るとか、一緒に触ることが多いとかが見えてきて、ドメインが見えてくるんだと思います。 ドメインエキスパートと対話し、最初からモデリングを考えたりすることもあると思いますが、そのプロダクトの開発経験を積むことが最も大事なんじゃないかと考えてます。

具体的なコーディング手法とは切り離して考えるとこのように、DDDはガチガチのフレームワーク的なものではなく、心構え的なものとして考えられると思います。 もちろん具体的な実装方法も提案されており、そこまでやる必要あるのか?という部分もあるとは思いますが、そこは適宜取捨選択すれば良いともいますし。

※ただし、後でサービス分割まで考えている場合は真面目にやっておかないと行けない部分は多いかも

DDDのサンプルコード書いてみた

こちらのリポジトリにサンプルコードを置いています。ちなみにまだコーディング途中の部分もあります汗

https://github.com/tonouchi510/golang-ddd-layout

参考書籍

ちなみに、以下の書籍を参考にしており、こちらの書籍のC#で書かれたサンプルをGolangに書き直しています(&一部追記)。

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本 | 成瀬 允宣 |本 | 通販 | Amazon

ちなみにこの本はDDDの入門書としてかなり良本だと思います。DDDについて体系的に学べて、実践方法もコードと共に書いてあります。 DDDで他人に勧める本としては間違いなくこの本を最初に勧めると思います。

GOでDDDする上でのTips

箇条書きになりますが、特筆すべきと思った点を書いておきます。適宜追記していくかもしれません。

  • Golangではレシーバーや引数は、パフォーマンスの都合上ポインタにする(コピー分の節約)慣習があるが、DDDやる上では基本使い分ける
    • DDD的には参照渡しと値渡しは明確に分けたい(脳死ポインタ指定はよくない)
    • 綺麗なコードを優先しつつ、大きなstructだけパフォーマンス面の都合からポインタにする、という方針が良さそう
  • クラスは基本privateで作成する
    • 外部パッケージからオブジェクトを生成する時にNewメソッドを課すため
      • => 生成ルールを必ず通ることになるため、受け取り側でvalidationがいらない
    • 注意:goの場合パッケージレベルでのアクセス制限しかないため、同一パッケージ内では依然としてNew以外でも作れてしまうが、そこは紳士協定を結んでおくしかなさそう
    • 注意:testも同一パッケージ内で書く必要がある(まあ大した問題にはならない はず)
  • 継承ないことはそこまで気にならない

一部サンプルコード抜粋

value objectの例

type userName string

func NewUserName(value string) (userName, error) {
    if value == "" {
        return "", fmt.Errorf("ValueError: userNameが空です。")
    }
    if len(value) < 3 {
        return "", fmt.Errorf("ValueError: ユーザ名は3文字以上です。")
    }
    name := userName(value)
    return name, nil
}

entityの例

type user struct {
    id   UserId
    name UserName
}

func NewUser(id UserId, name UserName) (*User, error) {
    user := User{
        id:   id,
        name: name,
    }
    return &user, nil
}