一、为什么需要关注项目结构

当你开始写一个小型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控制器和路由

缺点:新手容易过度设计,小型项目用不上。

四、进阶技巧:使用aliasimport

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属于商品目录而非用户,因为评价内容依赖商品信息。

六、注意事项

  1. 避免过深嵌套:如MyApp.Context.Module.Submodule,超过3层会难以维护。
  2. 统一命名风格:全用单数(User)或复数(Users),不要混用。
  3. 隔离第三方适配器:比如数据库调用封装在MyApp.Repo,外部API放在MyApp.External.{服务名}

七、总结

好的Elixir项目结构像一本分类清晰的字典:

  • 功能优先:把关联性强的模块放在一起。
  • 命名即路径:利用语言特性自然组织代码。
  • 平衡简洁与扩展:既不过度设计,又为未来留余地。

刚开始可能需要多花点时间规划目录,但长期来看,这些投入会让团队协作和代码维护轻松十倍。