boundary:实践指南

作者:袖梨 2026-09-11

面对实际交付,我看boundary的重点不在星标,而在这项能力:管理和约束 Elixir 项目中的跨模块依赖关系。在日常自动化场景里,常见问题是输入边界、依赖和失败处理如果不清楚就很难稳定复用,这正是评估时需要盯住的地方。我的评估方法是用一项范围明确的真实任务完成最小试跑,然后检查配置时间、输出质量、异常信息和维护痕迹是否与文档一致。我的判断是,它更适合愿意先做小范围验证并复查原始文档的团队;若眼下没有这类需求,先保留观察即可。

边界

Boundary 是一个帮助管理和限制 Elixir 项目中的跨模块依赖关系的库。您可以使用边界执行的操作的一些示例包括:

  • 防止从上下文层到Web层的调用
  • 防止从 Web 层调用内部上下文模块
  • 防止在上下文层中使用 Phoenix 和 Plug
  • 将 Ecto 在 Web 层的使用限制为仅 Ecto.Changeset
  • 允许仅在编译时使用 :mix 模块

状态

该库已在较小的生产项目中使用了大约一年,虽然尚未在较大的项目或伞式项目上进行过测试,但预计在这些情况下不会出现问题。话虽这么说,如果您确实遇到问题,请提出问题!

文档

有关详细参考,请参阅 边界模块 和 混合编译器 的文档。

基本用法

要使用这个库,您首先需要定义项目的边界。 boundary 是一个或多个模块的命名组。每个边界导出其部分(但不是全部!)模块,并且可以依赖于其他边界。在编译过程中,边界编译器会发现并报告所有根据边界配置不允许的跨模块函数调用。

示例

在 mix.exs 中添加边界作为依赖项:

defmodule MySystem.MixProject do
  use Mix.Project

  # ...

  defp deps do
    [
      {:boundary, "~> 0.10", runtime: false},
      # ...
    ]
  end

  # ...
end

以下代码定义了使用 mix phx.new 生成的典型基于 Phoenix 的项目的边界。

defmodule MySystem do
  use Boundary, deps: [], exports: []
  # ...
end

defmodule MySystemWeb do
  use Boundary, deps: [MySystem], exports: [Endpoint]
  # ...
end

defmodule MySystem.Application do
  use Boundary, top_level?: true, deps: [MySystem, MySystemWeb]
  # ...
end

上面的配置定义了三个边界:MySystemMySystemWebMySystem.Application

边界模块是根据边界名称自动确定的。例如,MySystem 边界包含 MySystem 模块,以及名称以 MySystem. 开头的任何模块(e.g、MySystem.UserMySystem.User.Schema、...)。

该配置指定了以下规则:

  • 驻留在 MySystemWeb 边界中的模块可以调用 MySystem 边界导出的模块中的函数。
  • 驻留在 MySystem.Application 命名空间中的模块可以调用由 MySystemMySystemWeb 边界导出的模块中的函数。

不允许所有其他跨境呼叫。

接下来,您需要添加混合编译器:

defmodule MySystem.MixProject do
  use Mix.Project

  def project do
    [
      compilers: [:boundary] ++ Mix.compilers(),
      # ...
    ]
  end

  # ...
end

边界规则在编译期间进行验证。例如,如果我们有以下代码:

defmodule MySystem.User do
  def auth do
    MySystemWeb.Endpoint.url()
  end
end

编译器会发出警告:

$ mix compile

warning: forbidden reference to MySystemWeb
  (references from MySystem to MySystemWeb are not allowed)
  lib/my_system/user.ex:3

完整的工作示例可在此处 here 获得。

由于boundary是作为混合编译器实现的,因此它与可以与混合编译器一起使用的编辑器无缝集成。例如,在 VS 代码和 Elixir LS 中:

限制外部应用程序的使用

边界还可以用于管理对其他应用程序的调用,即使这些应用程序没有定义自己的边界。例如,假设您要强制执行以下规则:

  • MySystemWeb可以使用phoenix模块
  • MySystem可以使用ecto模块,Ecto.Changeset除外,MySystemWeb也可以使用ecto模块
  • 只有MySystemMix可以在运行时使用混合模块。每个人都可以在编译时使用混合模块。

默认情况下,边界不检查对其他应用程序的调用。但是,我们可以指示它检查对所需应用程序的调用。可以为每个单独的边界或全局提供此设置。由于我们希望在整个项目中限制对这些边界的调用,因此我们在 mix.exs 中全局执行此操作:

# mix.exs

defmodule MySystem.MixProject do
  def project do
   [
     boundary: [
       default: [
         check: [
           apps: [:phoenix, :ecto, {:mix, :runtime}]
         ]
       ]
     ],
     # ...
   ]
  end
end

通过这些设置,boundary 将检查对 phoenix 和 ecto 的所有调用,以及对 mix 的所有运行时调用。不会检查对 mix 的编译时调用。

现在我们需要允许在我们的边界内调用这些应用程序:

defmodule MySystemWeb do
  use Boundary, deps: [Phoenix, Ecto.Changeset]
end

defmodule MySystem do
  use Boundary, deps: [Ecto, Ecto.Changeset]
end

defmodule MySystemMix do
  use Boundary, deps: [Mix]
end

请注意,在 MySystem 中,我们指定了 EctoEcto.Changeset。这是因为 Ecto.Changeset 被列为 MySystemWeb 中的 dep,因此它被视为单独的边界。

路线图

  • [x] 验证对外部部门的调用(e.g。防止 MySystemWeb 使用 Ecto,或阻止 MySystem 使用 Plug
  • [x] 支持编译时与运行时依赖
  • [x] 支持嵌套边界(在边界内定义内部边界)
  • [ ] 支持 Erlang 模块

相关文章

精彩推荐