Shopping Mall directory

Let’s 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"
      }
    },
    {
      "IndexName": "gsi2pk-gsi2sk-index",
      "KeySchema": [
        {
          "AttributeName": "gsi2pk",
          "KeyType": "HASH"
        },
        {
          "AttributeName": "gsi2sk",
          "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})$/,
      },
      tenants: {
        type: "set",
        items: "string",
      },
      warnings: {
        type: "number",
        default: 0,
      },
      deposit: {
        type: "number",
      },
      contact: {
        type: "set",
        items: "string",
      },
      rentalAgreement: {
        type: "list",
        items: {
          type: "map",
          properties: {
            type: {
              type: "string",
            },
            detail: {
              type: "string",
            },
          },
        },
      },
      petFee: {
        type: "number",
      },
      fees: {
        type: "number",
      },
      tags: {
        type: "set",
        items: "string",
      },
    },
    indexes: {
      stores: {
        pk: {
          field: "pk",
          composite: ["cityId", "mallId"],
        },
        sk: {
          field: "sk",
          composite: ["buildingId", "storeId"],
        },
      },
      units: {
        index: "gsi1pk-gsi1sk-index",
        pk: {
          field: "gsi1pk",
          composite: ["mallId"],
        },
        sk: {
          field: "gsi1sk",
          composite: ["buildingId", "unitId"],
        },
      },
      leases: {
        index: "gsi2pk-gsi2sk-index",
        pk: {
          field: "gsi2pk",
          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.units({ mallId }).go();

All Stores in a particular mall building

Fulfilling Requirement #1.

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

let stores = await StoreLocations.query.units({ 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
  .units({ mallId, buildingId, unitId })
  .go();

Stores by Category at Mall

Fulfilling Requirement #2.

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

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

Stores by upcoming lease

Fulfilling Requirement #3.

let storeId = "LatteLarrys";
let q2StartDate = "2020-04-01";

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

Stores will renewals for Q4

Fulfilling Requirement #3.

let storeId = "LatteLarrys";
let q4StartDate = "2020-10-01";
let q4EndDate = "2020-12-31";

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

Spite-stores with release renewals this year

Fulfilling Requirement #3.

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

let stores = await StoreLocations.query
  .leases({ storeId })
  .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
  .units({ mallId, buildingId, storeId })
  .go();