Phoenix/Elixir:何时创建新上下文的提示

Nat*_*han 1 elixir phoenix-framework

我已经启动了许多Phoenix项目,但我总是发现正确定义Contexts具有挑战性。我经常最终在上下文中创建“子上下文”,我认为这很好,但它从来都不是完美的。

我的问题是人们对于定义 Phoenix 上下文有什么建议?或者某种在上下文之间划线的试金石。


举个例子:

我有一个与 Slack 的 API 集成的应用程序,并使用 Google 的地图 API 查找时区/位置信息,并使用 Stripe 进行付款。

  1. 我从 Slack Context 开始,但很快发现我将 Slack API 逻辑与应用程序的实际业务逻辑混合在一起。
  2. 我将所有 Google 地图内容放在 Google Context 下
  3. 我将所有 Stripe 内容放在 Payments Context 下

此时我基本上是通过第三方工具分离上下文,但最终得到了过于臃肿的 Slack 上下文。一旦应用程序建立起来,重新构建一个人的观点并重新定义上下文也具有挑战性。

T. *_*tek 7

供参考的快速链接

  1. BoundedContext 作者:Martin Fowler (bliki)
  2. Vaughn Vernon 提炼的领域驱动设计(书籍)
  3. 使用 OTP 设计 Elixir 系统,作者:James Edward Gray, II 和 Bruce Tate(书)

前言

首先我要说的是,我不喜欢我们将处理相同领域问题的分组模块的概念称为“Phoenix 上下文”,因为 Phoenix 是一个 Web 框架,这些上下文正在处理业务逻辑(又名.应用程序的核心功能)。也就是说,我很高兴他们(Phoenix)正在传播更好、更具可扩展性的软件架构。

什么是上下文

Phoenix 文档描述它们的方式是“上下文是公开和分组相关功能的专用模块”。这从代码的角度描述了上下文,但它并没有从概念上描述上下文。为了更清楚地了解什么是(有界)上下文,我建议查看Martin Fowler 的 BoundedContext。简而言之,它们是一种设计和架构软件的方法,以便处理特定域或域的一部分的模块仍然封装在一个气泡中,在气泡中它们讲自己的语言,每个处理这个问题的人都熟悉

你的问题

您提到您创建了一个 Slack 上下文,但最终将 Slack API 逻辑与业务逻辑混合在一起。

您并不是唯一对此感到困惑的人。我正在一家公司工作,我是该公司招聘渠道的一部分。基本上,我会评估求职者提交的任务分配,这是我遇到的最常见的设计之一。

问题在于您仅在一个维度上考虑上下文。您只考虑如何根据域中的相似性对模块进行分组 - 我喜欢将其视为“水平”分组。

诀窍是还可以根据应用程序本身的相似性对模块进行分组。代码正在处理应用程序的哪一层。

什么是层

层是一种对在应用程序架构中执行的任务相似的模块进行分组的方法。例如,您可以拥有“业务逻辑”(核心、不可变功能)层、处理外部服务(HTTP 请求、Slack 库、Dropbox 库等)的模块层、处理外部服务的模块层具有应用程序状态和生命周期(GenServers,Supervisors,...),处理工作进程和例程(工作模块,...)等的模块层。

James Edward Gray, II 和 Bruce Tate 所著的 Designing Elixir Systems With OTP是有关层以及如何构建应用程序并使这些层尽可能相互连接的重要资源。

这种类型的分组更“垂直”。按层分组。

您知道您有一些处理 Slack 的逻辑,因此您创建了一个 Slack 上下文。您知道您有一些 Google 逻辑,因此您创建了一个 Google 上下文。然后,您还有主要业务逻辑,因此您创建一个时区(例如)上下文。

请注意这三个上下文之间的区别 - 时区几乎是一个业务逻辑上下文。我们知道那里正在发生一些奇特的日期时间计算,仅此而已。但随着 Slack 和 Google 的出现,事情变得更加棘手。可能有 HTTP 请求、一些供应商特定的错误处理、映射、过滤、减少等。

如何结合上下文和图层

对于您来说,回答这个问题是一个困难的问题,因为为了创建良好的上下文,我必须成为日期时间操作领域的专家,我们在其中搜索最佳会议时间(根据您的应用程序,何时聊天)。同样,为了能够在适当的层中创建适当的模块,我必须从技术角度更多地了解您的应用程序正在做什么。

不过,我会尝试猜测它可能会做什么,这样我就可以给你一个例子。

各层

  1. 我认为第一层也是最重要的一层当然是你的业务逻辑层。这就是你的秘密武器所在。您神奇的日期操作,您可以计算最佳会议时间。该层的模块可以存在于lib/when_to_chat/
  2. 我们需要的第二层是网络层。我假设您的应用程序公开了某种 REST API 供其他服务调用。这就是菲尼克斯基本上居住的地方。控制器、视图、模板、通道等;该层可以位于lib/when_to_chat_web/(根据凤凰公约)
  3. 我们需要的第三层是services层。这是 Slack、Stripe、Google 地图集成所在的位置。在这里,您将发出 HTTP 请求、使用调用外部服务的库、处理超时等;该层的模块可以驻留在lib/when_to_chat_services/例如
  4. 您可能需要的另一层是数据库/存储层。如果您使用 Ecto 并且要保留一些数据,则所有查询和模式、验证等都应驻留在该特定层中。该层的目录可以是lib/when_to_chat_storage/

当我思考你的代码可能会做什么时,这只是我的想法。也许您需要另一层来处理 GenServer 和 Supervisor 等。

背景

现在来说说背景。请记住,上下文根据模块的功能对模块进行分组,而层则根据模块的工作方式对模块进行分组。这也意味着不同层中可能有不同的上下文。例如,如果您考虑一下,Slack这是我们正在集成的一项服务。在服务层中拥有 Slack 上下文是有意义的,但在业务逻辑层中则不然。

因此,首先,在业务逻辑层中,您可以有 3 个上下文: - 时区 <- 这是您的核心魔法(示例函数:)find_best_time_for_users(user)- 付款 <- 这是您计算发票、折扣等的地方。(示例函数:process_payment(user, amount, selected_provider) - 通知 < - 这是您推送消息或发出警报等的地方(示例功能notify_via_slack(user, notification)

接下来,在 Web 层中,您将拥有标准的 Phoenix 内容,我们将在此处跳过。

之后,在服务层中,您可能拥有 3 个上下文:slack、stripe 和 google_maps。在它们内部,您处理一些数据导入/导出,但不处理任何核心计算逻辑。

最后,在存储层中,您可以有一个用于accounts存储用户信息的上下文和一个payments用于处理支付查询、表、模式等的上下文。

那么这一切看起来怎么样呢?

您的项目最终可能看起来像这样: 包含所有层和上下文的项目的屏幕截图

当然,这既是艺术,也是科学。线条不是很清晰,有时层之间确实会有点泄漏。好的一面是,凭借多年的经验,我们不断学习和改进,因此重构总是能让我们的代码变得更好!

有关如何设计应用程序以使其专注于业务的更多信息,我建议您查看Vaughn Vernon 的领域驱动设计蒸馏

最后一点:我的答案不是某种处理软件架构的标准化方法。它是链接参考文献和我自己的想法的教导的结合。请随意尝试并找出适合您和您的团队的方法!