oeCloud.io Example Soft Delete

is a app which demonstrate softDelete feature of oeCloud. soft delete is a way where record is not permantnly deleted from database but rather it changes the status.

What you’ll build

A oeCloud based app to demonstrate Soft Delete feature, in order to do that we will create a Product model and perform Delete operation, and learn how soft delete works behind the scene.

Scenario which will be coverd are :

  • Delete a record.

What you’ll need:

  • You should have Node and NPM installed.
  • You should have example application based on oeCloud.io running.

How to complete this guide

Install and download example application from here.

Note: We are using swagger explorer, for exploring model api’s, alternatively user can choose to use any other explorer tool (Ex: Postman).

Once you have started the server, login to application and open a browser link: http://localhost:3000/explorer/.

You should create Product model if it is not available. Please refer to ‘how to create model guide’ here. You should able to see model as below.

Schema of Product Model should be similar below:

{
    "name": "Product",
    "base": "BaseEntity",
    "strict": false,
    "plural": "Products",
    "idInjection": true,
    "options": {
        "validateUpsert": true
    },
    "properties": {
        "code": {
            "type": "string"
        },
        "name": {
            "type": "string"
        },
        "category": {
            "type": "string"
        },
        "price": {
            "type": "number"
        },
        "offeredSince": {
            "type": "date"
        },
        "active": {
            "type": "boolean"
        },
        "description": {
            "type": "string"
        }
    }
}

Create a record

Using swagger explorer, Post following two records in Product model[POST /api/Products ]

   [
        {
            "code": "HC1001",
            "name": "Caffe Latte",
            "category": "",
            "price": 1.23,
            "offeredSince": "2011-12-31T18:30:00.000Z",
            "description": "Strong Blend Espresso filled up with...",
            "id": "one",
            "_isDeleted": false,
            "flavourable": true,
            "flavours": []
        },
        {
            "code": "HC1002",
            "name": "Cappuccino",
            "category": "",
            "price": 1.23,
            "offeredSince": "2011-12-31T18:30:00.000Z",
            "description": "Strong Blend Espresso filled up with...",
            "id": "two",
            "_isDeleted": false,
            "flavourable": true,
            "flavours": []
        }
    ]

You should see output as shown below

[
  {
    "code": "HC1001",
    "name": "Caffe Latte",
    "category": "",
    "price": 1.23,
    "offeredSince": "2011-12-31T18:30:00.000Z",
    "flavourable": true,
    "flavours": [],
    "description": "Strong Blend Espresso filled up with...",
    "id": "one",
    "_type": "Product",
    "_createdBy": "admin",
    "_modifiedBy": "admin",
    "_createdOn": "2017-05-24T05:42:34.842Z",
    "_modifiedOn": "2017-05-24T05:42:34.842Z",
    "_version": "f9bbdf37-7e0f-4ac9-8962-ba85add2ecb8",
    "_isDeleted": false
  },
  {
    "code": "HC1002",
    "name": "Cappuccino",
    "category": "",
    "price": 1.23,
    "offeredSince": "2011-12-31T18:30:00.000Z",
    "flavourable": true,
    "flavours": [],
    "description": "Strong Blend Espresso filled up with...",
    "id": "two",
    "_type": "Product",
    "_createdBy": "admin",
    "_modifiedBy": "admin",
    "_createdOn": "2017-05-24T05:42:34.845Z",
    "_modifiedOn": "2017-05-24T05:42:34.845Z",
    "_version": "c1f907ea-30ab-49ac-b10e-9c1e0c44f50c",
    "_isDeleted": false
  }
]

Find a record

You can see records what you posted. To find records, use api - GET /Products

Output:

[
  {
    "code": "HC1001",
    "name": "Caffe Latte",
    "category": "",
    "price": 1.23,
    "offeredSince": "2011-12-31T18:30:00.000Z",
    "flavourable": true,
    "flavours": [],
    "description": "Strong Blend Espresso filled up with...",
    "id": "one",
    "_type": "Product",
    "_createdBy": "admin",
    "_modifiedBy": "admin",
    "_createdOn": "2017-05-24T05:42:34.842Z",
    "_modifiedOn": "2017-05-24T05:42:34.842Z",
    "_version": "f9bbdf37-7e0f-4ac9-8962-ba85add2ecb8",
    "_isDeleted": false
  },
  {
    "code": "HC1002",
    "name": "Cappuccino",
    "category": "",
    "price": 1.23,
    "offeredSince": "2011-12-31T18:30:00.000Z",
    "flavourable": true,
    "flavours": [],
    "description": "Strong Blend Espresso filled up with...",
    "id": "two",
    "_type": "Product",
    "_createdBy": "admin",
    "_modifiedBy": "admin",
    "_createdOn": "2017-05-24T05:42:34.845Z",
    "_modifiedOn": "2017-05-24T05:42:34.845Z",
    "_version": "c1f907ea-30ab-49ac-b10e-9c1e0c44f50c",
    "_isDeleted": false
  }
]

Note: Each record has an extra field named _isDeleted, which is by default set to false.

Delete a record.

To Delete a record, use api - Delete /Products/{id}/{version}

Note: Set id as “one” and version to f9bbdf37-7e0f-4ac9-8962-ba85add2ecb8

Output:

{
    "count" : 1
}

Note: The record is not actually deleted but _isDeleted field is set to true. An access hook is added to filter out deleted records (where isDeleted : true).

Do findById, use api - GET /Products/{id}

Note: Set id as one.

Output:

{
    "error": {
        "name": "Error",
        "status": 404,
        "message": "Unknown \"Product\" id \"one\".",
        "statusCode": 404,
        "code": "MODEL_NOT_FOUND",
        "stack": "Error: Unknown \"Product\" id \"one\".\n..."
    }
}

Observation: As we have access filter, so even though the record is still in database( with _isDeleted true) it will not be fetched.

Note: If user is using any other database then in memory, they can directly query database, to see the deleted record would be present with _isDeleted true.

Observation made:

Sr.No Scenario Observation
1 Delete a record When a record is deleted, _isDeleted field is set to true. And with use of access hooks deleted records are filtered out, so it appears the same as permanent delete.

Internals

  • Soft delete functionality is implemented as mixin. You can disable (by default it is enabled) this functionality by setting mixin value to false in your model definition.
 "mixins": {
        "SoftDeleteMixin": false
    }

Summary

  • We saw that Delete functionality doesn’t really hard delete record from database. It just changes to _isDeleted field value to true
  • All find operations ensure that _isDeleted = false filter applied so that deleted records doesn’t come up
  • Version is mandatory so that you must first select the record before you delete the record.