皆さま、本日は「クリーンアーキテクチャ」という壮大なソフトウェアの世界を巡るツアーにご参加いただき、ありがとうございます!
これから4つの不思議な“層の国”を旅してまいります。ガイドは私。エンジニアリングの世界をナビゲートするコンダクターです。では、旅を始めましょう!
🪄第一の目的地:ドメイン層王国
最初に訪れるのは、「仮想世界の創造主たち」が住むドメイン層王国。ここは、物語の世界や魔法の国、未来の宇宙都市さえもプログラムの力で再現された、想像力あふれる世界です。
この国の住人たちは、「ドメインモデル図」という地図を使って、自分たちの世界を可視化しています。解決したい問題領域に対する最適な構造を探求する、まさに創造の中心地です。
🎯具体例:エンティティ「User」とその振る舞い
// domain/user.go
package domain
import (
"errors"
)
type User struct {
ID string
Name string
Email string
}
func (u *User) ChangeEmail(newEmail string) error {
if !isValidEmail(newEmail) {
return errors.New("invalid email format")
}
u.Email = newEmail
return nil
}
func isValidEmail(email string) bool {
return len(email) > 5 && // シンプルな例
(email[len(email)-4:] == ".com" || email[len(email)-3:] == ".jp")
}
🎭第二の目的地:ユースケース層シアター
さて、お次はドメイン層王国で築かれた仮想世界を舞台に、ドラマが繰り広げられるユースケース層シアターへと向かいましょう!
この劇場では、「どのように物語が動くのか」が脚本として記されています。
たとえば「ユーザーがボタンを押すと、どんな反応が返ってくるのか?」──そんなアプリケーションの具体的な流れが、ここで演出されているのです。
そして、このステージの舞台装置として使われているのが、なんとドメイン層王国の住人たちが作り上げた“仮想世界のふるまい”。
彼らの創造した魔法や仕組みを、私たち現実世界の演出家(開発者)はありがたく利用しながら、劇を成立させているのです。
このユースケース層はまさに、「仮想世界の力を借りて現実のニーズに応える舞台」──私たちはその公演の恩恵を存分に享受している観客でもあり、演出家でもあるのです。
🎯具体例:ユーザーのメールアドレス変更ユースケース
// usecase/change_email.go
package usecase
import (
"context"
"example.com/project/domain"
)
type UserRepository interface {
FindByID(ctx context.Context, id string) (*domain.User, error)
Save(ctx context.Context, user *domain.User) error
}
type ChangeEmailUseCase struct {
Repo UserRepository
}
func (uc *ChangeEmailUseCase) Execute(ctx context.Context, userID, newEmail string) error {
user, err := uc.Repo.FindByID(ctx, userID)
if err != nil {
return err
}
if err := user.ChangeEmail(newEmail); err != nil {
return err
}
return uc.Repo.Save(ctx, user)
}
🔄第三の目的地:インターフェースアダプタ通訳センター
続いてやって来たのは、現実世界と仮想世界の橋渡しをしてくれるインターフェースアダプタ通訳センターです!
ここではControllerたちがユーザーの入力を翻訳し、Presenterたちが仮想世界からの返答を“私たちにも分かる言葉”に変換してくれます。まさに国際空港の通訳ブースのような場所!
さらに、データベースとの橋渡しをしてくれるGatewayたちも働いています。おかげで、複雑な内部構造に迷わずにすむのです。
🎯具体例:HTTP Controllerとデータ変換
// interface/controller/user_controller.go
package controller
import (
"net/http"
"encoding/json"
"example.com/project/usecase"
)
type ChangeEmailRequest struct {
UserID string `json:"user_id"`
NewEmail string `json:"new_email"`
}
func ChangeEmailHandler(uc *usecase.ChangeEmailUseCase) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req ChangeEmailRequest
_ = json.NewDecoder(r.Body).Decode(&req)
err := uc.Execute(r.Context(), req.UserID, req.NewEmail)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("email changed"))
}
}
🛠️最終目的地:フレームワーク&ドライバ市場
旅の最後は、現実世界との接点である“道具の市場”──フレームワーク&ドライバ層へとご案内します。
ここには、WebサーバやUIフレームワーク、データベースといった実行環境が所狭しと並んでおり、まさに開発者のためのバザール!
このマーケットにあるツールたちが、先ほどの通訳センターを通して、ユースケース層の動きを具体的なアプリとして実現してくれるのです。
🎯具体例:DBへの接続と実装
// infrastructure/repo/user_repo.go
package repo
import (
"context"
"database/sql"
"example.com/project/domain"
)
type UserRepo struct {
DB *sql.DB
}
func (r *UserRepo) FindByID(ctx context.Context, id string) (*domain.User, error) {
row := r.DB.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = ?", id)
var user domain.User
if err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {
return nil, err
}
return &user, nil
}
func (r *UserRepo) Save(ctx context.Context, user *domain.User) error {
_, err := r.DB.ExecContext(ctx, "UPDATE users SET email = ? WHERE id = ?", user.Email, user.ID)
return err
}