はじめに
MongoDBのドキュメントをいじっていると、ある時、GETのHTTPリクエストを出してもドキュメントが返ってこなくなってしまいました。
結論からいうと、MongoDB内に収められているデータ型とアプリケーションサーバ側でハンドリングした時のデータ型が異なったため、クライアントアプリケーションまでデータが返ってこなかったのでした。
通常の運用ではおそらく起こり得ないパターンだと思いますが、コーディング上では起こり得るかもしれないです。今まで返ってきたのにある時突然返ってこなくなったときは、解決の足しにしていただければ幸いです。
状況の整理
私がやったこと
MongoDBのドキュメントを更新しました。タイトルでドキュメントを絞り込み、価格(price
)を1.08倍しました。
もともと価格はInt
型で保持していましたが、1.08倍ということでDouble
型にキャストしました。
router.put(Book.self, at:"api/book") { (req, book) -> Future<HTTPStatus> in
let client = try! req.make(MongoClient.self)
let collection = client.db("vaporapp").collection("books", withType: Book.self)
return try req.content.decode(Book.self).map(to: HTTPStatus.self) { book in
let query: Document = try Document.init(fromJSON: "{\"title\":\"" + book.title + "\"}")
let updatedBook: Document = try Document.init(fromJSON: "{\"$set\": {\"price\"" + String(Double(book.price) * 1.08) + "}}")
_ = try! collection.updateOne(filter: query, update: updatedBook)
return .ok
}
}
そのままupdateOne
で更新しました。
データモデル
struct Book: Content {
var title: String
var publisher: String
var price: Int
}
価格(price
)は確かにInt
型ですね。
起こったこと
2件以上のドキュメントがあるコレクションに対して全件取得のAPIを叩いたにも関わらず、1件しか返ってきていません。
同名のタイトルや金額があるのでわかりにくいですが、要するに中途半端にドキュメントが得られるのです。全部返ってこないとかエラーが返ってくるとかならまだしも、中途半端に返してくるとは……
原因
型の不一致が原因です。もう白状したも同然ですが、犯人は私でした。
もともとはInt
型で保持されたいた値をDouble
型にキャストしたままMongoDBに保存してしまっています。
{\"$set\": {\"price\":" + String(Double(book.price) * 1.08) + "}}
MongoDBのドキュメントを保存する時、当たり前ですが型の確認なんてしてくれません。なので、Double
型で保存するとそのまま保存されます。
読み出し時もそのまま返してくれます。そして、流れてきたデータがアプリケーションサーバでデータモデルに合わせて変換される際にはねられてしまうようです。
対策
データモデルの各プロパティのデータ型はクライアントアプリケーションからデータベースサーバまで一貫して統一させておきましょう。
今回のケースでは扱っていませんが、クライアントアプリケーションまでJSONで流してくるケースでも注意が必要そうです。まぁ、データ型を一貫して統一しておけばいいだけの話ですね。
まとめ
MongoDBは渡したデータをそのまま保存し、要求したデータはそのまま返してくれます。データ型チェックはコーディング時にきっちりとですね!
また、JSONからデータモデルに変換される時の挙動にも注意が必要です。
アプリケーションサーバにてデータモデルをContent
プロトコルに準拠させているので、JSONとのエンコードやデコードはお手軽です。お手軽故に気が緩んでしまいかねません。データ型だけは要注意です。それにしても、適合しなかった型を持つプロパティだけがはねられるのではなく、ドキュメント自体がはねられるとは思わなかった……
それと、2進数と小数表現に関する古典的な注意点にもしっかりと引っかかっていますね。油断大敵!