Data Modeling for Reward Program

I hope this is the right place to ask about modeling the data. I put together a simple data and am trying to figure out what I need to do for model.

In my MERN app which is a reward program, I just want to display the list of customers, and once you click on the customer, it takes you to the customer’s recorded purchases per month. I will be using React to calculate how many reward points the customer earn for each purchase in a month, and the total reward points per month, and the total of 3 months’ reward points.

So basically, the database would be just the customers and its purchases and I will be using React to calculate the reward points.

So to put together the model for this, do I need one model like Customer.js or two models: Customer.js and Purchases.js?

Here’s what I put together so far for Customer.js:

    const mongoose = require('mongoose');

    const CustomerSchema = new mongoose.Schema({
      name: {
        type: String
      },
      account: {
        type: String
      },
      purchase: {
        type: Number
      },
      purchase_date: {
        type: Date
      }
    });

    module.exports = Customer = mongoose.model('customer', CustomerSchema);

Here’s a sample of my data:

    [
      {
      "account": "ABC123",
      "name": "John Doe",
      "purchase-history": [
        {
          "month1": "January",
          "transitions": [
            {
              "date": "01/01/20",
              "purchase": 120
            },
            {
              "date": "01/03/20",
              "purchase": 195
            },
            {
              "date": "01/15/20",
              "purchase": 49
            },
            {
              "date": "01/23/20",
              "purchase": 63
            }
          ]
        },
        {
          "month2": "February",
          "transitions": [
            {
              "date": "02/01/20",
              "purchase": 120
            },
            {
              "date": "02/03/20",
              "purchase": 85
            },
            {
              "date": "02/15/20",
              "purchase": 300
            },
            {
              "date": "02/23/20",
              "purchase": 51
            }
          ]
        },
        {
          "month3": "March",
          "transitions": [
            {
              "date": "03/01/20",
              "purchase": 120
            },
            {
              "date": "03/03/20",
              "purchase": 225
            },
            {
              "date": "03/15/20",
              "purchase": 9
            },
            {
              "date": "03/23/20",
              "purchase": 72
            }
          ]
        },
        {
          "month4": "April",
          "transitions": [
            {
              "date": "04/03/20",
              "purchase": 195
            },
            {
              "date": "04/23/20",
              "purchase": 63
            }
          ]
        },
        {
          "month5": "May",
          "transitions": [
            {
              "date": "05/01/20",
              "purchase": 161
            },
            {
              "date": "05/03/20",
              "purchase": 155
            },
            {
              "date": "05/15/20",
              "purchase": 52
            },
            {
              "date": "05/23/20",
              "purchase": 84
            }
          ]
        }
        ]
      },
      {
        "account": "ACD134",
        "name": "Nancy Drew",
        "purchase-history": [
          {
            "month1": "January",
            "transitions": [
              {
                "date": "01/01/20",
                "purchase": 130
              },
              {
                "date": "01/03/20",
                "purchase": 185
              },
              {
                "date": "01/15/20",
                "purchase": 59
              },
              {
                "date": "01/23/20",
                "purchase": 33
              }
            ]
          },
          {
            "month2": "February",
            "transitions": [
              {
                "date": "02/01/20",
                "purchase": 110
              },
              {
                "date": "02/03/20",
                "purchase": 25
              },
              {
                "date": "02/15/20",
                "purchase": 340
              },
              {
                "date": "02/23/20",
                "purchase": 71
              }
            ]
          },
          {
            "month3": "March",
            "transitions": [
              {
                "date": "03/01/20",
                "purchase": 150
              },
              {
                "date": "03/03/20",
                "purchase": 215
              },
              {
                "date": "03/15/20",
                "purchase": 49
              },
              {
                "date": "03/23/20",
                "purchase": 82
              }
            ]
          },
          {
            "month4": "April",
            "transitions": [
              {
                "date": "04/03/20",
                "purchase": 125
              },
              {
                "date": "04/23/20",
                "purchase": 83
              }
            ]
          },
          {
            "month5": "May",
            "transitions": [
              {
                "date": "05/01/20",
                "purchase": 181
              },
              {
                "date": "05/03/20",
                "purchase": 115
              },
              {
                "date": "05/15/20",
                "purchase": 62
              },
              {
                "date": "05/23/20",
                "purchase": 14
              }
            ]
          }
          ]
        }

Hi @Kristina_Bressler,

The problem with the described data model is that in purchases collection there is a possibility to have unbonded arrays which is a. Big no no and antipattern for MongoDB.

Since those arrays will constantly update it will introduce update overhead as well as complexity.

I would suggest going the following alternative:

  1. Keep a document per month for each account . Index the account field and a new date field describing the month to be able to sort properly + iterate over the query to show in client:
{
      "account": "ABC123",
      "name": "John Doe",
      "purchase-history" : {
       month: "January",
       monthDate: ISODate("2020-01-01"),
      "transitions" : [ ... ]}
  1. If the monthly volume is large consider having a monthly/yearly collection partition data based on collection names:
purchase_2020

I would suggest reading our anti pattern blog for more useful schema design known no nos
https://www.mongodb.com/article/schema-design-anti-pattern-summary/

Thanks
Pavel

In my project, I’m just going to limit it to just 3 months only. I’ll probably edit my data to change it to month01, month02, and month03. So basically, just a list of customers with 3 months of purchases each… Would this be fine? I’m not going to code the app to add more purchases every month…

Hi @Kristina_Bressler,

Can the transitions array grow unboundly?

I don’t think so. I’m just creating the data so that I can display the list of customers, click on the customer to display their purchase history, and use React to calculate how much reward points they received each purchase, and the total reward points each month.