MongoDBのドキュメントの更新はPUTで良いのか考える

MongoDB
MongoDBのドキュメントの更新はPUTで良いのか考える

はじめに

PUTPOSTの区別がつき、概ね認識が間違ってなさそうなことが分かりました。
そこでひっそりと登場してきたのがPATCH

気がついた以上見逃すわけにはいかない。というわけで、これまで書いてきたコードを見直してみます。

MongoDBのドキュメントの更新処理を見直す

見直し対象のコード

ドキュメントの更新処理で利用するコードです。
指定のドキュメントのtitle末尾に(更新)をつける処理です。これは果たしてPUTで良いのかどうか?

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\": {\"title\":\"" + book.title + "(更新)\"" + "}}")
        _ = try! collection.updateMany(filter: query, update: updatedBook)
        return .ok
    }
}

返り値が.okなのは見逃してくださいお代官様。

HTTPメソッドの意味合いから考えると、これはPATCHではないでしょうか。

書き直してみる

早速書き直してみます。

router.patch(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\": {\"title\":\"" + book.title + "(更新)\"" + "}}")
        _ = try! collection.updateMany(filter: query, update: updatedBook)
        return .ok
    }
}

PUTPATCHに変わっただけで、中身はそのまま。

ではPUTはどうなるの?

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 + "\"}")
        _ = try! collection.replaceOne(filter: query, replacement: book)
        return .ok
    }
}

元々のコードを生かした状態で書き換えたのでクエリに若干の無理がありますが、replaceOneでドキュメントを置き換えています。

PUTは指定したURIに紐づくドキュメントを扱うものなので、router.put(Book.self, at:"api/book")の形ではなく、router.put("api/book",String.parameter)でドキュメントを特定できるパラメータを渡してクエリを組むのが良さそうです。更新ドキュメントはリクエストボディに詰め込んでおきましょう。

いずれにせよ、PUTならばreplaceOnePATCHならばupdateManyまたはupdateOne、と対応付けておくと良さそうです。

まとめ

PUTPATCHの違いを意識して見直してみました。PUTでも部分的な更新をすることができるので、ここまでこだわる必要があるかどうかというところですが、HTTPメソッドの正しい(と思う)使い方を目指してみるという点ではこだわってみるのも良いかもしれません。

メジャーどころの御三家GETPOSTDELETEに比べると、影の薄さは否めません。でも、メソッドとして存在するからにはちゃんと理解して使ってみたいですよね。

補足

PATCHはRFCに現れたり消えたりしていた経緯があるようです。確かに、PUTPATCHを明確に区別する必要もなさそうな気もします。そもそもPUTで事足りてしまったためにそこまで利用する人が居なかったということだったのかもしれません。

また、クライアントアプリケーション側がPATCHに対応していない可能性があります。こだわりのAPIを公開してもクライアント側から使うことができなければ意味がなくなってしまいますので注意が必要です。