クリーンアーキテクチャ呼び出し順(暗記)
code:go
⏺ 超詳細な実行フロー(層とファイル名まで完全記述)
Step 1: HTTPリクエスト受信
GET /api/master-books/search?title=Go言語&author_name=&publisher_name=
Step 2: ルーター層
// 【層】: Router層(自動生成)
// 【ファイル】: /pkg/oapi/api.gen.go(自動生成ファイル)
// 【呼び出し元】: HTTPサーバー(net/http)
// 【呼び出し先】: Router層のパース処理
// 【処理内容】: クエリパラメータをパースして構造体を生成
oapi.SearchMasterBooksRequestObject{
Params: oapi.SearchMasterBooksParams{
Title: (*string)(0xc0001234) // "Go言語"のポインタ
AuthorName: (*string)(nil)
PublisherName: (*string)(nil)
}
}
Step 3: Router層 → Handler層
// 【層】: Router層 → Handler層
// 【ファイル】: /pkg/oapi/api.gen.go → /core/handler/master_book.go
// 【呼び出し元】: Router層の自動生成コード
// 【呼び出し先】: handler.Core.SearchMasterBooks メソッド
// 【引数】:
// - ctx: context.Context
// - request: oapi.SearchMasterBooksRequestObject
// 【戻り値】:
// - oapi.SearchMasterBooksResponseObject
// - error
func (h Core) SearchMasterBooks(
ctx context.Context,
request oapi.SearchMasterBooksRequestObject,
) (oapi.SearchMasterBooksResponseObject, error) {
// 現在TODO状態
Step 4: Handler層内部(input構造体作成)
// 【層】: Handler層内部
// 【ファイル】: /core/handler/master_book.go
// 【呼び出し元】: handler.Core.SearchMasterBooks メソッド内
// 【呼び出し先】: /core/usecase/input/master_book.go の NewSearchMasterBook関数
// 【引数】:
// - title: string = "Go言語"
// - authorName: string = ""
// - publisherName: string = ""
// 【戻り値】: input.SearchMasterBook
title := ""
if request.Params.Title != nil {
title = *request.Params.Title
}
searchInput := input.NewSearchMasterBook(title, authorName, publisherName)
// 結果: input.SearchMasterBook{
// Title: null.String{Valid: true, String: "Go言語"}
// AuthorName: null.String{Valid: false, String: ""}
// PublisherName: null.String{Valid: false, String: ""}
// }
Step 5: Handler層 → UseCase層
// 【層】: Handler層 → UseCase層
// 【ファイル】: /core/handler/master_book.go → /core/usecase/master_book_impl.go
// 【呼び出し元】: handler.Core.SearchMasterBooks メソッド
// 【呼び出し先】: usecase.masterBook.Search メソッド(インターフェース経由)
// 【引数】:
// - ctx: context.Context
// - in: input.SearchMasterBook
// 【戻り値】:
// - []*output.SearchMasterBook
// - error
result, err := h.masterBookUsecase.Search(ctx, searchInput)
Step 6: UseCase層内部(条件チェック)
// 【層】: UseCase層内部
// 【ファイル】: /core/usecase/master_book_impl.go
// 【メソッド】: masterBook.Search
func (u *masterBook) Search(
ctx context.Context,
in input.SearchMasterBook,
) ([]*output.SearchMasterBook, error) {
// 【層】: UseCase層 → Input層
// 【ファイル】: /core/usecase/master_book_impl.go → /core/usecase/input/master_book.go
// 【呼び出し元】: masterBook.Search メソッド
// 【呼び出し先】: input.SearchMasterBook.HasAnySearchCriteria メソッド
// 【引数】: なし(レシーバーのみ)
// 【戻り値】: bool = true
if !in.HasAnySearchCriteria() {
return nil, nil
}
Step 7: UseCase層 → Query層
// 【層】: UseCase層 → Query層
// 【ファイル】: /core/usecase/master_book_impl.go → /core/infra/query/master_book_impl.go
// 【呼び出し元】: usecase.masterBook.Search メソッド
// 【呼び出し先】: query.masterBook.Search メソッド(インターフェース経由)
// 【引数】:
// - ctx: context.Context
// - query: query.MasterBookSearchQuery{
// Title: null.String{Valid: true, String: "Go言語"}
// AuthorName: null.String{Valid: false, String: ""}
// PublisherName: null.String{Valid: false, String: ""}
// }
// 【戻り値】:
// - []*model.MasterBook
// - error
masterBooks, err := u.masterBookQuery.Search(
ctx,
query.MasterBookSearchQuery{
Title: in.Title,
AuthorName: in.AuthorName,
PublisherName: in.PublisherName,
},
)
Step 8: Query層内部(SQL構築)
// 【層】: Query層内部
// 【ファイル】: /core/infra/query/master_book_impl.go
// 【メソッド】: query.masterBook.Search
func (m *masterBook) Search(
ctx context.Context,
query query.MasterBookSearchQuery,
) ([]*model.MasterBook, error) {
mods := []bob.Mod*dialect.SelectQuery{}
if query.Title.Valid {
// 【層】: Query層 → DB層
// 【ファイル】: /core/infra/query/master_book_impl.go → /pkg/db/escape.go
// 【呼び出し元】: query.masterBook.Search メソッド
// 【呼び出し先】: db.EscapeLikePattern 関数
// 【引数】: "Go言語"
// 【戻り値】: "Go言語"(エスケープ済み)
pattern := "%" + db.EscapeLikePattern(query.Title.String) + "%"
// 【層】: Query層 → Bob ORM層
// 【ファイル】: /core/infra/query/master_book_impl.go → /pkg/dbmodels/master_books.bob.go
// 【呼び出し元】: query.masterBook.Search メソッド
// 【呼び出し先】: dbmodels.SelectWhere.MasterBooks.Title.ILike メソッド
// 【引数】: "%Go言語%"
// 【戻り値】: bob.Mod*dialect.SelectQuery
mods = append(mods, dbmodels.SelectWhere.MasterBooks.Title.ILike(pattern))
}
Step 9: Query層 → Bob ORM → Database
// 【層】: Query層 → Bob ORM層 → Database層
// 【ファイル】: /core/infra/query/master_book_impl.go → Bob ORM → PostgreSQL
// 【呼び出し元】: query.masterBook.Search メソッド
// 【呼び出し先】:
// 1. dbmodels.MasterBooks.Query(mods...) - クエリビルダー
// 2. .All(ctx, m.dbClient.Get(ctx)) - SQL実行
// 【生成SQL】:
// SELECT id, isbn, cover_s3_url, title, author_id, publisher_id,
// total_page, created_at, updated_at, published_at
// FROM master_books
// WHERE title ILIKE '%Go言語%'
// 【戻り値】: []*dbmodels.MasterBook
dbMasterBooks, err := dbmodels.MasterBooks.Query(mods...).All(ctx, m.dbClient.Get(ctx))
Step 10: Query層内部(型変換)
// 【層】: Query層内部
// 【ファイル】: /core/infra/query/master_book_impl.go
// 【処理】: dbmodels.MasterBook → model.MasterBook への変換
masterBooks := make([]*model.MasterBook, len(dbMasterBooks))
for i, dbMasterBook := range dbMasterBooks {
// 【層】: Query層内部
// 【ファイル】: /core/infra/query/master_book_impl.go
// 【処理】: ポインタキャスト(dbmodels → domain model)
masterBooksi = (*model.MasterBook)(dbMasterBook)
}
return masterBooks, nil
}
Step 11: UseCase層内部(output変換)
// 【層】: UseCase層内部
// 【ファイル】: /core/usecase/master_book_impl.go
// 【呼び出し元】: usecase.masterBook.Search メソッド
// 【呼び出し先】: /core/usecase/output/master_book.go の NewMasterBookSearch関数
// 【引数】: []*model.MasterBook
// 【戻り値】: []*output.SearchMasterBook
return output.NewMasterBookSearch(masterBooks), nil
}
Step 12: Handler層内部(レスポンス変換)
// 【層】: Handler層内部
// 【ファイル】: /core/handler/master_book.go
// 【処理】: output.SearchMasterBook → oapi.MasterBook への変換
books := make([]oapi.MasterBook, len(result))
for i, book := range result {
booksi = oapi.MasterBook{
Id: book.ID,
Title: book.Title,
AuthorName: book.AuthorName,
PublisherName: book.PublisherName,
}
}
// 【層】: Handler層 → Router層
// 【ファイル】: /core/handler/master_book.go → /pkg/oapi/api.gen.go
// 【戻り値】: oapi.SearchMasterBooks200JSONResponse
return oapi.SearchMasterBooks200JSONResponse{
Books: books,
}, nil
}
Step 13: Router層 → HTTPレスポンス
// 【層】: Router層 → HTTPクライアント
// 【ファイル】: /pkg/oapi/api.gen.go → net/http
// 【処理】: 構造体をJSONにシリアライズして送信
HTTP/1.1 200 OK
Content-Type: application/json
{
"books": [
{
"id": 1,
"title": "Go言語入門",
"author_name": "山田太郎",
"publisher_name": "技術出版社"
}
]
}
上から順番にただ読んでもわかるようにしてほしい
⏺ 実行フローを上から順番に読める形式で】
1. HTTPリクエストが来る
ブラウザ/クライアント → サーバー
GET /api/master-books/search?title=Go言語&author_name=&publisher_name=
---
2. Goサーバーがリクエストを受け取る
【層】: Router層(自動生成)
【ファイル】: /pkg/oapi/api.gen.go
// HTTPリクエストのクエリパラメータを構造体に変換
title := "Go言語" // クエリから取得
authorName := "" // 空文字
publisherName := "" // 空文字
// 構造体を作成
request := oapi.SearchMasterBooksRequestObject{
Params: oapi.SearchMasterBooksParams{
Title: &title, // ポインタ
AuthorName: nil, // 空なのでnil
PublisherName: nil, // 空なのでnil
}
}
---
3. RouterがHandlerを呼ぶ
【層】: Router層 → Handler層
【ファイル】: /pkg/oapi/api.gen.go → /core/handler/master_book.go
// Handlerのメソッドを呼び出す
response, err := handler.SearchMasterBooks(ctx, request)
---
4. Handlerメソッドが実行される
【層】: Handler層
【ファイル】: /core/handler/master_book.go
func (h Core) SearchMasterBooks(ctx context.Context, request oapi.SearchMasterBooksRequestObject) (oapi.SearchMasterBooksResponseObject, error) {
// requestの中身:
// request.Params.Title = &"Go言語"
// request.Params.AuthorName = nil
// request.Params.PublisherName = nil
---
5. Handlerがinput構造体を作る
【層】: Handler層
【ファイル】: /core/handler/master_book.go → /core/usecase/input/master_book.go
// nilチェックして文字列に変換
title := ""
if request.Params.Title != nil {
title = *request.Params.Title // "Go言語"
}
authorName := "" // nilなので空文字のまま
publisherName := "" // nilなので空文字のまま
// input構造体を作成
searchInput := input.NewSearchMasterBook(title, authorName, publisherName)
---
6. NewSearchMasterBookの中身
【層】: Input層
【ファイル】: /core/usecase/input/master_book.go
func NewSearchMasterBook(title, authorName, publisherName string) SearchMasterBook {
search := SearchMasterBook{}
if title != "" { // "Go言語" != "" なのでtrue
search.Title = null.StringFrom(title) // Valid: true, String: "Go言語"
}
if authorName != "" { // "" == "" なのでfalse、スキップ
// スキップされる
}
if publisherName != "" { // "" == "" なのでfalse、スキップ
// スキップされる
}
return search
// 返される値:
// SearchMasterBook{
// Title: null.String{Valid: true, String: "Go言語"}
// AuthorName: null.String{Valid: false, String: ""}
// PublisherName: null.String{Valid: false, String: ""}
// }
}
---
7. HandlerがUseCaseを呼ぶ
【層】: Handler層 → UseCase層
【ファイル】: /core/handler/master_book.go → /core/usecase/master_book_impl.go
// searchInputを渡してUseCaseのSearchを呼ぶ
result, err := h.masterBookUsecase.Search(ctx, searchInput)
---
8. UseCaseのSearchメソッドが実行される
【層】: UseCase層
【ファイル】: /core/usecase/master_book_impl.go
func (u *masterBook) Search(ctx context.Context, in input.SearchMasterBook) ([]*output.SearchMasterBook, error) {
// inの中身:
// Title: null.String{Valid: true, String: "Go言語"}
// AuthorName: null.String{Valid: false, String: ""}
// PublisherName: null.String{Valid: false, String: ""}
---
9. UseCaseが検索条件をチェック
【層】: UseCase層
【ファイル】: /core/usecase/master_book_impl.go → /core/usecase/input/master_book.go
// 検索条件があるかチェック
if !in.HasAnySearchCriteria() {
// HasAnySearchCriteriaメソッドの中身:
// return s.Title.Valid || s.AuthorName.Valid || s.PublisherName.Valid
// = true || false || false
// = true
// なので、!true = false となり、このifには入らない
}
---
10. UseCaseがQuery層を呼ぶ
【層】: UseCase層 → Query層
【ファイル】: /core/usecase/master_book_impl.go → /core/infra/query/master_book_impl.go
// Query層用の構造体に変換して呼び出す
masterBooks, err := u.masterBookQuery.Search(
ctx,
query.MasterBookSearchQuery{
Title: in.Title, // そのまま渡す
AuthorName: in.AuthorName, // そのまま渡す
PublisherName: in.PublisherName, // そのまま渡す
},
)
---
11. Query層のSearchメソッドが実行される
【層】: Query層
【ファイル】: /core/infra/query/master_book_impl.go
func (m *masterBook) Search(ctx context.Context, query query.MasterBookSearchQuery) ([]*model.MasterBook, error) {
// queryの中身:
// Title: null.String{Valid: true, String: "Go言語"}
// AuthorName: null.String{Valid: false, String: ""}
// PublisherName: null.String{Valid: false, String: ""}
// SQL条件を入れる配列
mods := []bob.Mod*dialect.SelectQuery{}
---
12. Query層がSQL条件を組み立てる
【層】: Query層
【ファイル】: /core/infra/query/master_book_impl.go
// Title条件
if query.Title.Valid { // true なので実行
pattern := "%" + db.EscapeLikePattern(query.Title.String) + "%"
// pattern = "%Go言語%"
mods = append(mods, dbmodels.SelectWhere.MasterBooks.Title.ILike(pattern))
}
// AuthorName条件
if query.AuthorName.Valid { // false なのでスキップ
// スキップ
}
// PublisherName条件
if query.PublisherName.Valid { // false なのでスキップ
// スキップ
}
---
13. Query層がデータベースを実行
【層】: Query層 → Database
【ファイル】: /core/infra/query/master_book_impl.go → PostgreSQL
// SQLを実行
dbMasterBooks, err := dbmodels.MasterBooks.Query(mods...).All(ctx, m.dbClient.Get(ctx))
// 実行されるSQL:
// SELECT * FROM master_books WHERE title ILIKE '%Go言語%'
// 結果(例):
// dbMasterBooks = []*dbmodels.MasterBook{
// {ID: 1, Title: "Go言語入門", AuthorID: 10, PublisherID: 5, ...},
// {ID: 2, Title: "実践Go言語", AuthorID: 15, PublisherID: 3, ...}
// }
---
14. Query層が型変換して返す
【層】: Query層
【ファイル】: /core/infra/query/master_book_impl.go
// dbmodels.MasterBook → model.MasterBook に変換
masterBooks := make([]*model.MasterBook, len(dbMasterBooks))
for i, dbMasterBook := range dbMasterBooks {
masterBooksi = (*model.MasterBook)(dbMasterBook)
}
return masterBooks, nil // UseCase層に返す
---
15. UseCase層が結果を受け取る
【層】: UseCase層
【ファイル】: /core/usecase/master_book_impl.go
// masterBooksを受け取った
// masterBooks = []*model.MasterBook{
// {ID: 1, Title: "Go言語入門", ...},
// {ID: 2, Title: "実践Go言語", ...}
// }
// output形式に変換
return output.NewMasterBookSearch(masterBooks), nil
---
16. output変換の中身
【層】: Output層
【ファイル】: /core/usecase/output/master_book.go
func NewMasterBookSearch(books []*model.MasterBook) []*output.SearchMasterBook {
result := make([]*output.SearchMasterBook, len(books))
for i, book := range books {
resulti = &output.SearchMasterBook{
ID: book.ID,
Title: book.Title,
AuthorName: "著者名", // 本来はJOINして取得
PublisherName: "出版社名", // 本来はJOINして取得
}
}
return result
}
---
17. Handler層が結果を受け取る
【層】: Handler層
【ファイル】: /core/handler/master_book.go
// resultを受け取った
// result = []*output.SearchMasterBook{
// {ID: 1, Title: "Go言語入門", AuthorName: "山田太郎", ...},
// {ID: 2, Title: "実践Go言語", AuthorName: "鈴木一郎", ...}
// }
// OpenAPI形式に変換
books := make([]oapi.MasterBook, len(result))
for i, book := range result {
booksi = oapi.MasterBook{
Id: book.ID,
Title: book.Title,
AuthorName: book.AuthorName,
PublisherName: book.PublisherName,
}
}
// レスポンスを返す
return oapi.SearchMasterBooks200JSONResponse{
Books: books,
}, nil
---
18. 最終的なHTTPレスポンス
【層】: Router層 → クライアント
【ファイル】: /pkg/oapi/api.gen.go → ブラウザ
HTTP/1.1 200 OK
Content-Type: application/json
{
"books": [
{
"id": 1,
"title": "Go言語入門",
"author_name": "山田太郎",
"publisher_name": "技術出版社"
},
{
"id": 2,
"title": "実践Go言語",
"author_name": "鈴木一郎",
"publisher_name": "プログラミング社"
}
]
}