打字游戏正确率设计vc_正确设计服务

打字游戏正确率设计vc

深入了解代码库的核心。

问候,域服务。 您已被Business Logic招募,以捍卫边界免受Inconsistency和Interfaces入侵。

几天前, 我写了一篇关于“六角形体系结构”的文章 ,解释了为什么您应该使用这种强大的体系结构模式来设计系统。

在本文中,我想深入研究六角体系结构的最重要部分之一: 域服务

什么是域服务?

这取决于您从哪个角度进行描述。

从客户端的角度来看,服务是唯一可以与业务逻辑进行交互的端点。

从内部角度来看,服务可以协调并执行业务逻辑,同时保护您的业务不变量。

很抽象,不是吗? 让我们进入更多细节。

服务的剖析

服务中的所有事情都是顺序发生的。

当接口调用服务表面上可用的方法之一时,这是在内部执行的典型序列:

  1. 方法调用的参数被转换为域对象;
  2. 实施跨实体业务约束;
  3. 相关业务逻辑是在特定实体上执行的;
  4. 由此产生的变更将在服务范围之外发布;
  5. 方法调用的结果将传递回接口。

让我们一步一步地进行操作。

1.参数翻译

作为业务逻辑和接口之间的联系点,服务必须将传入的请求转换为对内部域有意义的内容。

这基本上意味着将方法参数转换为属于该域的值对象

但是,这不仅是数据表示转换,而且是域约束的早期应用。 通常,开发人员将其称为验证

让我们来看一个例子。

假设您正在开发一个非常简单的床和早餐管理系统。

其中一个模块必须允许通用管理员按名称添加新的B&B。

您要强制执行的第一个业务约束是B&B名称不能为空

并且,当您将传入的字符串值包装在有意义的Name值对象中时,您想在转换阶段强制执行它。

这个想法是, 空的Name在您的域中没有任何意义,因此甚至不可能构造这样的object

这就是您的Value Object声明的样子:

我们还假设您的系统处理来自不同所有者的许多属性。 这意味着要为特定的所有者创建新的B&B,我们需要将OwnerId和B&B名称一起传递。

到目前为止,这是服务端点的外观:

2.跨实体约束保护

让我们为您的虚拟域添加更多规则:

  1. 所有者可以被激活或停用;
  2. 无法为已停用的所有者添加新的B&B。

#2表示在添加新的B&B之前,您必须检查是否已激活所有者。

您发现了另一个业务限制。 但是这次不同了。

这次,您的约束不受B&B约束。 这次,您的约束取决于不是您的服务主题的外部实体(所有者)。

因此,这里的相关问题是: 我们应该在哪里定义这个新约束?

在B&B实体类中? 还是像我们对名称所做的那样,在语义上属于实体的值对象中?

答案是:都不是。 检查跨实体约束的唯一地方是服务本身。

主题实体无法访问有关所有者及其当前状态的任何知识。 这就是为什么此约束不能属于B&B实体的原因。

服务必须对其进行处理,以使总体状态与业务不变性保持一致。

让我们更新代码并在Service级别添加约束:

3.实体操纵

是时候照顾这家住宿加早餐旅馆了。

传递所有跨实体约束之后,服务可以安全地操纵主题实体。

在这种情况下,您要创建一个属于激活的所有者的新B&B并将其保存。

连同跨实体约束,这是我们业务逻辑的核心。

这是怎么回事:

  1. 如果该实体已经存在(不是您当前的用例),那么该服务将从相关存储库中加载主题实体开始;
  2. 如果实体存在,则服务调用与当前用例相关的特定实体方法,从而导致实体状态的内部变化;
  3. 相反,如果Entity尚不存在,则Service只需使用类构造函数或语义相关的静态工厂方法来创建它;
  4. 服务将实体保存回存储库,更新状态将在其中进行序列化。

在以下用例中,您可能想要例如将房间添加到现有的B&B。

在这种情况下,更新后的服务将如下所示:

4.发布更改

在六边形架构的上下文中, 服务是CQRS架构写侧的实现

因此,它必须没有任何get *方法可用于按需检索信息。

读取模型之一完全负责此责任。 读取模型在决策支持系统中的作用不属于任何服务。 它们属于接口。

那么,您如何告知外部业务逻辑已成功应用并且发生了相关的事情?

简单: 您发布了域事件

域事件是唯一逃避服务黑匣子的信息(有一个例外,请参阅本节后面的内容)。

B&B已成功添加?

该服务发布了一个BnBWaFyre事件。

是否已将新房间添加到特定的B&B?

该服务发布了RoomWaFyre事件。

所有等待发布这些事件以触发其自身行为的外部系统都必须以发布/订阅的方式对其进行订阅。

从技术上讲,事件的发布方式特定于您的系统和需求。 我发现将LoopBack(在内存中)发布者和Message Broker(例如Kafka)结合起来通常是一个很好的解决方案。

但是,服务发布事件唯一需要的是发布者接口。 因此,让我们将其添加到我们的代码中。

在最后的代码更新中,除了添加Publisher界面之外,还发生了几件事。

#1 — Bnb实体引入并实现了ReadOnlyBnb接口。

当您要共享有关实体的信息,但又不想公开更改其内部状态的功能时,此功能很有用。

它还可用于向存储库实现提供有关内部状态的信息。 当调用save方法时,所有必需的信息都将随手可用,以便存储库以所需的任何格式存储实体。

#2-现在,每次成功执行业务逻辑并且更改系统状态时,服务都会发布一个事件。

如何将这些事件序列化为消息是您的系统和所用技术的固有特性,因此我将不对其进行深入研究。

5.响应界面

在本节的开头,我预计域事件是唯一可以逃避服务黑匣子的信息 ,只有一个例外。

此异常是Service方法的返回值

是的,您的服务实际上应该返回一些东西。

这对于接口对他们所请求的命令具有即时反馈并避免无用的意外复杂性至关重要。

但是,服务应返回什么?

输入结果

不无效。 不是布尔值。 不为空。 不是你的赤裸裸的实体。

结果是一个容器对象,其中包含有关服务内部操作结果的信息。

如果操作成功,则Service返回一个Result对象,该对象包装操作主题(例如,新添加的(ReadOnlyBnb)Bnb )。

如果出了什么问题怎么办?

如果操作不成功,则该服务仍将返回一个Result对象。 但是在这种情况下,该对象将包装Service方法调用内发生的异常。

如果您习惯了函数式编程,那么这与Either monad类似。

在任何情况下,Service方法都不应让异常逸出其框外。

您想100%控制您的服务传达回接口的内容。 让内部异常从服务中自由逸出会违反此原则。

是代码示例所使用的Result微库的Java实现。 (是的,它使用表情符号作为方法名称!)

现在,让我们通过使服务健壮和可靠,并返回Result对象,为服务添加画龙点睛的功能。 您可能还需要在此时添加一些日志记录。

因此,这是服务如何使用Result库同时实现优雅和鲁棒性的方法:

  • 服务通过get factory方法创建类型化的Result。
  • 如果一切顺利,则Result对象的类型将为Success,并将包含业务主题( ReadOnlyBnbRoom ); 因此,通过实现Success类,❌方法调用不会产生任何效果;
  • 如果抛出任何异常,则将通过get factory方法捕获该异常,而将返回一个Failure对象; ❌方法将被执行并记录故障。 从现在开始,Result的类型将为Aborted,这意味着✅和both方法都将不会产生任何效果。
  • 之后,假设业务逻辑已成功执行,第二次使用✅方法发布相关事件;
  • 如果在事件发布步骤中出现问题,将执行第二个❌方法并记录部分失败;
  • 最后,返回结果。 重要的是要注意,如果至少方法的第一部分 成功 (主题实体的状态更改),则调用将被视为成功。 发布事件的成功与否与返回值无关。

这是覆盖所有可能的故障点并且仍然专注于方法内部重要内容的好方法,而将次要内容放在单独的逻辑轨道上。

最酷的是,表情符号确实可以帮助眼睛识别不同的路径,因此您只需很少的认知工作就可以从一个切换到另一个。

大! 您的服务现已准备就绪:-)

或不。 您仍然需要添加测试。

测试策略

大新闻: 您不应该对服务进行单元测试 。 就那么简单。

众所周知,单元测试对服务起反作用。

哇!?! ”-我已经听到了愤怒的尖叫声。

让我解释一下。

首先,您绝对希望以某种方式测试您的服务。 您应归功于您的利益相关者。 从那里逃脱不了

您不希望做的是使您的代码复杂,僵硬并且难以更改。

因为这正是对服务进行单元测试时发生的情况。

从本质上讲,服务主要是协调器。 它是一个对象,通过委派其依赖项来依次执行步骤。

服务序列也有许多分支。

为了对所有可能的分支进行单元测试,您将最终模拟所有内容,在各处设置期望值,并进行非常僵化和肿的测试设置。

基本上,您最终只会在测试模拟中复制大部分服务逻辑,仅用于测试一个特定分支。

不进行单元测试的另一个原因是: 意外的复杂性

对服务流进行完整的单元测试意味着您必须抽象化实体的创建方式和进入其生命周期的方式。

如果您无法从外部进行控制,则无法对服务进行真正的单元测试。

因此,您最终将要做的是开始引入Entity Factories

实体工厂在域服务上下文中是一种反模式,因为创建实体是服务在其“ 实体管理器 ”角色中的全部责任。

您应该拥有在端点参数中创建新实体所需的所有数据,并且不应将此责任委托给从属人。

那么测试服务的最佳策略是什么?

服务的最佳测试策略是用例驱动。

设置可选的前提条件,运行用例,评估结果。

该策略可以通过不同的方式实施-一种是给定的,何时,然后是结构-但是请不要:

  • 使用繁琐的测试工具;
  • 通过任何接口进行测试;

您的测试应该很简单:

  1. 设置服务所需的基础架构部分。 这通常是一个存储层;
  2. 在测试中实例化服务;
  3. 向您的服务发送可选命令以使其处于所需状态并满足前提条件;
  4. 发送用例命令并获得结果;
  5. 评估结果。

您还应该关闭事件发布,因为这可能会触发测试范围之外的逻辑。

从某种意义上说, 这是一个单元测试 。 在这种情况下,单位就是您整体的服务。

恭喜! 现在您的服务可以真正上线了:-)

翻译自: https://hackernoon.com/design-your-services-the-right-way-4c1b2af2b12c

打字游戏正确率设计vc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值