top | item 41348826

(no title)

akrauss | 1 year ago

There is one thing I'd like to know about this general approach of centralising permission data. I guess my question applies to Permify as well as to its various competitors:

When objects and relations change in the applications database, then these changes will often have to be reflected in the permissions database as well, and the application must keep things in sync. But this is probably harder as it first seems, in the presence of errors and transaction rollbacks.

What about this case:

- User creates a FOO in the application.

- The app creates an entry in the "foo" table and assigns the user id as owner, and attempts to store various other data.

- The app also creates an entity in the permissions database and assigns the user id as an owner.

- Then further steps are performed, and one of them fails, rolling back the transaction in the application database.

I would assume that the change to the permissions database cannot be rolled back then, so there is now an inconsistency.

What do people typically do about these things?

discuss

order

EgeAytin|1 year ago

In a standard relational based databases, the suggested place to write relationships to Permify is sending the write request in database transaction of the client action: such as assigning the user as owner.

If the transaction fails, you should delete the malformed relation tuple from Permify to maintain consistency.

Here's an example of how this might look in code:

```

func CreateDocuments(db *gorm.DB) error {

  tx := db.Begin()
  defer func() {
    if r := recover(); r != nil {
      tx.Rollback()
      // if transaction fails, then delete malformed relation tuple
      permify.DeleteData(...)
    }
  }()

  if err := tx.Error; err != nil {
    return err
  }

  if err := tx.Create(docs).Error; err != nil {
     tx.Rollback()
     // if transaction fails, then delete malformed relation tuple
     permify.DeleteData(...)
     return err
  }

  // if transaction successful, write relation tuple to Permify
  permify.WriteData(...)

  return tx.Commit().Error
}

```

Although this is an anti-pattern, this approach ensures that if the transaction in the application database fails and is rolled back, the corresponding data in Permify is also deleted, preventing inconsistencies.

akrauss|1 year ago

You call this an anti-pattern (and rightfully so, since cluttering application code like this is a nightmare), but then what is the better pattern? In an app with >100 entities and >500 types of business transactions, many of which can fail in unexpected ways, will I be forced to maintain what was changed in Permify and roll back manually?

If yes, then this is quite a burden and might be a valid reason for not using a separate permissions store. But maybe there are better ways...?

rzzzt|1 year ago

One option would be to employ a two-phase commit mechanism that keeps track of all "sub-transactions" and considers a global transaction to be completed when all datastores report back that they are very, very certain that they can commit the changes on their end without any issues. Then it asks each local transaction to actually commit.

XA is such a standard that pops up often when data sources support such a mechanism:

- https://en.wikipedia.org/wiki/X/Open_XA

- https://dev.mysql.com/doc/refman/8.0/en/xa.html

- https://mariadb.com/kb/en/xa-transactions/

akrauss|1 year ago

I understand. But I'd rather not get into the complexities and new funny failure modes of such a system, so I was hoping for something simpler.