はじめに
前記事からの続きで、パスパラメータのidでcustomerの個別データを取得するAPIを作成しました。
エラーハンドリングなど、以上のケースにおけるAPI作成にあたっての注意点をメモとしてまとめました。
Go初心者の方の参考になれば幸いです。
数値以外のパスパラメータをはじく
idとして渡すパスパラメータが数値のみとなるように、正規表現を使用します。
router.HandleFunc("/customers/{customer_id:[0-9]+}", ch.getCustomer).Methods(http.MethodGet)
試しにパラメータに数値以外ddd
を入力してみると、404 page not found
が表示されます。
JSONオブジェクトを返す
Handlerとして、getCustomer
を新しく作成します。
取得したパラメータを元にch.service.GetCustomer(id)
でデータを取得し、err
の有無に関わらずにwriteResponse()
を返すようにします。
writeResponse()
では、Content-Typeの設定、ヘッダーへのステータスコードの付加、json形式へのデータのエンコードを行います。
func (ch *CustomerHandlers) getCustomer(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["customer_id"]
customer, err := ch.service.GetCustomer(id)
if err != nil {
writeResponse(w, err.Code, err.AsMessage())
} else {
writeResponse(w, http.StatusOK, customer)
}
}
func writeResponse(w http.ResponseWriter, code int, data interface{}) {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(code)
if err := json.NewEncoder(w).Encode(data); err != nil {
panic(err)
}
}
指定したidのデータがないときに404を返す
前項のcustomer, err := ch.service.GetCustomer(id)
でerr
に渡す中身を考えます。
リポジトリパターンのため、処理内容はServiceではなくRepositoryに記載されます。
なお、(*domain.Customer, *errs.AppError)
のようにGetCustomer()
の返り値がポインタ型となっていますが、データがなかった場合にnilを返すためにこのようにしています。
初期値をnilにするときなどにもポインタ型は使われます。
func (s DefaultCustomerService) GetCustomer(id string) (*domain.Customer, *errs.AppError) {
return s.repo.ById(id)
}
話を戻しますが、customerRepositoryDb
のメソッドById()
を以下のように記述します。
func (d CustomerRepositoryDb) ById(id string) (*Customer, *errs.AppError) {
customerSql := "select customer_id, name, city, zipcode, date_of_birth, status from customers where customer_id = ?"
row := d.client.QueryRow(customerSql, id)
var c Customer
err := row.Scan(&c.Id, &c.Name, &c.City, &c.Zipcode, &c.DateofBirth, &c.Status)
if err != nil {
if err == sql.ErrNoRows {
return nil, errs.NewNotFoundError("customer not found")
} else {
log.Println("Error while scanning customer " + err.Error())
return nil, errs.NewUnexpectedError("unexpected database error")
}
}
return &c, nil
}
row.Scan()
でerr
が返ってきたときのエラーハンドリングで、さらにif err == sql.ErrNoRows
でデータが空だったときの分岐を行います。
errs.NewNotFoundError("customer not found")
のようなエラーを返していますが、この中身はMessageとCodeを含んだAppErrorという構造体となります。
package errs
import "net/http"
type AppError struct {
Code int `json:",omitempty"`
Message string `json:"message"`
}
func (e AppError) AsMessage() *AppError {
return &AppError{
Message: e.Message,
}
}
func NewNotFoundError(message string) *AppError {
return &AppError {
Message: message,
Code: http.StatusNotFound, // 404
}
}
JSONにCodeを加えない場合に、AsMessage()
というメソッドも用意しています。
想定外のエラーの際に500をメッセージとあわせて返す
ById()
のエラーハンドリングで、データが空じゃない場合のエラー(想定外のエラー)として、errs.NewUnexpectedError("unexpected database error")
を返します。
こちらについては、500のCodeとMessageを含んだ構造体を返すようにします。
func NewUnexpectedError(message string) *AppError {
return &AppError {
Message: message,
Code: http.StatusInternalServerError,
}
}
参考資料