defmodule WorkloadService.Aggregates.Task do @moduledoc """ Behaviour and __using__ macro for task aggregates. Each task type can override validation and add specific behavior. Usage: defmodule WorkloadService.Aggregates.QuoteTask do use WorkloadService.Aggregates.Task, task_type: "quote" end """ @callback validate_submission(map()) :: :ok | {:error, term()} defmacro __using__(opts) do task_type = Keyword.fetch!(opts, :task_type) commands_module = Keyword.get(opts, :commands, WorkloadService.Commands.Task) quote do @behaviour Commanded.Aggregates.Aggregate @task_type unquote(task_type) alias unquote(commands_module).CreateTask alias unquote(commands_module).SubmitResponse alias unquote(commands_module).ApproveSubmission alias unquote(commands_module).CompleteTask alias Commanded.Aggregates.Aggregate defstruct [ :id, :application_id, :task_info, :submission, :attachments, :status ] @impl Aggregate def execute(%__MODULE__{status: nil}, %CreateTask{} = cmd) do %WorkloadService.Events.TaskCreated{ id: cmd.id, application_id: cmd.application_id, task_info: cmd.task_info, attachments: cmd.attachments || [] } end @impl Aggregate def execute(%__MODULE__{}, %CreateTask{}) do {:error, :task_already_exists} end @impl Aggregate def execute(%__MODULE__{status: status}, %SubmitResponse{} = cmd) when status in [nil, "created", "draft", "approved"] do with :ok <- validate_submission(cmd.submission) do new_status = if status == "approved", do: "draft", else: "draft" %WorkloadService.Events.SubmissionUpdated{ id: cmd.id, submission: cmd.submission, attachments: cmd.attachments } end end @impl Aggregate def execute(%__MODULE__{id: id, status: "draft"}, %ApproveSubmission{}) do %WorkloadService.Events.SubmissionApproved{ id: id } end @impl Aggregate def execute(%__MODULE__{status: status}, %ApproveSubmission{}) do {:error, {:invalid_state, "cannot approve in state: #{status}"}} end @impl Aggregate def execute(%__MODULE__{status: "approved"}, %CompleteTask{} = cmd) do %WorkloadService.Events.TaskCompleted{ id: cmd.id, completed_by: cmd.completed_by } end @impl Aggregate def execute(%__MODULE__{status: status}, %CompleteTask{}) do {:error, {:invalid_state, "cannot complete in state: #{status}"}} end @impl Aggregate def apply(%__MODULE__{} = agg, %WorkloadService.Events.TaskCreated{} = e) do %{ agg | id: e.id, application_id: e.application_id, task_info: e.task_info, attachments: e.attachments, status: "created" } end @impl Aggregate def apply(%__MODULE__{} = agg, %WorkloadService.Events.SubmissionUpdated{} = e) do %{ agg | submission: e.submission, attachments: e.attachments || [], status: "draft" } end @impl Aggregate def apply(%__MODULE__{} = agg, %WorkloadService.Events.SubmissionApproved{}) do %{ agg | status: "approved" } end @impl Aggregate def apply(%__MODULE__{} = agg, %WorkloadService.Events.TaskCompleted{}) do %{ agg | status: "completed" } end defoverridable execute: 2, apply: 2 end end end