Customizing RavenDB: Read-only and read-write document stores

One of the simple customizations we've made to RavenDB is the ability to specify read-only vs. read-write document stores.  We have editorial and ingest applications that need to write into our RavenDB databases.  However, we don't want anyone -- even ourselves -- to be able to write to most of those databases from the public apps that render our various sites.

Raven has a lot of extensibility points, and we used one of them to make some document stores read-only from some of our apps.

The Raven.Client.Listeners namespace contains several interfaces that allow you to hook into any query, save or delete that happens on a given DocumentStore instance.  For this feature, we used IDocumentStoreListener and IDocumentDeleteListener. We implemented both in a ReadOnlyListener class that throws on any attempt to call Store() or Delete().  That happens on the client side, in our app, before any communication with the Raven server.

Here's the complete code of ReadOnlyListener:

    public class ReadOnlyListener : IDocumentStoreListenerIDocumentDeleteListener
    {
        private const string ErrorMessage = 
            "The store is read-only. To enable writes, use StoreAccessMode.ReadWrite.";

        public void AfterStore(string key, object entityInstance, RavenJObject metadata)
        {
            // Do nothing.
        }

        public bool BeforeStore(string key, object entityInstance, RavenJObject metadata)
        {
            throw new InvalidOperationException(ErrorMessage);
        }

        public void BeforeDelete(string key, object entityInstance, RavenJObject metadata)
        {
            throw new InvalidOperationException(ErrorMessage);
        }
    }

 

The Before methods are executed by the Raven client, um, before the call to the Raven server.

We have a class called PlatformDocumentStore that enforces some conventions when creating a DocumentStore. This is a typical call that happens at app startup:

    PlatformDocumentStore.Register(container, StoreName.Content);

 

That line of code creates an instance of DocumentStore pointed at the nearest read-only version of a RavenDB database named Content.  It also registers it in the container (an instance of UnityContainer).  The second parameter is just a string.  We don't want arbitrary stores being created; the StoreName class contains the names of the "allowed" stores.  When we register the store, we register it as IDocumentStore using the store name.  That allows us to inject it into our controllers with Unity's [Dependency] attribute and specify the name of the store.

But back to read-only vs. read-write...  What you don't see is the optional third parameter.  An editorial app would have this line at app startup:

    PlatformDocumentStore.Register(container, StoreName.Content, StoreAccessMode.ReadWrite);

 

StoreAccessMode defaults to ReadOnly.  That third parameter says it's okay for the application to write to the store.  When we new up the DocumentStore instance inside of PlatformDocumentStore, we have this code:

    if (accessMode == StoreAccessMode.ReadOnly)
    {
        documentStore.RegisterListener((IDocumentStoreListener)new ReadOnlyListener());
        documentStore.RegisterListener((IDocumentDeleteListener)new ReadOnlyListener());
    }

With the listener attached for both Store() and Delete() calls, we have effectively prevented any modifications to the data from within this app.

It's important to note that the database on the Raven server is perfectly happy to accept writes -- we've just hooked into all attempts to use the client-side API from within this one application.  Also, a developer could simply new up a DocumentStore without the listeners and call Store() or Delete() to their heart's content.  This is not a security measure -- it's just a simple way to prevent inadvertent writes where we don't want them.  Like setting the Read-only flag on a file in the file system.

Discuss this post

You're in Easy Mode. If you prefer, you can use XHTML Mode instead.
As a new user, you may notice a few temporary content restrictions. Click here for more info.