文章
Elixir in Action - 6
请牢记,进程是轻量级的,因此可以大量创建。基于消息传递的并发机制,即使在高并发系统中也能保持清晰的逻辑推演。
构建一个并发系统#
在本章中,你将看到一个由多进程协同工作实现的复杂系统示例,这些进程相互配合以提供完整服务。你的终极目标是构建一个分布式HTTP服务器,能够同时处理多端用户对多个待办清单(to-do lists)的操作。这一目标将通过后续章节逐步实现。而在本章,你将重点开发以下基础架构:
- 多待办清单管理
- 数据持久化存储(磁盘) 不过首先,我们需要学习如何使用 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 项目中。以下是您需要做的:
- Remove the file todo/lib/todo.ex.
- Remove the file todo/test/todo_test.exs.
- Place the TodoList code in the todo/lib/todo/list.ex file. Rename the module as Todo.List.
- Place the TodoServer code in the todo/lib/todo/server.ex file. Rename the module to Todo.Server.
- 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