Search code examples
sorm

Can I add hooks to database operations in SORM?


In many cases, functions need to be applied before and after database operations. An example is encryption. Data needs to be encrypted before INSERTs and UPDATEs. It needs to be decrypted after SELECTs. Would it be possible to add such hooks with SORM?


Solution

  • Well, theoretically you can hook into SORM by simply overriding it's methods, like so:

    case class Thing( normalField : String, encryptedField : String )
    
    object Db extends Instance (...) {
    
      override def save
        [ T <: AnyRef : TypeTag ]
        ( value : T )
        : T with Persisted
        = value match {
            case value : Thing => 
              super.save(value.copy(encryptedField = encrypt(value.encryptedField)))
            case _ => super.save(value)
          }
    
    }
    

    But SORM 0.3.* was not designed for customizations like that, and hooking into querying functionality will require quite much more effort and boilerplate. I'm quite unsure whether problems like that are at all of SORM's concern, because you have a rather confusing case.

    Anyway, you have other ways to solve your issue on the application side. Here's a couple straight outta head:

    1. The classical DAO approach:

    object Dao {
      def saveA( a : Thing ) = 
        Db.save(a.copy(encryptedField = encrypt(a.encryptedField)))
      def fetchAByNormalField( a : String ) = 
        Db.query[Thing].whereEqual("normalField", a).fetch()
          .map(a => a.copy(encryptedField = decrypt(a.encryptedField)))
    }
    

    The con here is that SORM's API is so simple that creating a DAO over it primarily introduces only a redundant abstraction and boilerplate.

    2. The converters approach:

    case class Thing( normalField : String, decryptedField : String ){
      def encrypted = EncryptedThing( normalField, encrypt(decryptedField) )
    }
    case class EncryptedThing( normalField : String, encryptedField : String ){
      def decrypted = Thing( normalField, decrypt(encryptedField) )
    }
    

    Note that you should register the EncyptedThing, not the Thing, with SORM:

    object Db extends Instance( entities = Set(Entity[EncyptedThing]() ) )
    

    You can use it like so:

    val thing = Thing(...)
    Db.save(thing.encrypted)
    
    val thing = Db.query[EncryptedThing].fetch().map(_.decrypted)
    

    SORM will bark at you if you accidentally forget to trigger the conversion, for trying to save a value of a type not registered with it. It should be noted though that the barking will be at runtime.