Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Model Customer Surveys in a Graph Database

DZone's Guide to

How to Model Customer Surveys in a Graph Database

Want to incorporate customer feedback into a visually appealing graph database? Look no further. See how to do just that here.

· Database Zone
Free Resource

What if you could learn how to use MongoDB directly from the experts, on your schedule, for free? We've put together the ultimate guide for learning MongoDBSign up and you'll receive instructions for how to get started!

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 questions we would like to ask the user. These questions 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 answers leading the user to the next question, or to the end of the survey. These answers 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 products 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:

  1. 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).

  2. 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 this repository.

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).

What if you could learn how to use MongoDB directly from the experts, on your schedule, for free? We've put together the ultimate guide for learning MongoDBSign up and you'll receive instructions for how to get started!

Topics:
endpoints ,graph database ,database ,javascript

Published at DZone with permission of Michael Hackstein, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}