前端整洁架构,你了解多少?( 五 )


在设计阶段,还没有外部限制 。这使我们能够尽可能地反映主题领域的数据转换 。转换越接近现实,检查其工作就会更容易 。
实现细节:共享内核你可能已经注意到,在描述领域类型时使用了一些类型,例如Email、UniqueId或DateTimeString 。这些都是类型别名:
// shared-kernel.d.tstype Email = string;type UniqueId = string;type DateTimeString = string;type PriceCents = number;我通常使用类型别名来摆脱基本类型过度使用的问题 。
这里使用DateTimeString而不仅仅是string,是为了清楚地表明使用了哪种类型的字符串 。类型与主题领域越接近,处理错误时就越容易 。
指定的类型位于shared-kernel.d.ts文件中 。共享内核是代码和数据,其依赖关系不会增加模块之间的耦合 。
实际上,共享内核可以这样解释 。我们使用TypeScript,使用它的标准类型库,但不认为它们是依赖关系 。这是因为使用它们的模块可能不了解彼此,并保持解耦状态 。
并非所有的代码都可以归类为共享内核 。最主要且最重要的限制是这类代码必须与系统的任何部分兼容 。如果应用的一部分是用TypeScript编写的,另一部分是用其他语言编写的,共享内核只能包含可用于这两部分的代码 。例如,JSON 格式的实体规范是可以的,但TypeScript的帮助类就不行 。
在我们的例子中,整个应用程序都是用 TypeScript 编写的,所以对内置类型的类型别名也可以归类为共享内核 。这样的全局可用类型不增加模块之间的耦合,可以在应用的任何部分使用 。
实现细节:应用层既然已经搞清楚了领域,下面来继续介绍应用层,这一层包含了用例 。
在代码中,我们描述了场景的技术细节 。用例是对在将商品添加到购物车或进行结账后数据应该发生的情况的描述 。
用例涉及与外部的交互,因此需要使用外部服务 。与外部的交互是副作用 。我们知道,在没有副作用的情况下,更容易处理和调试函数和系统 。而且,我们的大部分领域函数被编写为了纯函数 。
了将纯净的转换和与非纯的交互结合起来,可以使用应用层作为非纯的上下文 。
纯转换的非纯上下文纯转换的非纯净上下文是一种代码组织方式,其中:

  • 首先执行一个副作用来获取数据 。
  • 然后对该数据进行纯转换 。
  • 最后再次执行一个副作用来存储或传递结果 。
在“将商品放入购物车”用例中,这看起来像是:
  • 首先,处理程序将从存储中检索购物车状态 。
  • 然后,它将调用购物车更新函数,并传递要添加的商品 。
  • 最后将更新后的购物车保存在存储中 。
整个过程是一个“三明治”:副作用,纯函数,副作用 。主要逻辑体现在数据转换中,与外部的所有通信都被隔离在一个命令式的外壳中 。
前端整洁架构,你了解多少?

文章插图
函数式架构:副作用,纯函数,副作用
非纯上下文有时被称为命令式外壳中的函数式核心 。这就是我们在编写用例函数时将使用的方法 。
设计用例这里我们将选择和设计结账用例 。这是最具代表性的一个,因为它是异步的,并与许多第三方服务进行交互 。
先来思考一下在这个用例中想要达到什么目标 。用户有一个带有商品的购物车,当用户点击结账按钮时:
  • 想要创建一个新的订单 。
  • 在第三方支付系统中支付订单 。
  • 如果付款失败,向用户通知 。
  • 如果成功,将订单保存在服务器上 。
  • 将订单添加到本地数据存储中以显示在屏幕上 。
从 API 和函数签名的角度来看,我们希望将用户和购物车作为参数传递,并让函数自行处理其他所有事情 。
type OrderProducts = (user: User, cart: Cart) => Promise<void>;理想情况下,用例不应该采用两个单独的参数,而是一个命令,它将在自身内部封装所有的输入数据 。但我们不希望让代码变得臃肿,所以将使用这种方式 。
编写应用层端口让我们来仔细看看用例的步骤:订单创建本身就是一个领域函数,其他都是想要使用的外部服务 。


推荐阅读