How to model customer surveys in a graph database
Use-Case
The graph database use-case we are stepping through in this post is the following: In our web application we have several places where a user is led through a survey, where she decides on details for one of our products. Some of the options within the survey depend on previous decisions and some are independent.
Examples:
- Configure a new car
- Configure a new laptop
- Book extras with your flight (meal, reserve seat etc.)
- Configure a new complete kitchen
- Collect customer feedback via logic-jump surveys
We would like to easily offer a generic page which can be seeded with any decision-tree and just plays the Q&A game with the user. We also want to be able to easily create new and modify existing decision trees in case the products change.
Graph Database Data Model
When speaking of trees it directly pops into my mind that a graph database probably is a good solution to store the data in, well every tree is a graph. So in this case the data model is actually pretty simple: First we have question
s we would like to ask the user. These question
s are modeled as vertices
having an attribute query
stating the string that should be displayed to the user. In case of a localized application we will not have one query
attribute, but one for every language we support (e.g. en
, de
, jp
etc.), for simplicity we now assume our shop is only available in English.
Attached to each question
we have a list of possible answer
s leading the user to the next question
, or to the end of the survey. These answer
s are best realized as the edges
connecting everything together. Each answer
has again a text
attribute that should be presented to the user. Finally we have product
s which best fit for a user after undergoing the survey.
Let’s now visualize a simple decision tree. We are in an online Avocado Shop and want to give our customer the best Avocado:
Tips and Tricks
If we set up the surveys using a graph all vertices in the graph can be reused in several surveys.
This is especially important if you want to offer the same product in completely different surveys. You do not have to store the product twice, making it easier to update. It is also important if you have overlapping surveys, say for two surveys the starting point is different, but based on some decisions the user will end up in the same dialog for both surveys and can only reach identical products with identical questions.
An example for this is if you are selling and leasing cars to a user. You at first have two sub different dialogs asking the user for selling respectively leasing conditions, but at some point in both dialogs the user finally has to decide on the car. This car selection process is again identical for both surveys.
UI
Sorry I am no UX designer 😉 In general I would require only two API endpoints:
showQuestion/:id
Which would enter the survey at any point and return the question with it’s set of possible answers (the number of answers at this point is unknown and my UI widget is able to display an arbitrary number of option boxes).showProduct/:id
Which will show the product because the user is at the end of her survey.
If I want to hold some user state the UI part is responsible for it, it can for instance maintain a list of questions the user has visited and if the user presses “back” it can just resend showQuestion/:id
with the second to last question.
Queries
In the first endpoints we need to return a query and all it’s possible answers. Using the data model design from above this means we have to select one question by it’s _id
and all its outgoing edges.
In ArangoDB the query to achieve this is the following (including comments):
```
FOR question IN questions
// Select the question. No worries we use the Primary Index O(1), no looping here ;)
FILTER question._key == @id
// Here we start a subquery to join together all answers into a single array
LET answers = (
// Now we select all outbound edges from question
FOR next, answer IN OUTBOUND question answers
// We make a projection to only return the text and the id of the next object
// If we would use localisation this place has to be adjusted
// We have to select the correct translation
// Also we use the indicator that if next has a query we are not at the end
RETURN {nextId: next._key, text: answer.text, isFinal: next.query == null}
)
// Finally we return the query, and the list of possible answers
RETURN {
query: question.query,
answers: answers
}
```
For the other endpoint we only have to display the product by its id
.
This can be done via the following even simpler query:
```
FOR product IN products
// Select the Product. No worries we use the Primary Index O(1), no looping here ;)
FILTER product._key == @id
RETURN product
```
Creating an API out of it
ArangoDB also offers a nice little framework called Foxx. This framework can easily wrap both queries into nice API endpoints by only adding a couple of lines of JS to it.
In this example we will use the nice shorthand notations that are offered by ES6.
```
// First just use our queries:
const questionQuery = `
FOR question IN questions
FILTER question._key == @id
LET answers = (
FOR next, answer IN OUTBOUND question answers
RETURN {nextId: next._key, text: answer.text, isFinal: next.query == null}
)
RETURN {
query: question.query,
answers: answers
}`;
const productQuery = `
FOR product IN products
FILTER product._key == @id
RETURN product
`;
// Now define the routes:
router.get("/showQuestion/:id", (req, res) => {
let cursor = db._query(questionQuery, {id: req.pathParams.id})
res.json(cursor.toArray());
});
router.get("/showProduct/:id", (req, res) => {
let cursor = db._query(productQuery, {id: req.pathParams.id})
res.json(cursor.toArray());
});
```
And there we go.
See it in action
Luckily Foxx can also serve static files, which can contain HTML and JS code. This feature is useful if you want to ship a small administration frontend with you Foxx App.
In this post I will misuse it to ship my entire survey-demo widget with it. You can simply install it from github using the following repository: https://github.com/arangodb-foxx/survey-generator
Have fun trying it out or even enhance it for your Product if you like it. It is all Apache 2 Licence (and so is ArangoDB).
2 Comments
Leave a Comment
Get the latest tutorials, blog posts and news:
Hi michael, i want to know how to run and install https://github.com/arangodb-foxx/survey-generator.can you please provide steps,as soon as possible.
Hi Ravi,
i have added a README to the repository that should explain how to get it up and running.
Here is the content of it:
#survey-generator
In order to test this App you need to have an ArangoDB running. Now you have two options:
1. WebUI
Open the web ui default: https://localhost:8529/.
Navigate to SERVICES on the left.
Click on “Add Service”
On the top tab bar select “GitHub”.
Fill out the form:
Repository: “arangodb-foxx/survey-generator”
Version: “master”
Click INSTALL
Fill out:
Mount point: your choice, e.g. “/survey”
Run Setup?: check
Click Install
Now you see this App deployed, click on it.
2.Foxx CLI
Requires Node, install the FoxxCLI:
Using Yarn: yarn global add foxx-cli
Or using NPM: npm install –global foxx-cli
Clone this repository to any path: or download the zip file to
Now you can run: foxx install “/survey” on either the repo or the zip file.
Now you App is deployed under /survey.
If you are using default configuration of arangodb and installed this app under survey you can access it now at: default: https://localhost:8529/_db/_system/survey/index.html.