machinery:轻量级 Elixir 状态机库,内置 Phoenix 集成

Elixir State machine thin layer for structs

分支1Tags11
文件最后提交记录最后更新时间
3 年前
5 年前
3 年前
3 年前
3 年前
7 年前
8 年前
5 年前
5 年前
3 年前
8 年前
5 年前
5 年前
3 年前
8 年前
3 年前
5 年前
8 年前
3 年前

机械装置(Machinery)

构建状态模块版本Hex 文档总下载量许可证

Machinery 是一个轻量级的 Elixir 状态机库,内置了 Phoenix 集成功能。它提供了一个简单的声明状态的 DSL,并支持条件语句和回调。

目录

安装

mix.exs 的依赖列表中添加 :machinery

def deps do
  [
    {:machinery, "~> 1.1.0"}
  ]
end

在你想应用状态机的模块上创建一个名为 state(或其他自定义名称)的字段,并确保它被包含在你的 defstruct 中。

如果使用 Phoenix 模型,将其作为 string 添加到模式中并在 changeset/2 函数中包括它:

defmodule YourProject.User do
  schema "users" do
    # ...
    field :state, :string
    # ...
  end

  def changeset(%User{} = user, attrs) do
    #...
    |> cast(attrs, [:state])
    #...
  end
end

声明状态

为您的状态机逻辑创建一个独立的模块。例如,如果你想向 User 模型添加状态机,创建一个 UserStateMachine 模块。

然后在这个新模块中导入 Machinery 并以参数形式声明状态。

Machinery 需要一个关键字参数作为输入,键分别为 fieldstatestransitions

  • field: 表示状态字段名的原子(默认值是 state
  • states: 代表每个状态的字符串列表。
  • transitions: 对每个状态及其允许的下一个状态(或状态集)的映射。

示例

defmodule YourProject.UserStateMachine do
  use Machinery,
    field: :custom_state_name, # 可选,默认值是 `:field`
    states: ["created", "partial", "completed", "canceled"],
    transitions: %{
      "created" =>  ["partial", "completed"],
      "partial" => "completed",
      "*" => "canceled"
    }
end

你可以使用通配符 "*" 来声明从任意状态到特定状态的转换。

改变状态

通过调用 Machinery.transition_to/3Machinery.transition_to/4 将结构体转换到另一个状态。

Machinery.transition_to/3Machinery.transition_to/4

接受以下参数:

  • struct: 要转换状态的结构体。
  • state_machine_module: 包含状态机逻辑的模块,即导入 Machinery 的模块。
  • next_event: 字符串,表示结构体要转换到的下一个状态。
  • (可选) extra_metadata: 一个映射,可以包含你在由状态变化触发的任何状态机函数中可能需要访问的额外数据。
Machinery.transition_to(your_struct, YourStateMachine, "next_state")
# {:ok, updated_struct}

# 或者

Machinery.transition_to(your_struct, YourStateMachine, "next_state", %{extra: "metadata"})
# {:ok, updated_struct}

示例

user = Accounts.get_user!(1)
{:ok, updated_user} = Machinery.transition_to(user, UserStateMachine, "completed")

持久化状态

要在状态转换后持久化结构体,你需要在状态机模块中声明一个 persist/2/3 (如果你想访问传递给 transition_to/4 的元数据)函数。

此函数将接收未修改的 struct 作为第一个参数,以及一个表示下一个状态的 string 作为第二个参数。

你的 persist/2persist/3 应该始终返回更新后的结构体。

示例

defmodule YourProject.UserStateMachine do
  alias YourProject.Accounts

  use Machinery,
    states: ["created", "completed"],
    transitions: %{"created" => "completed"}
  
  # 你可以添加一个可选的第三个参数来访问传递给 `transition_to/4` 的元数据。
  def persist(struct, next_state) do
    # 使用新的状态在数据库中更新用户。
    {:ok, user} = Accounts.update_user(struct, %{state: next_stated})
    # `persist` 总应返回更新后的结构体
    user
  end
end

记录转换

为了记录转换,Machinery 提供了一个 log_transition/2/3 (如果你想访问传递给 transition_to/4 的元数据)回调,该回调会在执行 persist 函数后对每次转换调用。

这个函数接收未修改的 struct 作为第一个参数,一个表示下一个状态的 string 作为第二个参数。

log_transition/2log_transition/3 应该始终返回结构体。

示例

defmodule YourProject.UserStateMachine do
  alias YourProject.Accounts

  use Machinery,
    states: ["created", "completed"],
    transitions: %{"created" => "completed"}

  # 你可以添加一个可选的第三个参数来访问传递给 `transition_to/4` 的元数据。
  def log_transition(struct, _next_state) do
    # 在这里记录转换。
    # ...
    # `log_transition` 总应返回结构体
    struct
  end
end

守护函数

通过在状态机模块中添加 guard_transition/2/3 (如果你想访问传递给 transition_to/4 的元数据)函数签名来创建守护条件。 这个函数会接收两个参数:struct 和一个它将转换到的状态的 string

使用第二个参数对想要守护的特定状态进行模式匹配。

# 第二个参数用于模式匹配并保护对该状态的转换。
#
# 你可以添加一个可选的第三个参数来访问传递给 `transition_to/4` 的元数据。
def guard_transition(struct, "guarded_state") do
 # 你的守护逻辑在这里
end

守护条件只有在返回的不是 {:error, "cause"} 元组时才会允许转换:

  • {:error, "cause"}: 不允许转换。
    • _(其他任何东西): 守护条件将允许转换。

示例

defmodule YourProject.UserStateMachine do
  use Machinery,
    states: ["created", "completed"],
    transitions: %{"created" => "completed"}

  # 守护向 "completed" 状态的转换。
  def guard_transition(struct, "completed") do
    if Map.get(struct, :missing_fields) == true do
      {:error, "There are missing fields"}
    end
  end
end

当试图过渡被其守护条件阻止的结构体时,你会得到以下返回结果:

blocked_struct = %TestStruct{state: "created", missing_fields: true}
Machinery.transition_to(blocked_struct, TestStateMachineWithGuard, "completed")

# {:error, "There are missing fields"}

前置与后置回调函数

您还可以使用前置和后置回调来处理特定状态转换时的预期副作用和响应。

您可以声明 before_transition/2/3(如果需要访问在 transition_to/4 中传递的元数据), 以及 after_transition/2/3(如果需要访问在 transition_to/4 中传递的元数据),根据所需匹配的状态模式。

前置和后置回调函数应返回结构体。

# 前置和后置回调函数应该返回结构体。
# 您可以添加一个可选的第三个参数以接收额外的元数据。
def before_transition(struct, "state"), do: struct
def after_transition(struct, "state"), do: struct

示例

defmodule YourProject.UserStateMachine do
  use Machinery,
    states: ["created", "partial", "completed"],
    transitions: %{
      "created" =>  ["partial", "completed"],
      "partial" => "completed"
    }

    def before_transition(struct, "partial") do
      # ... 执行所需的全局副作用操作
      struct
    end

    def after_transition(struct, "completed") do
      # ... 执行所需的全局副作用操作
      struct
    end
end

版权与许可

版权所有 © 2016 João M. D. Moura

本软件遵循 Apache 许可证 2.0 版("许可证")授权; 除非遵守该许可证,否则您不得使用此文件。 您可以在以下链接获取许可证的副本:

http://www.apache.org/licenses/LICENSE-2.0

除非适用法律要求或书面同意,根据许可证分发的软件均按“原样”基础提供, 不附带任何形式的明示或暗示保证或条件。 请参照许可证了解特定的权限和限制规定。