0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クリーンアーキテクチャについて備忘録

Posted at

はじめに

DDDやクリーンアーキテクチャについて備忘録がてらまとめます。
明らかな誤りがあったらメッしてください。(お手柔らかに)

DDDについて

まずはDDD(ドメイン駆動設計)について記載します。
ざっくりとしたDDDの理解は下記の通りです。

  • エンジニアも事業に対して関心を持つべし
  • 事業ドメインを定義して(ここが難しい)ドメインモデルを設計する

ドメインモデルってORMのモデルと違うの?という数年前の私に向けて言うのであれば、「違います」が回答となります。
ドメインモデルはデータベースの構造とは関係なく、「データベースからデータの取得→ドメインモデルに変換」をすることになります。

クリーンアーキテクチャ

クリーンアーキテクチャはビジネスルール(ユースケース)を中心として、データベースやフレームワーク、UIは外側に配置します。

CleanArchitecture.jpg

引用元:The Clean Architecture

クリーンアーキテクチャはDDDを実現するために利用されることもありますが、必ずしもDDDに限定されるものではありません。

  • DDD (ドメイン駆動設計) は、ソフトウェアの設計思想であり、複雑なビジネスルールを整理するための方法論
  • クリーンアーキテクチャ は、アーキテクチャの設計原則であり、層の分離や依存関係の制御を目的とする

類似のアーキテクチャとの違い

クリーンアーキテクチャだけでなく、他にもオニオンアーキテクチャやヘキサゴナルアーキテクチャ(ポート&アダプタ)が有名です。
違いとして、層(レイヤー)を分けの仕方やどの層を中心とするかはアーキテクチャごとに異なります。
しかし、下記は共通しています。

  • 依存性の注入(Dependency Injection)を使って疎結合にする
  • 依存性逆転の法則(Dependency Inversion Principle)を使って、外側の層から内側の層へ依存させる

依存性の注入(Dependency Injection)

では、依存性の注入とはなんでしょうか。
ここで簡単な例を出します。

下記のnotifierはフィールドとしてMessageSenderを保持しています。
Notifyが呼ばれた時にMessageSender.SendMessageを実行します。

notifier.go
type Notifier struct {
	sender MessageSender
}

func (n *Notifier) Notify(message string) {
	emailSender := &EmailSender{}
    notifier := Notifier{sender: emailSender} 
    notifier.Notify("Hello Email!")
}

このMessageSenderの生成処理をNotifierで行うこともできますが、密結合となってしまいます。
そのため、Notifierを使う時にMessageSenderを外部から渡します。

main.go
func main() {
	// 依存性の注入
	notifier := Notifier{sender: MessageSender{})
}

外部からMessageSenderを渡すことで、MessageSenderが切り替えやすくなりモックを使用したテストの記載がしやすいなどのメリットがあります。
その他にもMessageSenderをEmailとSMSで切り替えることも可能です。

message_sender.go
type MessageSender interface {
	SendMessage(message string)
}

// EmailSender
type EmailSender struct{}

func (e *EmailSender) SendMessage(message string) {
    // Email送信処理
}

// SMS Sender
type SMSSender struct{}

func (s *SMSSender) SendMessage(message string) {
	// SMS送信処理
}
main.go
func main() {
	emailNotifier := Notifier{sender: &EmailSender{}}
	smsNotifier := Notifier{sender: &SMSSender{}}

	emailNotifier.Notify("Hello Email!")
	smsNotifier.Notify("Hello SMS!")
}

依存性の注入をすることで疎結合になります。

依存性逆転の法則(Dependency Inversion Principle)

では、依存性逆転の法則とはなんでしょうか。
話を単純にするため、登場するのはプレゼンテーション層(Controller)、アプリケーション層(Usecase)、データ層(Repository)とします。

処理の流れとして、Controller→Usecase→Repositoryです。

flow.png

詳細の実装は下記の通りです。

user_repository.go
type IUserRepository interface {
	GetUser(id int) string
}

type userRepository struct{}

func (r *userRepository) GetUser(id int) string {
    // データ取得
	return fmt.Sprintf("User%d", id)
}

func NewUserRepository() IUserRepository {
	return &userRepository{}
}
user_usecase.go
type IUserUsecase interface {
	GetUserName(id int) string
}

type userUsecase struct {
	repo IUserRepository
}

func (u *userUsecase) GetUserName(id int) string {
	return u.repo.GetUser(id)
}

func NewUserUsecase(repo IUserRepository) IUserUsecase {
	return &userUsecase{repo: repo}
}

NewUserUsecaseでは受け取ったUserRepositoryを自身のフィールドとして保存して使います。

user_controller.go
type UserController struct {
	usecase IUserUsecase
}

func (c *UserController) GetUser(id int) {
	name := c.usecase.GetUserName(id)
	fmt.Println("User Name:", name)
}
main.go
func main() {
	// 依存性の注入
	repo := NewUserRepository()
	usecase := NewUserUsecase(repo)
	controller := UserController{usecase: usecase} // 直接構造体を生成

	// 実行(本来はnet/http)
	controller.GetUser(1)
}

main.goで依存性の注入を実施します。

では、この実装のどのあたりが依存性の逆転なのでしょうか。
依存の方向は下記の図の通りになります。

reverse.png

依存性逆転の法則では、具体的な実装(クラス)ではなく、抽象(インターフェース)に依存することで、依存関係を制御するのが目的です。
例として、UserUsecaseIUserRepositoryに依存することでuserRepositoryの具体的な実装に依存しません。

他にも、下記のようなメリットがあります。

  • 疎結合
  • 単体テストがしやすい
  • 置き換えやすい
  • 再利用しやすい

まとめ

このアーキテクチャの最大のメリットは、コードがスパゲッティのようになりにくいことだと思います。
中規模以上のシステムでは、ビジネスロジックの複雑性もそれなりにあり、人員もある程度必要となります。
その際に、決められたルールに従うことでクリーンな状態を保てます。

デメリットとしては、書くコードが多くなりがちな点です。
Interfaceを都度用意して、レイヤーを分けてと手間がかかります。
そのため、小規模なシステムや速度が優先される状況では向かないかもしれません。

参考書籍

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?