Show Navigation

Introducing the Grails® 3 GORM Logical Delete Plugin

By Nirav Assar

April 5, 2018

Tags: #gorm #plugins

Recently our development team came upon the need to develop a Grails® 3 plugin that implements logical delete for entities.

A "logical" delete (sometimes referred to as a "soft" delete) is a delete that doesn’t actually delete the relevant data but instead marks the data as deleted. Marking the data as deleted has the benefit of excluding the data from queries by default, while still maintaining the ability to retrieve the data when/if necessary. Essentially the plugin offers a mechanism to "hide" domain objects from retrieval. This is useful for retaining data without having it clutter the current set of active data.

Logical Delete functionality is available in a few Grails 2 plugins. However these implementations rely on filters, ASTs, and runtime metaClass enhancements. We decided to create another implementation of the logical delete using some of latest Grails 3 features such as Traits and Listeners.

This blog will highlight how to use the plugin and also give some insight into the techniques used under the covers.


Summary

GORM Logical Delete Plugin

Logical delete of an entity has a few relevant use cases in enterprise applications. Companies may want to "delete" data from their everyday usage, but still keep it for later retrieval. Auditing requirements for financial institutions may require the data to be sustained for up to seven years.

From a technical perspective, if a domain model has several complex associations that make a chain of domain objects, hard deletion may cause slow cascading affects or be blocked by referential integrity. In these situations, a logical delete alleviates these concerns.

Installation

To add the GORM Logical Delete plugin to an application, add the following dependency to the dependencies block of your build.gradle:

compile "org.grails.plugins:gorm-logical-delete:2.0.0.M2"

Enable Domain Entity with Logical Delete

Any domain entity can be implemented with the LogicalDelete trait. The trait adds a boolean persistent property named deleted to the domain class. The property is used to "hide" entities from queries if it is set to true. Note the mapping columns can customize the property to a database column name.

import gorm.logical.delete.LogicalDelete

class Person implements LogicalDelete<Person> {
    String userName

    static mapping = {
        // the deleted property may be configured
        // like any other persistent property...
        deleted column:"delFlag"
    }
}

In order to delete a domain object enabled with logical delete, simply use the same GORM interface as usual.

Person p = new Person(userName: "Nirav").save()
p.delete()

If you would like to physically delete the record from persistence, use the attribute hard: true:

p.delete(hard: true)

Undelete functionality is quite handy if you want to reverse the property to false.

Person.withDeleted {
    Person p = Person.get(id)
    p?.unDelete()
}

Querying Objects

When an object is enabled with logical delete, queries associated with the domain object will hide those marked with deleted = true.

Dynamic Finders, Criteria Query, Detached Criteria Query, and the GormEntity methods like get, load, proxy, and read are all supported.

See the Query documentation for a list of examples.

Note Hibernate Criteria and HQL queries are NOT supported by this plugin as they are ORM implementation specific.

Behind The Scenes

It's beneficial to understand how the plugin is implemented under the covers. We have tried to use the most efficient techniques with Groovy and the Grails framework, which reduces some of the noise and clutter found in previous plugin implementations.

In concept, what is occurring is that any domain object can be given a logical delete capability with the attribute deleted. When the entity is logically deleted, it is set to true. It is not physically deleted from persistence. During query time, a query event is intercepted by a listener and the query is altered to only include deleted = false items from the result set. This gives the illusion that the items are not present, but in reality they are just hidden by the query.

LogicalDelete

The LogicalDelete trait makes available a deleted attribute. As stated earlier, an entity can implement this.

The LogicalDelete trait has overridden static methods which take into account the deleted property in an altered query. This allows the client to use the GormEntity methods to essentially hide those items.

PreQueryListener

The PreQueryListener implements ApplicationListener and when the event fires, it will add a deleted equals false to the query.

The ThreadLocal variable IGNORE_DELETED_FILTER should also be noted. When set to true, then all entities that exist in the database are included in the query. In other words, it bypasses the logical delete flag all together. In LogicalDelete, note how this ThreadLocal variable is used in the withDeleted method. The method is passed in a closure (which is a query) and the variable is set to true, which bypasses the delete flag.

WithDeletedTransformation essentially achieves the same goal, but is intended as an implementation for an annotation.

The spock tests contained in the plugin are a good resource to reference examples and usages of the plugin API.

Take a look and we hope the plugin is useful for your needs.

You might also like ...