Elixir Ecto: 事件流处理和回调

回调模块 Ecto.Model.Callbacks 已经在 2.0 中被废弃, 1.x 版本的 Ecto 能够继续使用.数据库

Ecto 提供了 before_insert, after_insert这样的回调函数来在数据库操做先后作一些事情, 咱们常常把这种函数称为钩子. 咱们这里有一个例子, 用户注册完成后须要向其邮箱发送一封激活邮件. 一般咱们在控制器(UserController)中实现一个create函数用于建立用户, 例如:设计模式

def create(conn, %{"user" => params}) do
  changeset = User.changeset(%User{}, params)
  case Repo.insert(changeset) do
    {:ok, user} ->
      send_activation_email(user)
    {:error, changeset} ->
      Logger.error "Can not register user"
  end
end

注意这段代码可能在整个系统中有不少重复的, 由于可能会在不少地方须要用到用户注册的代码, 好比Web入口, RESTFUL API接口, 等等. 所以, send_activation_email(user)必须在每一个注册用户的地方调用这种相同功能的代码函数

实现一个事件广播程序(事件管理器), 咱们在须要产生事件的地方能够调用EctoTest.EventManager.broadcast/1函数来发送一个事件给事件管理器EctoTest.EventManager. 它会把事件发送给各个已注册的处理器, 若是该事件是某个处理器须要处理的, 就进入事件处理程序, 若是不是, 事件处理程序会忽略该事件:设计

defmodule EctoTest.EventManager do
  @handlers [
    # 在这里插入事件处理器模块
  ]
  def start_link do
    {:ok, manager} = GenEvent.start_link(name: __MODULE__)
    Enum.each(@handlers, &GenEvent.add_handler(manager, &1, []))
    {:ok, manager}
  end
  def broadcast(event) do
    GenEvent.notify(__MODULE__, event)
  end
end

上述事件管理器实际上在软件工程当中实现了一个发布/订阅设计模式, EctoTest.EventManager是一个事件发布者, @handlers属性是一堆订阅者, 当有事件产生时, EctoTest.EventManager会把这个事件广播出去.code

注意, 须要把EctoTest.EventManager挂载到监控树下面接口

children = [
  worker(EctoTest.EventManager, [])
]

广播生命周期事件

一个模型的生命周期实际上就是一堆事件, 好比insert, update, 和delete, 下面咱们来建立一个这样的生命周期模块:生命周期

defmodule EctoTest.ModelLifecycle do
  # Ecto.Model.Callbacks 在 2.0 已经被废弃
  import Ecto.Model.Callbacks
  alias EctoTest.EventManager
  defmacro __using__(_) do
    quote do
      import unquote(__MODULE__)
      after_insert :broadcast_event, [:insert]
      after_update :broadcast_event, [:update]
      after_delete :broadcast_event, [:delete]
    end
  end
  # 以以下的格式广播事件
  # {:model, :update, changeset}
  def broadcast_event(changeset, type) do
    EventManager.broadcast({:model, type, changeset})
    changeset
  end
end

而后在模型当中使用这个生命周期模块:进程

defmodule EctoTest.Model.User do
  use Ecto.Model
  use EctoTest.ModelLifecyle
  # ...
end

如今,当模型在执行insert, updatedelete操做后, 会回自产生这些事件. 经过GenEvent API, 咱们在能够收到这些通知(事件)时作一些事情. 如今咱们来完成上述用户注册后的邮件发送功能, 建立一个EctoTest.EmailSender, 它实现了 GenEvent 行为:事件

defmodule EctoTest.EmailSender do
  use GenEvent
  alias EctoTest.Model.User
  def handle_event({:model, :insert, %{model: %User{}} = changeset}, state) do
    send_activation_email(changeset.model)
    {:ok, state}
  end
end

好处

  • 一个事件能够设置多个订阅者回调函数

  • 多个模型的事件处理逻辑能够放在一个位置

  • 生命周期事件在单独的进程中处理, 不会阻塞模型的执行流

缺点

  • 增长了复杂度

  • 有可能过分封装, 代码不直观

相关文章
相关标签/搜索