Docussandra marries MongoDB's simple data storage model with the horizontal scaling of Cassandra. It enables developers to store arbitrary payloads as BSON, as they would with MongoDB, in a Cassandra cluster. It supports indexing, filtering, sorting, querying and pagination (via familiar limit and offset semantics), all at a blazing speed. Simple json document storage with effortless scaling exposed as a service - that's Docussandra!
Let's begin by starting a server and beginning to store data.
A running Cassandra instance and maven (for building Java packages).
Before we can start the Docussandra API server that talks to Cassandra we have to create the keyspace where data will be stored. We do this by loading the schema found in the rest/src/main/resources/docussandra.cql directory. On the command line this looks like:
cqlsh -f rest/src/main/resources/docussandra.cql
To build the project, type: (Disclaimer: we have negative tests that run, so the output might look scary, it's probably normal.)
mvn clean install
Next we need to build and run the API server. First, if using a command line, navigate to the api project folder in the root project. Once there, type:
mvn exec:java -Dexec.mainClass="com.pearson.docussandra.Main"
If successful, the Docussandra will now be listening to requests on 127.0.0.1:8081.
Once the server has been started we can begin to interact with it. The first step is a create a database in which to store data. This can be done by making a POST request to:
The {databaseName} variable mentioned above can be whatever the user would like as long as it has not been previously used. Names must be globally unique and in lower case. Otherwise an error will result.
To create a logical grouping of similar data we'll create a table. We can do this by making another POST request to:
http://localhost:8081/databases/*{databaseName}*/tables/*{tablename}*
Naming restrictions on the table name are similar to the database: unique to the database and in lower case.
Storing information to Docussandra is simple. To add a JSON document to a table that you've created, POST the document to the table.
http://localhost:8081/databases/*{databaseName}*/tables/*{tablename}*/documents
Suppose that we're interested in storing information about the appearances of various superhero characters in comics. First, we need to create the database with Docussandra:
With our comic database created, we now need to create a table for JSON documents that describe our superhero (and villian) appearances.
POST http://localhost:8081/databases/comics/tables/appearances
What does that appearance JSON document look like? Perhaps we want to store something like:
{
"name":"Wolverine",
"date":"1974-11-01",
"comic":"Incredible Hulk, The",
"title":"And Now... the Wolverine!",
"number":"181"
}
That JSON document contains a number of important pieces of information: the name of the character, the date of this appearance, the name of the comic, the title and number of the issue. Good stuff, to be sure. But in our use case we are most concerned with returning information primarily by the character name, followed by the date of the appearance. To do that, we need to create an index. We do this by posting to the reserved word indexes at our endpoint, followed by the name we're giving that index.
POST http://localhost:8081/databases/comics/tables/appearances/indexes/namedate
In the body of the POST, we include the fields within each JSON document that we want Docussandra to index the information by:
{ "fields":["name","date"] }
With the index created, we can now add our data.
POST http://localhost:8081/databases/comics/tables/appearances/
with a body of:
{ "name":"Wolverine", "date":"1974-11-01", "comic":"Incredible Hulk, The", "title":"And Now... the Wolverine!", "number":"181" }
Note: While the endpoint of "http://localhost:8081/databases/comics/tables/appearances" was used to create the table, data is written to be written to "http://localhost:8081/databases/comics/tables/appearances/documents".
We can retrieve all our appearances by making a GET request to our table:
GET http://localhost:8081/databases/comics/tables/appearances/documents
Docussandra now supports plugin hooks that allow for custom code to be run as part of Docussandra to support security and notifications of document changes.
To implement and use your own plugins in Docussandra, you must:
- Create a new Java Project for your plugin/plugins.
- Include the Docussandra plugins project as a dependency:
<dependency>
<groupId>com.pearson.docussandra</groupId>
<artifactId>docussandra-plugin</artifactId>
<version>1.0.1-SNAPSHOT</version>
</dependency>
- Implement one or both of the provided interfaces (technically abstract classes):
NotifierPlugin
orSecurityPlugin
. - Build your project with any dependencies into a jar.
- Rename the jar so that it starts with "plugin"; ensure it keeps it's .jar file extension.
- Drop the jar (or jars) into the home directory of the user that is running the Docussandra service/application. Ensure that the permissions are correct for the user. (Please note, order of execution of plugins is arbitrary, don't expect any ordering unless you provide this yourself. Also note that these plugins are executed synchronously as part of Docussandra's data flow, be cautious and mindful of performance.)
SecurityPlugins are executed prior to the processing of any (every) HTTP request. You will be provided a HashMap
of HTTP headers, the requested path, and the HttpMethod that is being requested. You should throw a PermissionDeniedException
if the user/client should receive an permissions error, be sure to catch and handle all other exceptions gracefully. Reminder be mindful of performance; some sort of caching is recommended.
/**
* Method that performs a validation based on the passed in headers. Should
* throw a PermissionDeniedException if the client is not authorized.
*
* @param headers Map of the headers.
* @param requestedPath Path that is being requested.
* @param method Type of Http method that was requested.
* @throws PermissionDeniedException If the client is not authorized to make
* this call.
*/
public abstract void doValidate(HashMap<String, List<String>> headers, String requestedPath, HttpMethod method) throws PermissionDeniedException;
NotiferPlugins are executed immediately after any document is mutated (created, updated, or deleted). You will be provided with a type of mutation (CREATE, UPDATE, or DELETE) and the Document that has changed. Note, that the Document could be null
(especially in the case of a delete) and you should always check it for null
. Depending on the application, these type of notifications could occur very frequently, be mindful of performance and resource usage. Be sure to catch all exceptions and handle them gracefully.
/**
* This method will get called any time a document is mutated. Be careful
* about the amount of overhead this method produces, as it will be called
* frequently.
*
* @param type Type of mutation that has occurred.
* @param document Updated document for this mutation. Will be null if the
* mutation was a delete, be sure to check for null.
*/
public abstract void doNotify(MutateType type, Document document);
In chronological order by contribution:
- Todd Fredrich -- Architect and Concept of Operations
- Jeffrey DeYoung -- Lead Developer
- Matthew Rienbold -- Architect
- Chandan Kumar -- Software Engineer
- Anusha Vidap -- Software Development Intern
- Hillary Moore -- Project Owner
- Apache Usergrid (http://usergrid.apache.org/)
Copyright 2016 Pearson Education, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.