Shopping Mall directory

Lets look at the needs of application used to manage Shopping Mall properties. The application assists employees in the day-to-day operations of multiple Shopping Malls.

Requirements

  1. As a Maintenance Worker, I need to know which stores are currently in each Mall down to the Building they are located.
  2. As a Helpdesk Employee, I need to locate related stores in Mall locations by Store Category.
  3. As a Property Manager, I need to identify upcoming leases in need of renewal.

Example Setup

Example Setup

Table Definition
{
  "TableName": "electro",
  "KeySchema":[
      {
          "AttributeName":"pk",
          "KeyType":"HASH"
      },
      {
          "AttributeName":"sk",
          "KeyType":"RANGE"
      }
  ],
  "AttributeDefinitions":[
      {
          "AttributeName":"pk",
          "AttributeType":"S"
      },
      {
          "AttributeName":"sk",
          "AttributeType":"S"
      },
      {
          "AttributeName":"gsi1pk",
          "AttributeType":"S"
      },
      {
          "AttributeName":"gsi1sk",
          "AttributeType":"S"
      }
  ],
  "GlobalSecondaryIndexes":[
      {
          "IndexName":"gsi1pk-gsi1sk-index",
          "KeySchema":[
              {
                  "AttributeName":"gsi1pk",
                  "KeyType":"HASH"
              },
              {
                  "AttributeName":"gsi1sk",
                  "KeyType":"RANGE"
              }
          ],
          "Projection":{
              "ProjectionType":"ALL"
          }
      }
  ],
  "BillingMode":"PAY_PER_REQUEST"
}
Example Entity
import DynamoDB from "aws-sdk/clients/dynamodb";
import { Entity } from 'electrodb';

const client = new DynamoDB.DocumentClient();

const table = 'electro';

const StoreLocations = new Entity({
    model: {
      service: "MallStoreDirectory",
      entity: "MallStore",
      version: "1",
    },
    attributes: {
        cityId: {
            type: "string",
            required: true,
        },
        mallId: {
            type: "string",
            required: true,
        },
        storeId: {
            type: "string",
            required: true,
        },
        buildingId: {
            type: "string",
            required: true,
        },
        unitId: {
            type: "string",
            required: true,
        },
        category: {
            type: [
                "spite store",
                "food/coffee",
                "food/meal",
                "clothing",
                "electronics",
                "department",
                "misc"
            ],
            required: true
        },
        leaseEndDate: {
            type: "string",
            required: true
        },
        rent: {
            type: "string",
            required: true,
            validate: /^(\d+\.\d{2})$/
        },
        discount: {
            type: "string",
            required: false,
            default: "0.00",
            validate: /^(\d+\.\d{2})$/
        }
    },
    indexes: {
        stores: {
            pk: {
                field: "pk",
                composite: ["cityId", "mallId"]
            },
            sk: {
                field: "sk",
                composite: ["buildingId", "storeId"]
            }
        },
        units: {
            index: "gis1pk-gsi1sk-index",
            pk: {
                field: "gis1pk",
                composite: ["mallId"]
            },
            sk: {
                field: "gsi1sk",
                composite: ["buildingId", "unitId"]
            }
        },
        leases: {
            index: "gis2pk-gsi2sk-index",
            pk: {
                field: "gis2pk",
                composite: ["storeId"]
            },
            sk: {
                field: "gsi2sk",
                composite: ["leaseEndDate"]
            }
        }
    }
}, {table, client});
(Example code on this page that references the entity StoreLocations uses the following Entity and Table Definition found below)

Mutations

Add a new Store to the Mall

await StoreLocations.create({
	mallId: "EastPointe",
	storeId: "LatteLarrys",
	buildingId: "BuildingA1",
	unitId: "B47",
	category: "spite store",
	leaseEndDate: "2020-02-29",
	rent: "5000.00",
}).go();

Returns the following:

{
  "data": {
    "mallId": "EastPointe",
    "storeId": "LatteLarrys",
    "buildingId": "BuildingA1",
    "unitId": "B47",
    "category": "spite store",
    "leaseEndDate": "2020-02-29",
    "rent": "5000.00",
    "discount": "0.00"
  }
}

Change the Stores Lease Date

When updating a record, you must include all Composite Attributes associated with the table’s primary PK and SK.

let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";

await StoreLocations
    .patch({ storeId, mallId, buildingId, unitId })
    .set({ leaseEndDate: "2021-02-28" })
    .go();

Returns the following:

{
  "data": {
	  "leaseEndDate": "2021-02-28"
  }
}

Retrieve a specific Store in a Mall

When retrieving a specific record, you must include all Composite Attributes associated with the table’s primary PK and SK.

let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";

await StoreLocations
    .get({ storeId, mallId, buildingId, unitId })
    .go();

Returns the following:

{
  "data": {
    "mallId": "EastPointe",
    "storeId": "LatteLarrys",
    "buildingId": "BuildingA1",
    "unitId": "B47",
    "category": "spite store",
    "leaseEndDate": "2021-02-28",
    "rent": "5000.00",
    "discount": "0.00"
  }
}

Remove a Store location from the Mall

When removing a specific record, you must include all Composite Attributes associated with the table’s primary PK and SK.

let storeId = "LatteLarrys";
let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
let storeId = "LatteLarrys";

await StoreLocations
    .delete({ storeId, mallId, buildingId, unitId })
    .go();

Returns the following:

{ "data": {} }

Queries

All Stores in a particular mall

Fulfilling Requirement #1.

let mallId = "EastPointe";

let stores = await StoreLocations.query
    .malls({ mallId })
    .go();

All Stores in a particular mall building

Fulfilling Requirement #1.

let mallId = "EastPointe";
let buildingId = "BuildingA1";

let stores = await StoreLocations.query
    .malls({ mallId, buildingId })
    .go();

Find the store located in unit B47

Fulfilling Requirement #1.

let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";

let stores = await StoreLocations.query
    .malls({ mallId, buildingId, unitId })
    .go();

Stores by Category at Mall

Fulfilling Requirement #2.

let mallId = "EastPointe";
let category = "food/coffee";

let stores = await StoreLocations.query
    .malls({mallId})
    .where((attr, op) => op.eq(attr.category, category))
    .go();

Stores by upcoming lease

Fulfilling Requirement #3.

let mallId = "EastPointe";
let q2StartDate = "2020-04-01";

let stores = await StoreLocations.query
    .leases({mallId})
    .lt({leaseEndDate: q2StateDate})
    .go();

Stores will renewals for Q4

Fulfilling Requirement #3.

let mallId = "EastPointe";
let q4StartDate = "2020-10-01";
let q4EndDate = "2020-12-31";

let stores = await StoreLocations.leases(mallId)
    .between(
        { leaseEndDate: q4StartDate },
        { leaseEndDate: q4EndDate })
    .go();

Spite-stores with release renewals this year

Fulfilling Requirement #3.

let mallId = "EastPointe";
let yearStarDate = "2020-01-01";
let yearEndDate = "2020-12-31";
let storeId = "LatteLarrys";

let stores = await StoreLocations.leases(mallId)
    .between (
      { leaseEndDate: yearStarDate },
      { leaseEndDate: yearEndDate })
    .where((attr, op) => op.eq(attr.category, "Spite Store"))
    .go();

All Latte Larry’s in a particular mall building

let mallId = "EastPointe";
let buildingId = "BuildingA1";
let unitId = "B47";
let storeId = "LatteLarrys";

let stores = await StoreLocations.query
    .malls({ mallId, buildingId, storeId })
    .go();