redis-om-node icon indicating copy to clipboard operation
redis-om-node copied to clipboard

Transactions & relations

Open gothy opened this issue 2 years ago • 2 comments

First of all, let me thank you for this cool library :) Now, I'm really considering using Redis Enterprise as a primary DB in the future projects!

So, my question arises from this section in the readme https://github.com/redis/redis-om-node#-embedding-your-own-logic-into-entities We can fetch "relations" in our entities, but I'm more interested in updating those relations as a part of a transaction. I can see that currently, saving an entity requires you to call repository.save(entity), but what about related entries? What if I need to update an entity, some sub-entity and commit or discard all of the changes?

It would be cool to have something like

getCurrentClient().startTransaction(() => {
  repository1.save(entity);
  repository2.save(subentity);
}).then(...).catch(...)

gothy avatar Jan 12 '22 21:01 gothy

This could be achieved by exposing/using .multi() from the @node-redis Client used in RedisShim. Then, instead of saving the data using the .save() method from a Repository one could prepare the data like it is prepared in the .save(), skip the saving, and instead appending it to the exposed .multi().

I threw together a proof of concept

Preparing the data for transaction in the repository could look like:

prepareForTransaction(entity: Entity) {
  const key = this.makeKey(entity.entityId);
  const {dataStructure} = this.schema;

  const data = dataStructure === 'JSON'
    ? this.jsonConverter.toJsonData(entity.entityData)
    : this.hashConverter.toHashData(entity.entityData);

  return {key, data, dataStructure};
}

This data could then be used in some kind of Transaction class like:

export default class Transaction {
  private multi;

  constructor(client: Client) {
    this.multi = client.multi();
  }

  add(repository: Repository<Entity>, entity: Entity) {
    const { key, data, dataStructure } = repository.prepareForTransaction(entity);
    dataStructure === 'JSON' ? this.multi.jsonset(key, data) : this.multi.hSet(key, data);
    return this;
  }

  exec() {
    return this.multi.exec();
  }
}

And finally, a transaction could be created and executed with as many steps as needed:

new Transaction(client)
  .add(fooRepo, fooEntity)
  .add(barRepo, barEntity)
  .exec();

It's probably better to execute this inside of an isolated Client though, like RedisShim is doing for .hsetall().

HeyItsBATMAN avatar Jan 13 '22 00:01 HeyItsBATMAN

Regarding transactions, I think this is an interesting idea. I'll consider it in the future.

Regarding nesting objects and relationships, this has been asked a couple of different times by a couple of different people with different flavors. It's something that's on the roadmap but not in the backlog. It'll happen. Not sure exactly what it will look like yet.

guyroyse avatar Jan 25 '22 18:01 guyroyse