返回文章列表

文章

Elixir in Action - 6

请牢记,进程是轻量级的,因此可以大量创建。基于消息传递的并发机制,即使在高并发系统中也能保持清晰的逻辑推演。

构建一个并发系统#

在本章中,你将看到一个由多进程协同工作实现的复杂系统示例,这些进程相互配合以提供完整服务。你的终极目标是构建一个分布式HTTP服务器,能够同时处理多端用户对多个待办清单(to-do lists)的操作。这一目标将通过后续章节逐步实现。而在本章,你将重点开发以下基础架构:

  1. 多待办清单管理
  2. 数据持久化存储(磁盘) 不过首先,我们需要学习如何使用 Mix工具 来管理更复杂的项目。

1、Working with the Mix project#

使用mix 为我们的to-do list创建一个project:

mix new todo

然后mix会给我们生成默认的project目录结构及相关文件 关于mix的详细用法可以参考mix官方文档 在此不做赘述。 关于文件的命名和组织方式没有硬性规定,但是有一些首选约定:

  • 您应该将module放在一个通用的顶级别名下。例如,module可能称为 Todo.List , Todo.Server 或类似名称。这减少了在将多个项目合并到单个系统时发生module名称冲突的可能性。
  • 通常,一个文件应包含一个module。有时,如果一个辅助模块很小并且只在内部使用,它可以与使用它的module放在同一个文件中。如果要为module实现协议,也可以在同一文件中执行此作。
  • 文件名应该是它实现的主模块名称的下划线大小写(又名snake-case)版本。例如,TodoServer 模块将驻留在 lib 文件夹中的 todo_server.ex 文件中。
  • 文件夹结构应对应于多部分模块名称。名为 Todo.Server 的模块应位于 lib/todo/server.ex 文件中。 这些不是严格的规则,但它们是 Elixir 项目以及许多第三方库使用的规则

现在,您需要将这些模块的代码添加到新生成的 todo 项目中。以下是您需要做的:

  1. Remove the file todo/lib/todo.ex.
  2. Remove the file todo/test/todo_test.exs.
  3. Place the TodoList code in the todo/lib/todo/list.ex file. Rename the module as Todo.List.
  4. Place the TodoServer code in the todo/lib/todo/server.ex file. Rename the module to Todo.Server.
  5. Replace all references to TodoServer with Todo.Server and all references to TodoList with Todo.List. 其中主要的是todo/lib/todo/list.ex和todo/lib/todo/server.ex的代码:代码如下:
defmodule Todo.List do
  defstruct next_id: 1, entries: %{}

  def new(entries \\ []) do
    Enum.reduce(
      entries,
      %Todo.List{},
      &add_entry(&2, &1)
    )
  end

  def size(todo_list) do
    map_size(todo_list.entries)
  end

  def add_entry(todo_list, entry) do
    entry = Map.put(entry, :id, todo_list.next_id)
    new_entries = Map.put(todo_list.entries, todo_list.next_id, entry)

    %Todo.List{todo_list | entries: new_entries, next_id: todo_list.next_id + 1}
  end

  def entries(todo_list, date) do
    todo_list.entries
    |> Map.values()
    |> Enum.filter(fn entry -> entry.date == date end)
  end

  def update_entry(todo_list, entry_id, updater_fun) do
    case Map.fetch(todo_list.entries, entry_id) do
      :error ->
        todo_list

      {:ok, old_entry} ->
        new_entry = updater_fun.(old_entry)
        new_entries = Map.put(todo_list.entries, new_entry.id, new_entry)
        %Todo.List{todo_list | entries: new_entries}
    end
  end

  def delete_entry(todo_list, entry_id) do
    %Todo.List{todo_list | entries: Map.delete(todo_list.entries, entry_id)}
  end
end