Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have an unified way of sending notifications #10

Open
burntcarrot opened this issue Feb 27, 2024 · 5 comments
Open

Have an unified way of sending notifications #10

burntcarrot opened this issue Feb 27, 2024 · 5 comments

Comments

@burntcarrot
Copy link
Member

Currently notifiex has multiple functions to send notifications; however, we could improve it by refactoring to a single function which accepts things like a list of services to send, type (async/sync), etc. Expected usage would be similar to this:

defmodule NotifiexExample do
  def example do
    payload = %{text: "🚀", files: ["/home/notifiex-example/README.md"]}

    options = %{
      slack: %{
        channel: "project",
        channel_ids: "general",
        token: "xoxb-token"
      },
      discord: %{
        webhook:
          "https://discord.com/api/webhooks/webhook_id/webhook_token"
      }
    }

    # Send notifications synchronously
    sync_result = Notifiex.send([:slack, :discord], payload, options, :sync)
    IO.inspect(sync_result)

    # Send notifications asynchronously
    async_result = Notifiex.send([:slack, :discord], payload, options, :async)
    IO.inspect(async_result)
  end
end

Things to note:

  • How to handle cases where options are invalid for a certain service?
    • we have two options:
      • silently ignore errors and print them out
        • advantages: doesn't "crash" the app; silent
        • disadvantages: print statements can interfere with users' code; no way to handle errors as they get printed out
      • return a list of errors along with the result (this is preferred)
        • advantages: we still don't "crash", but we can capture errors and return safely
        • disadvantages: users would need to iterate over returned errors and handle on their own (if required); they could also choose to ignore errors which is also desired in some cases
  • Do we need to perform additional validation on top of payloads?
    • not required in my opinion; as a library we shouldn't restrict functionality. Users should be able to send duplicate messages if they need to do so.
    • any other validator would also introduce friction; aim is to provide a simple way to send notifications; hence validation is not desired at the moment.
  • We would need to deprecate send_multiple/1 and send_async_multiple/1 now or sometime later.
@burntcarrot
Copy link
Member Author

There has been some progress made in #7, so we can pick up some pieces from there and move ahead with any future changes.

@andreascm
Copy link
Contributor

I think returning a list of errors along with the result would be tricky when it's :async. For :sync, we could return the list of errors in the same order as the input. But for :async it would be difficult to differentiate which ones succeeded and which ones failed, since they would finish in a different order than the input. Even more so if the user is sending multiple messages to the same service

One solution that I can think of is by returning the service and payload along with the error/success message. We can either return it in an array or pass it to the callback function that the user provided (this require a modification to Notifiex.send/4 to accept 1 more optional parameter)

@burntcarrot
Copy link
Member Author

@andreascm Oh yes, good catch. But I also believe that we could return the task itself, as we did previously. Reference.

Or, maybe we'll need to separate out async and sync into send and send_async; similar to the previous ones, if needed. The idea is to at least remove the redundant functions which explicitly handled multiple messages, as the current separation (of send and send_multiple) won't hold much value if we are able to pass multiple options and payloads through a function. We can consider merging sync and async into a single send function later if we find a way to return errors cleanly and reliably.

@andreascm
Copy link
Contributor

I'm still new to Elixir. If we return task, would it still allow user to access payload and success/error message? Otherwise, I think it won't be that helpful since user would have no context of the result

I think send and send_async would be a good idea. It makes it easier to tell the difference from the function name. send and send_multiple can be combined into the same function name but with different arity or parameter types

@burntcarrot
Copy link
Member Author

@andreascm Yes, just to be clear, we don't execute the task on our own, the task is created through the supervisor. The returned result looks like this:

{:ok, %Task{owner: #PID<0.250.0>, pid: #PID<0.251.0>, ref: #Reference<0.0.1.123>}}

Since the created task is returned, users can choose these options for executing the task:

  • use Task.start/1 to start the task and get the PID:
 task_pid = Task.start(task)
  • or use Task.await/1 to wait and get the result of the task's execution.
 task_result = Task.await(task)

Mostly people would use Task.await/1 and get the task results. For example, a mock service would return this:

{status, task} = Notifiex.send_async(:mock, %{hello: "world"}, %{})
task_result = Task.await(task)

# task_result is returned as this:
{:ok, true}

Here's a list of various things you can do with tasks. I do agree that the semantics here differ from other languages, and since this was my first Elixir project, I was hesitant with this approach too; but I've seen this pattern being followed in other Elixir projects too. If you have a better alternative, I'd love to hear that!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants