Failure Handling With Listeners.

This is more a design/architecture question. Just need more input. So, a few of the commands I am writing for my bot are getting… complicated. By that I mean, database state is being updated, which leads to server categories being updated, and channels being updated, etc…So, instead of putting all the logic in my command class, I decided to go the event emitter route and this.container.client.emit() route and move the server channel/category/role updating to a listener that just responds to the custom event I emitted. Something that crosses my mind though is how to handle if one of the listeners fail for whatever reason. Does Sapphire provide an option to automatically retry listeners if they fail? Or am I gonna need to manually handle that somehow?
Solution:
There's retry package by sapphire which can ease your retry attempts. But you have to manually handle the errors & retry attempts...
Jump to solution
10 Replies
Solution
MRDGH2821
MRDGH282110mo ago
There's retry package by sapphire which can ease your retry attempts. But you have to manually handle the errors & retry attempts
MRDGH2821
MRDGH282110mo ago
But there's sapphire client event (I think the name was listener error 🤔) Which you can use 🤔
Favna
Favna10mo ago
Also it's far easier to instead of moving it to listeners move it to wholly independent files (I.e. src/lib or src/utils) and await functions in your command.
Je Suis Un Ami
Je Suis Un AmiOP10mo ago
I do that already. The challenge is there are multiple things that can go wrong, and need to be undone/rolled back if they fail. And, adding that rollback logic just makes everything more convoluted. For example, a command runs and some data is persisted to my database. In success, a new channel is created in some category. And after that, the bot sends a message to that created channel, then as a side effect, the bot sends a message to the logging channel. If anything fails in the subsequent steps after the data is saved to the DB, rolling back will just be way harder. I.e. the created channel would need to be deleted (which could also fail), and as a result of that, the database record that was created needs to be deleted because it saved the channel ID which now doesn’t exist. And so forth. It’s much easier to read code when all it does is save stuff to the DB and then fire an event and let someone else handle the rest.
Favna
Favna10mo ago
Well then what MRDGH said I guess
Je Suis Un Ami
Je Suis Un AmiOP10mo ago
Ooh. I found the Event. What’s the retry package you are referring to? Is it a third-party? I’m looking all over GitHub for it. lol
Favna
Favna10mo ago
It's in sapphire utilities
Je Suis Un Ami
Je Suis Un AmiOP10mo ago
Ah. Found it. Thank you. 🙂
Favna
Favna10mo ago
Alternatively you could write a transactional function which also describes how to rollback each step Something like
type Transactional = <T, U, V>(
param1: (() => T | Promise<T>),
...restParams: ((...args: any[]) => U | Promise<U>)[]
) => Promise<V>;
type Transactional = <T, U, V>(
param1: (() => T | Promise<T>),
...restParams: ((...args: any[]) => U | Promise<U>)[]
) => Promise<V>;
But that's probably quite hard to manage. The idea is that the first parameter is the executor and the second (variadic if you need multiple steps) rolls back. For example you could have
transactional(doStuff, () => rollbackDB(interaction), () => rollbackCreation(interaction))
transactional(doStuff, () => rollbackDB(interaction), () => rollbackCreation(interaction))
this could even be a method annotation for a class method with some extra work The syntax then would roughly be
@Transactional(resetDb, resetCreation)
public override async chatInputRun(interaction: Command.ChatInputInteraction) {

}
@Transactional(resetDb, resetCreation)
public override async chatInputRun(interaction: Command.ChatInputInteraction) {

}
All depends on how advanced you want to get
Je Suis Un Ami
Je Suis Un AmiOP10mo ago
Yeah. Thats an option. I’m not a fan of adding even more complexity though. For now, I’ll just use the retry() option and just have it fail somewhat gracefully if hell breaks loose. 🙂
Want results from more Discord servers?
Add your server