一、为什么需要关注项目结构
当你开始写一个小型Elixir应用时,随便放几个模块可能不会有什么问题。但随着功能增加,代码量膨胀,你会发现:
- 模块命名冲突:比如两个
User模块,一个处理业务逻辑,一个处理数据库,但Elixir不允许同名模块。 - 代码难找:功能分散在不同文件,新成员接手时一脸懵。
- 编译变慢:错误的依赖关系会导致不必要的重新编译。
这时候,合理的项目结构设计就像整理房间——东西放对地方,找起来快,用起来顺。
二、Elixir的模块命名规则
Elixir的模块名对应文件路径。比如MyApp.User应该放在lib/my_app/user.ex。这种设计强制你思考模块的归属。
示例1:基础模块定义
# 技术栈:Elixir
# 文件:lib/my_app/user.ex
defmodule MyApp.User do
# 用户相关的业务逻辑
def greet(name), do: "Hello, #{name}!"
end
如果直接在lib/user.ex定义defmodule User,虽然能运行,但项目大了会乱套。
三、常见项目结构模式
1. 按功能划分(推荐)
适合大多数应用,比如:
lib/
my_app/
accounts/ # 账户相关功能
user.ex # MyApp.Accounts.User
auth.ex # MyApp.Accounts.Auth
products/ # 商品管理
item.ex # MyApp.Products.Item
utils/ # 公共工具
validator.ex # MyApp.Utils.Validator
优点:功能内聚,修改一个模块不会影响其他区域。
示例2:功能模块交互
# 技术栈:Elixir
# 文件:lib/my_app/accounts/auth.ex
defmodule MyApp.Accounts.Auth do
alias MyApp.Accounts.User # 同功能域内引用
def login(email, password) do
User.verify(email, password) # 调用用户验证逻辑
end
end
2. 按技术分层(适合复杂场景)
比如经典的“领域驱动设计”(DDD):
lib/
my_app/
domain/ # 核心业务逻辑
infrastructure/ # 数据库、外部API调用
web/ # Web控制器和路由
缺点:新手容易过度设计,小型项目用不上。
四、进阶技巧:使用alias和import
1. 善用alias缩短模块名
# 技术栈:Elixir
# 在模块顶部声明别名
alias MyApp.Accounts.{User, Auth}
defmodule MyApp.Billing do
def charge(user_id) do
user = User.get(user_id) # 代替 MyApp.Accounts.User.get
Auth.check_permission(user) # 代替 MyApp.Accounts.Auth.check_permission
end
end
2. 谨慎使用import
import会引入模块的所有函数,容易造成命名冲突。建议只在测试模块或特定工具模块中使用:
# 技术栈:Elixir
# 测试文件:test/support/test_helpers.ex
defmodule TestHelpers do
import MyApp.Utils.Validator # 引入所有验证函数
def validate_email(email), do: is_valid_email(email) # 直接使用Validator的函数
end
五、实际场景分析
场景:电商平台
假设我们有一个包含用户、商品、订单的电商应用:
lib/
eshop/
accounts/
user.ex # Eshop.Accounts.User
profile.ex # Eshop.Accounts.Profile
catalog/
product.ex # Eshop.Catalog.Product
review.ex # Eshop.Catalog.Review
sales/
order.ex # Eshop.Sales.Order
payment.ex # Eshop.Sales.Payment
关键设计点:
- 将
User放在accounts而非根目录,避免未来添加Admin.User时冲突。 review.ex属于商品目录而非用户,因为评价内容依赖商品信息。
六、注意事项
- 避免过深嵌套:如
MyApp.Context.Module.Submodule,超过3层会难以维护。 - 统一命名风格:全用单数(
User)或复数(Users),不要混用。 - 隔离第三方适配器:比如数据库调用封装在
MyApp.Repo,外部API放在MyApp.External.{服务名}。
七、总结
好的Elixir项目结构像一本分类清晰的字典:
- 功能优先:把关联性强的模块放在一起。
- 命名即路径:利用语言特性自然组织代码。
- 平衡简洁与扩展:既不过度设计,又为未来留余地。
刚开始可能需要多花点时间规划目录,但长期来看,这些投入会让团队协作和代码维护轻松十倍。
评论