【データ更新APIの実装】Go言語でのAPI開発 | 実践的な学習から実装まで④
こんにちは!たけのこです。
今回も引き続き、Go言語を使用したAPIの実装方法について解説していきます。
前回は、クリーンアーキテクチャに沿ってフォルダ整理を行なっていきました。
今回は、その記事の続きで整理し終わったプロジェクトで同様に、クリーンアーキテクチャ設計に沿ったユーザー情報を更新するAPIの実装について解説していきます。
では、早速行きましょう!
はじめに
これまでのAPIでは、URLの後続にキーを指定するクエリパラメータを使ってユーザーデータの取得を行なうような実装をしてきました。
しかし、データの追加や更新などの大量のデータ、複雑なデータを扱う際にはリクエストボディに値を指定してAPIを呼び出します。
また、リクエストボディに指定できる形式は、htmlやxmlなど色々とありますが、今回はパッと見て分かりやすいJSON形式でデータのやり取りを行なう設計にしていきます。
リクエストボディに値を指定する方法
リクエストボディに値をセットしてAPIを呼び出す方法は色々とありますが、一番簡単な方法がAPI管理ツールを使用する方法です。
僕の場合は、以前も紹介した「Postman」というツールを使用しています。
Postmanを使用してリクエストボディでデータをAPI側に送る方法については、ザックリとこんな感じです~
APIを呼び出すフェーズで再度、セットするJSONについても含めて紹介しますので
ここでは、ザックリと使い方だけ紹介します~
では、APIの実装に入っていきます。
Step 1:domain層の実装
domain層には、ユーザーエンティティの構造体を定義していましたが、JSON形式のリクエストされたデータも受け取れるように変更を加えます。
package domain
// 各エンティティ項目の`json:~`の部分を追記
type User struct {
UserID int `json:"user_id"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
Step 2: usecase層の実装
前回まで実装していたGetUserの下に新たにユーザーデータを更新するUpdateUserメソッドを追記します。
GetUserと大きく違うところは下記の2か所です。
- user_idだけでなく、domain層のUser構造体ごと引数で受け取っている。
- 戻り値にerror、string型の値を設定している。
データ取得APIの場合は、API実行時にSELECT文で取得してきたデータをそのまま実行結果に返答すればいいだけなのですが、更新の場合はUPDATE文を流してもDB側からは何も返答されてきません。
そのため、APIが正しく実行されたのかどうかを、開発者側がAPI実行者側に明示的に分かりやすくすることがセオリーです。
その関係上、戻り値にstring型の値(今回は更新されたデータを示すメッセージ)をセットしています。
package usecase
import (
"myApiProject/internal/domain"
"myApiProject/internal/repository"
)
type UserUseCase struct {
repo repository.UserRepository
}
func NewUserUseCase(repo repository.UserRepository) *UserUseCase {
return &UserUseCase{repo: repo}
}
/*
* ~ 前回までのGetUserロジック ~
*/
// ここから下のコードを新たに追記
func (uc *UserUseCase) UpdateUser(userRes domain.User) (string, error) {
return uc.repo.UpdateUser(userRes)
}
Step 3: repository層の実装
ここでは、データベース操作を具体的に行なう関数をこの層で実装します。
今回の一番主役となってくるところですね~
大きく関わってくる変数は以下の3つです。
- update:Updateしたいデータを指定
- param:リクエストボディで受け取った各値をセット
- message:更新をかけたデータのメッセージをセット
今回の実装では、受け取ったリクエストボディパラメータの有無によって更新するデータを条件分けしています。
たとえば、user_nameだけ受け取ったらuser_nameだけ更新。
user_name、email、passwordの3つ全て受け取ったら全部に更新を掛ける作りにしています。
package repository
import (
"database/sql"
"myApiProject/internal/domain"
// 新たに"strconv"をimportに追記
"strconv"
"strings"
)
/*
* ~ 前回までのFindByIDロジック ~
*/
// ここから下のコードを新たに追記
func (r *UserRepository) UpdateUser(userRes domain.User) (string, error) {
updates := []string{}
params := []interface{}{}
message := []string{}
message = append(message, "ユーザーID'"+strconv.Itoa(userRes.UserID)+"'の")
if userRes.Name != "" {
updates = append(updates, "name = ?")
params = append(params, userRes.Name)
message = append(message, "名前を'"+userRes.Name+"'に、")
}
if userRes.Email != "" {
updates = append(updates, "email = ?")
params = append(params, userRes.Email)
message = append(message, "Emailを'"+userRes.Email+"'に、")
}
if userRes.Password != "" {
updates = append(updates, "password = ?")
params = append(params, userRes.Password)
message = append(message, "パスワードを'"+userRes.Password+"'に、")
}
message = append(message, "変更しました!")
if len(updates) == 0 {
// 何も更新しない場合は、エラーではなく更新0件メッセージをセット
return "ユーザーデータの更新は0件数です!", nil
}
query := "UPDATE Users SET " + strings.Join(updates, ", ") + " WHERE user_id = ?"
params = append(params, userRes.UserID)
_, err := r.db.Exec(query, params...)
return strings.Join(message, ""), err
}
Step 4: repository層の実装
ここでは、GetUserと同様にHTTPリクエストデータを処理し、UpdateUserユースケースを呼び出して返答されたデータをHTTPレスポンスに返答しています。
ここの実装はGetUserとほとんど同じですが、HTTPレスポンスで返答している内容がUser構造体ではなくメッセージを返しています。
package handler
import (
"encoding/json"
"myApiProject/internal/domain"
"myApiProject/internal/usecase"
"net/http"
)
/*
* ~ 前回までのGetUserロジック ~
*/
// ここから下のコードを新たに追記
func (h *UserHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
var user domain.User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "User Data is missing", http.StatusBadRequest)
return
}
message, err := h.useCase.UpdateUser(user)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// messageをHTTPレスポンスに返答
json.NewEncoder(w).Encode(message)
}
Step 5: routeの設定
最後に、APIを呼び出す際のURLをセットします。
package router
import (
"database/sql"
"myApiProject/internal/handler"
"myApiProject/internal/repository"
"myApiProject/internal/usecase"
"net/http"
)
func NewRouter(db *sql.DB) *http.ServeMux {
repo := repository.NewUserRepository(db)
useCase := usecase.NewUserUseCase(*repo)
userHandler := handler.NewUserHandler(useCase)
router := http.NewServeMux()
router.HandleFunc("/user", userHandler.GetUser)
// ↓の1行だけ追加
router.HandleFunc("/user-update", userHandler.UpdateUser)
return router
}
Step 6:APIの呼び出し
では、実装した更新APIを呼び出してみましょう!
現状、作成していたDBの中は下記のようになっていると思います。
今回は「山田太郎」さんの名前を「田中次郎」に変更しようと思います。
Postmanで下記JSONをリクエストボディに指定して実行してみましょう。
{
"user_id": 1,
"name": "田中次郎"
}
実行後、うまく更新されると下記のようにメッセージが表示されると思います。
実際のデータも更新されているか確認してみましょう。
うん、更新されていますね!
まとめ
ということで、今回はデータ更新APIの実装を行なってきました。
データ取得APIと大きく違うところは、
- SQLがSELECTからUPDATEになっているところ。
- HTTPレスポンスに返答する値がUser構造体ではなく、メッセージ文字列。
という2か所ぐらいですね~
この記事が、更新API実装の助けとなって下されば幸いです。
ということで、今回はこのあたりで!ありがとうございました~!