干净的体系结构级联删除资源



目前,我一直在尝试使用"清洁建筑";尝试分离许多东西,创建定义更好的接口,并使代码更容易测试。

现在,我们的资源是以层次结构的方式相互关联的。我们可以将其视为:

A
|-B
| |
| C
|
|-D
|-F
|-G
|-H
|-I

这是一个非常粗略的例子,更多的是为了展示,而不是描述我正在处理的事情。

在这种情况下,我可能会有一些";用例";我想去的地方;删除A";。在我们的例子中,当我们删除该层次结构中的类型时,我们希望删除所有相关的数据,包括子类型的数据。这就是我不确定如何准确处理事情的地方。我已经考虑过一些方法,正在寻找更好的选择。

第一个是要有一个";用例";其具有所有这些功能(针对所有类型的删除),并且因此可以引用自身来调用其他删除方法。我真的不喜欢这样,因为在我看来这违反了";打开/关闭原理";而且似乎很难维持。

第二种方法是使";用例"删除A";取决于";删除B";以及";删除D";(例如)"删除A";则将具有"0"的依赖性;删除A&删除B&删除D";。这似乎使测试变得困难,因为现在它实际上依赖于每个子类型的接口";删除"用例";。

我正在努力解决这个问题。我考虑过一种服务定位器模式,但我也不太喜欢它。这似乎使依赖关系变得不清楚,并且它们只能在运行时得到解决(没有编译时检查)。

欢迎提出任何建议。我发现很难找到关于这类案件的信息。我发现的例子似乎与";DoSomethingWithX";其中结果不会级联到许多类型中。

我假设您拥有的资源是实体,用例/应用程序逻辑层知道这些实体。我对Clean Architecture有所了解,但不足以知道我要说的话是否违反了它的任何特定规则。

你是如何接触这些实体的?他们是";愚蠢的";比如DTO,还是他们有一些智慧?例如,一个实体知道它的直系亲属是谁吗?例如,在你的例子中,D会知道F和g。

脑海中浮现的选项:

愚蠢实体-如果您的实体没有智能,则应用程序逻辑将被迫通过提取所有可能相关的实体进行补偿,然后管理其删除。优点/缺点:

  • 删除逻辑/智能都干净地保存在一个层中(应用程序逻辑层-忽略所有应该在适当层中的基础CRUD代码)
  • 性能可能会有所不同,部分取决于获取实体树和处理它们的效率
  • 实体可以保持超级愚蠢。(也许太笨了?)
  • 该逻辑可以执行它需要的任何操作,以确保可以安全地删除实体。例如,确保逻辑的其他部分不会试图更改任何预定删除的内容的状态;确保删除符合ACID

";我认识我的直系亲属";实体-如果实体的智慧非常有限:他们知道自己的直系亲属是谁,但不知道其他什么。这将允许递归。逻辑层可以做一个循环,问第一个实体:"嘿,有孩子吗?"?如果为true,则提取这些实体,将其添加到实体的平面列表中,并询问相同的问题,直到只得到false,然后退出循环。然后,逻辑可以删除平面列表上的所有实体。优点/缺点:

  • 删除逻辑/智能都干净地保存在一个位置(应用程序逻辑层-忽略所有应该在其适当层中的基础CRUD代码)
  • 实体树越大,必须执行的递归次数越多,性能可能会受到影响
  • 假设实体树没有被外部逻辑部分更改(例如,添加到尚未删除的实体的子级,但只是告诉逻辑它没有子级)。我想你可以研究一些标志系统,这样逻辑就不会违反它自己。这个特定问题可能有设计模式,但我想不出任何模式,也无法快速搜索
  • 实体可以保持相对较笨的状态,但仍有一些基本有用的方法。确保你建立了清晰一致的设计规则;愚蠢的";足够有用,可以添加到实体中,而不会使它们膨胀,也不会意外地将它们变成逻辑(不干净的体系结构)

删除机制应该能够处理各种类型,只要它们实现给定的接口,例如IDeletable。

在结束时,考虑一下哪种方法最适合您的情况:基于递归(第二个选项)或基于集合(第一个选项),您可以建立一个所有内容的列表,然后删除。考虑正在进行的运行时假设(任何东西都能对您要删除的实体起作用吗),并平衡运行时需求与设计时需求。

别忘了你可以"柔软的";删除内容。例如,递归地设置一个标志,在对范围内的所有软删除项进行基于集合的删除之前,进行最后一分钟的检查。

更新RE软删除/OP注释

好吧,如果你有一个实体,它只代表其他地方的物理记录(数据库中的行、文件等),你可以软删除这个实体,并将其作为数据访问/文件系统代码的信号,继续执行实际的删除。

软删除可以是bool,也可以定义一个状态更复杂的标志,如NormalMarkedForDeletionDeletionInProgressDeleted或其他什么。

我想用"删除";你指的是持久层中的删除。如果是这样,级联删除应该放在那里,而不是放在用例中。

您可能有一个CMS系统,用户可以在其中创建网页。这些页面包含部分、图像、可能有附件等等;删除页面";在请求存储库删除页面之前,可能会执行一些验证逻辑,但随后存储库将负责删除。存储库的功能在很大程度上取决于您使用的存储类型。例如,如果";页面";是您的聚合根,存储库使用面向文档的nosqldb,它只是删除文档和所有包含的东西,如图像、附件。。。都不见了。如果持久层使用关系数据库,它可能会从不同的表中删除行,或者只删除页面行,其余的由数据库的级联删除配置完成

所以我认为应该把它放在持久层。

编辑

在我的情况下,数据不一定只存储在数据库中,还可以通过不同的存储介质存储(例如,某些文件存储中的二进制文件)。

您的建议似乎需要引用其他存储库的存储库。这就是你的意思吗?

UserRepo可以在DocumentRepository上调用delete,但您也可以使用接口反转依赖关系,例如具有方法onUserDeleted()UserChangedHandler

+------------+        +--------------------+
| DBUserRepo | -----> | UserChangedHandler |
+------------+        +--------------------+
^
|
+-----------------+--------------+ 
|                                |
+------------------------+        +---------------------+ 
| FileDocumentRepository |        | RestImageRepository |
+------------------------+        +---------------------+

正如您所看到的,ChangeHandler可以由任何其他存储库实现。它可能会删除存储在本地文件系统中的Document和存储在远程服务器上的Image

存储库实现UserChangeHandler,或者创建一个适配器将处理程序逻辑与存储库分离。

+------------+        +--------------------+
| DBUserRepo | -----> | UserChangedHandler |
+------------+        +--------------------+
^
|
+---------------------------+        +------------------------+ 
| DocumentUserChangeAdapter |  --->  | FileDocumentRepository |
+---------------------------+        +------------------------+

在分布式环境中,ChangeHandler也可能向队列发出消息,其他系统将异步删除其他数据。

你的描述似乎也意味着;用户回购";将知道数据的整个层次结构(User->SomeData->SometerData),因此每当层次结构中的任何事情发生变化时;用户回购";。

正如我在上面试图展示的,这并不一定意味着UserRepo实现知道所有其他数据结构和持久性类型。持久性管理不能驻留在同一服务器上,也不能在同一进程中运行。

这取决于你如何构建你的系统。有许多不同的方法来解决它,这取决于功能和非功能需求。

PS:您用打字脚本标记了您的问题,所以您可能想像这个一样定义UserChangeHandler

export type UserChangeHandler = (userId : string) => void;

或者如果更改处理程序应该支持异步,则返回CCD_ 16。

最新更新