Read-Only Views in MongoDB 3.4

Damien Cosset - Jul 16 '17 - - Dev Community

Introduction

MongoDB introduced read-only views in version 3.4. This article will briefly explore what they are, how you can use them and why you would want to use them.

What?

Read-only views are synthetic collections. They act a lot like a real collection in terms of reading from them, outside from the fact that they do not contain any documents. A view's state depends on a real collection, the source collection. Everytime you query a read-only view, you essentially aggregate the source collection. You can't write to a view (that's the read-only part!). If you update the source collection, it will propagate to your view (depending on how you define your view). Outside from creating a new document, (which we will see shortly after), views do not take up any space.

How?

An example might be more useful at this point. I will use the mongo shell to demonstrate a read-only view. Here is my database right now:

> use team
switched to db team

> db.players.find().pretty()
{
        "_id" : ObjectId("596b44d46879c5e83035f228"),
        "firstName" : "Joe",
        "lastName" : "Johnson",
        "position" : "forward",
        "number" : 9,
        "isMarried" : true
}
{
        "_id" : ObjectId("596b44d46879c5e83035f229"),
        "firstName" : "Alex",
        "lastName" : "Jones",
        "position" : "goalkeeper",
        "number" : 16,
        "isMarried" : false
}
{
        "_id" : ObjectId("596b44d46879c5e83035f22a"),
        "firstName" : "Ethan",
        "lastName" : "Hawk",
        "position" : "defender",
        "number" : 4,
        "isMarried" : false
}
{
        "_id" : ObjectId("596b44d46879c5e83035f22b"),
        "firstName" : "Richard",
        "lastName" : "Dawkins",
        "position" : "midfielder",
        "number" : 8,
        "isMarried" : true
}
Enter fullscreen mode Exit fullscreen mode

I have a bunch of documents in my players collection. In each document is defined firstName, lastName, position, number and isMarried. Next, I will create a read-only view with db.createView(). It takes 3 arguments: the first is your view's name, the second is the source collection and the third is an array containing your aggregation pipeline. This is where you define how your view will manipulate the source collection.

> db.createView("playersInfos", "players", [ {$project : { "firstName": 1, "lastName": 1, "position": 1} } ] )
{ "ok" : 1 }

> show collections
players
playersInfos
system.views

> db.system.views.find()
{ "_id" : "team.playersInfos", "viewOn" : "players", "pipeline" : [ { "$project" : { "firstName" : 1, "lastName" : 1, "position" : 1 } } ] }
Enter fullscreen mode Exit fullscreen mode

I named my view playersInfos. The source collection is players. In the third argument, I only use $project to return the firstName, lastName and position fields. When I look for collections in my database, I can see my playersInfos and system.views have been added. system.views keeps track of your views. Now, let's query our view. You do this like you would any other collection:

> db.playersInfos.find()
{ "_id" : ObjectId("596b44d46879c5e83035f228"), "firstName" : "Joe", "lastName" : "Johnson", "position" : "forward" }
{ "_id" : ObjectId("596b44d46879c5e83035f229"), "firstName" : "Alex", "lastName" : "Jones", "position" : "goalkeeper" }
{ "_id" : ObjectId("596b44d46879c5e83035f22a"), "firstName" : "Ethan", "lastName" : "Hawk", "position" : "defender" }
{ "_id" : ObjectId("596b44d46879c5e83035f22b"), "firstName" : "Richard", "lastName" : "Dawkins", "position" : "midfielder" }
Enter fullscreen mode Exit fullscreen mode

I only get back what was defined in my aggregation pipeline when I created my view.

Now, I forgot one player in my team. I will add a new document to my players collection and query my view again. Remember that you can't write to a read-only view.

> db.players.insertOne({"firstName": "Rigoberto", "lastName": "Song", "position": "wingback", "number": 2, "isMarried": false})
{
        "acknowledged" : true,
        "insertedId" : ObjectId("596b47506879c5e83035f22c")
}
> db.playersInfos.find()
{ "_id" : ObjectId("596b44d46879c5e83035f228"), "firstName" : "Joe", "lastName" : "Johnson", "position" : "forward" }
{ "_id" : ObjectId("596b44d46879c5e83035f229"), "firstName" : "Alex", "lastName" : "Jones", "position" : "goalkeeper" }
{ "_id" : ObjectId("596b44d46879c5e83035f22a"), "firstName" : "Ethan", "lastName" : "Hawk", "position" : "defender" }
{ "_id" : ObjectId("596b44d46879c5e83035f22b"), "firstName" : "Richard", "lastName" : "Dawkins", "position" : "midfielder" }
{ "_id" : ObjectId("596b47506879c5e83035f22c"), "firstName" : "Rigoberto", "lastName" : "Song", "position" : "wingback" }
Enter fullscreen mode Exit fullscreen mode

The document has been inserted in the source collection, and my view is able to query it right away.

Great! Now let's complicate things a little bit. I will delete my view and create it again. I will modify the aggregator pipeline.

> db.playersInfos.drop()
true

> db.createView("playersInfos", "players", [{$project : { "fullName": {$concat: ["$firstName", " ", "$lastName"]}, "position": 1}}])
{ "ok" : 1 }

> db.playersInfos.find()
{ "_id" : ObjectId("596b44d46879c5e83035f228"), "position" : "forward", "fullName" : "Joe Johnson" }
{ "_id" : ObjectId("596b44d46879c5e83035f229"), "position" : "goalkeeper", "fullName" : "Alex Jones" }
{ "_id" : ObjectId("596b44d46879c5e83035f22a"), "position" : "defender", "fullName" : "Ethan Hawk" }
{ "_id" : ObjectId("596b44d46879c5e83035f22b"), "position" : "midfielder", "fullName" : "Richard Dawkins" }
{ "_id" : ObjectId("596b47506879c5e83035f22c"), "position" : "wingback", "fullName" : "Rigoberto Song" }
Enter fullscreen mode Exit fullscreen mode

This time, I don't want firstName and lastName in separate fields. I want only one field fullName. I used $concat in my third argument to create this.

Why?

Why would you want to use views? After all, we can already do all of this with regular collections. Two reasons:

  • Security

You can allow an application to query a view without exposing certain fields. If you store sensitive information in your collection, those informations can't be accessed from a view if it is defined correctly, even if those informations are in the source collection.

  • Simplicity

If you find yourself querying for the same informations over and over, you can define a view that will allow you to not specify the query criterias every single time you need to query a collection.

Warnings

You can't use MapReduce, $text, $, $slice, $elemMatch, $meta with a view.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .