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

Support time-based triggers for process managers #36

Closed
slashdotdash opened this issue Nov 25, 2016 · 7 comments
Closed

Support time-based triggers for process managers #36

slashdotdash opened this issue Nov 25, 2016 · 7 comments
Labels

Comments

@slashdotdash
Copy link
Member

slashdotdash commented Nov 25, 2016

To allow long running process managers to handle timeouts.

A process manager may request to be notified after a given period or at an exact date and time.

When the time arrives the process manager is notified and it may dispatch commands in response.

Requires consideration of how to persist these time-based triggers to survive application restarts and process crashes.

@Papipo
Copy link

Papipo commented Nov 25, 2016

It feels weird to have commands that are dispatched later.

It seems better to have some kind of timed process (which should be a PM probably, and we already talked about PMs and timers) which just sends the command later. Just a ExpireOrder command that changes the state of the Order, no? Besides, a PM can control the expiring process if the command is dropped for any reason, something you can't do if you just send a delayed command once.

@slashdotdash slashdotdash changed the title Support delayed command dispatch Support time-based triggers for process managers Nov 25, 2016
@slashdotdash
Copy link
Member Author

slashdotdash commented Dec 12, 2016

It's possible to implement time-based triggers using an external job scheduler and by dispatching commands to raise events to set the callback time period (date time, recurring interval) and command to dispatch.

A standard event hander would then configure the job scheduler at the requested period to dispatch the command. This requires no changes to Commanded itself. It could be implemented as a standalone library.

@drozzy
Copy link

drozzy commented Aug 22, 2017

Why? Is there anyone who needs this?

I agree with @Papipo that it should be done in PM. This seems to me like a business concern, and so it should not be done in a framework. Unless you need timers for things like periodic snapshotting etc. But that is not what you're talking about here, I think.

@slashdotdash
Copy link
Member Author

Closing, could be implemented as an external library.

@ayarulin
Copy link
Contributor

ayarulin commented Jan 19, 2018

Hey guys, could we please discuss this proposal once more. The PM triggers are different from scheduled commands. Say we have a PM which need to check order status every minute since order creation, by calling external http service. Currently it means that we'll need to post command CheckOrderStatus once in minute, it will be processed by aggregate and emit OrderStatusRequested event which then will be handled by PM. In domain terms, this is almost meaningless event, the only purpose of it - is run PM handler (and eventstore will contain tons of them). So why can't we just trigger PM directly? I could imagine something like this:

defmodule ProcessManager do
# ...
  def handle_trigger(%ProcessManager{}, %CheckOrderStatus{order_id: order_id}) do
    # note %CheckOrderStatus{} is a not domain event and not persist in event store
    case MyApi.get_order_status(order_id) do
      {:ok, :complete} -> %CompleteOrder{id: order_id)
       _ -> []
    end
  end
  # ...
  def handle(%ProjectManager{id: id}, %OrderOpened{}), do: MyScheduler.add_job("* * * * *",{TriggerProcessManager, [id: id]})
  def handle(%ProjectManager{id: id}, %OrderClosed{}), do: MyScheduler.remove_job(id)
  # ...
end

defmodule TriggerProcessManager do
# ...
  def perform(id: id) do
    ProcessManager.trigger(id, %CheckOrderStatus{order_id: i})
  end
# ...
end

does it makes any sense for you guys?

@slashdotdash
Copy link
Member Author

@ayarulin I'm currently building a library for Commanded to support one-off and recurring commands: Commanded scheduler.

This library allows you to directly schedule commands:

alias Commanded.Scheduler

# schedule one-off command
Scheduler.schedule_once(reservation_id, %TimeoutReservation{..}, ~N[2020-01-01 12:00:00])

# schedule a recurring command to execute every 15 minutes
Scheduler.schedule_recurring(reservation_id, %TimeoutReservation{..}, "*/15 * * * *")

It also supports scheduling commands from a process manager by returning a schedule once or schedule recurring command:

defmodule OrderStatusProcessManager do
  use Commanded.ProcessManagers.ProcessManager,
    name: "order-status",
    router: AppRouter

  def handle(%OrderStatusProcessManager{}, %OrderOpened{order_id: order_id}) do
    %Commanded.Scheduler.ScheduleRecurring
      schedule_uuid: order_id,
      command: %CheckOrderStatus{order_id: order_id},
      schedule: "* * * * *",
    }
  end

  def handle(%OrderStatusProcessManager{}, %OrderClosed{order_id: order_id}) do
    %CancelSchedule{order_id: order_id}
  end
end

Then you'd do the order checking in the command handler:

def OrderHandler do
  def handle(%Order{} = order, %CheckOrderStatus{order_id: order_id}) do
    case MyApi.get_order_status(order_id) do
      {:ok, :complete} -> Order.mark_complete(order)
       _ -> []
    end
  end
end

The benefit of this approach is that process managers are purely functional. They return commands in response to events, thus are easily testable. The details of fetching the external order status can be done in the command handler. The response can be passed into the aggregate to create the appropriate domain event(s).

@ayarulin
Copy link
Contributor

@slashdotdash, +1 to Commanded Scheduler
and thanks for clarifications, i've totally missed that services can be called from command handlers as well.

The benefit of this approach is that process managers are purely functional

good point

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

No branches or pull requests

4 participants