はじめに
VaporアプリケーションからMongoDBへデータを追加したり取得したりすることができるようになりました。かなり勢い任せで危なっかしいですがね。今回はそれらのデータを更新してみたり削除してみたり。ドドドッと進んでいきます。
今回も別のVapor3アプリケーションを作って、FluentSQLite用に作ったものと比べつつ進めていく方針で進めていきます。
ドキュメントの更新
FluentSQLiteの場合
router.post("api/book",Book.parameter) { req -> Future<Book> in
return try req.parameters.next(Book.self).flatMap { book in
var renewBook = Book(title: book.title, publisher: "ヌッコ出版", price: 900)
renewBook.id = book.id
return renewBook.update(on: req)
}
}
FluentSQLiteの場合はrouter.post("api/book",Book.parameter) {~}
としておくだけで、http://localhost/api/book/3
とするとIDが3のレコードを特定してくれます。あとはそのIDをキーとして中身を更新していきます。
よくあるRDBのアプローチですね。SQL
でUpdate文を発行するのと同じ理屈です。
MongoDBの場合
router.post(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.1) + "}}")
_ = try! collection.updateOne(filter: query, update: updatedBook)
return .ok
}
}
やっていることは単純だけれども複雑に見えてしまう……
ひとまず更新処理はできますが、ツッコミどころが少々。
- 今更ですが、プロトコルはpostかputのどちらが適切なのか。
query
で複数のドキュメントが対象となった時どうなのか。
query
で必ず1件に絞り込めるようにするか、updateMany
メソッドで複数一気に更新するかの対応をするのが良いかもしれません。
それと、特筆すべき点はHTTPStatus
です。FluentSQLiteでは更新したデータを表示しましたが、MongoDBでは更新できたらHTTPステータスの.ok
を返すようにしてみました。いわゆる200 OK
というやつです。
詰めの甘さは否めません。HTTPステータスを返すようにするなら、更新失敗の時などは分岐して適切なステータスを返すべきです。HTTPステータス周りは不慣れな領域なので掘り下げてみたい。
ドキュメントの削除
FluentSQLiteの場合
router.delete("api/book",Book.parameter) { req -> Future<Book> in
return try req.parameters.next(Book.self).delete(on: req)
}
更新の時と同様、http://localhost/api/book/3
とするとIDが3のレコードを特定してくれます。そのIDをキーとしてレコードを削除しています。
これまたよくあるRDBのアプローチ。SQLiteの場合は1ファイル1データベースになっていることが多いので、SQLで特定のファイル(データベース)と主キー値を指定してDelete
文を発行するイメージです。
MongoDBの場合
router.delete("api/book",String.parameter) { req -> HTTPStatus in
let client = try! req.make(MongoClient.self)
let collection = client.db("vaporapp").collection("books", withType: Book.self)
let title = try req.parameters.next(String.self).lowercased()
let query:Document = try Document.init(fromJSON: "{\"title\":\"" + title + "\" }")
_ = try! collection.deleteOne(query)
return .ok
}
今回はパラメータを渡して削除対象のドキュメントを指定してみました。「nukko」です。ドキュメントがちゃんと削除されています。見た通りのあっさりした処理です。こう見るとFluentSQLiteは楽ちんだなぁ…と感じます。
ここでもちょっと詰めが甘いですけどね。削除失敗した時や削除すべきドキュメントがなかった時など、それぞれに適切な応答を返すようにしておきたいですね。
なお、ここではdeleteOne
メソッドを使っています。query
で指定した条件に該当するドキュメントが複数の時はどうなるんでしょうね。おそらくドキュメントのうち「任意の」1つが削除されると思います。用意されているメソッド群を見る限り、「任意の」を制御できなさそうなので、素直に1件だけに絞り込める条件をquery
に指定するか、deleteMany
メソッドに切り替えるようにするのが良さそうです。コードを書く側の責任というところでしょうか。
まとめ
RDBに慣れきってしまっているので、更新のやり方に違和感が残っています。そもそもMongoDBはおらなんとかさんのようなRDBとは性質や用途が異なるので、考え方自体を変えてみるのが良いかもしれません。
ドキュメントの中身を更新するよりも、一旦削除して追加するとか。主キーでレコードを特定する、といったアプローチもMongoDBのようなNoSQLには通用しにくいですね。
ドキュメントの作りをRDB風にしてしまって、MongoDBを「なんちゃってRDB」に仕立て上げてしまうのも一つの対応方法ですけど、それはどうだろうかと思います。DBとハサミは使いようということで柔軟に行きたいですね。
あぁついでに、updateOne
、updateMany
メソッドの他にreplaceOne
メソッドを見つけました。それでなくとも違和感で困惑しているのに、使い分けに困るものを投下するMongoDB。掘り下げ必須ですね。
取り組みたい課題
今更かよ、という項目もありますが、復習ということでご勘弁を。
- NoSQLの設計思想を知る(RDBの扱い方と何が違うのか)
PUT
とPOST
の使い分け- HTTPステータスの使い方
updateOne
とreplaceOne
の違い
それと、今回はブラウザではなくMongoExpress
を使っています。なぜか。GET
できなくなったからです。この原因も探らないと……