Bob叔叔的干净架构-每层都有一个实体/模型类?


44

背景 :

我试图在我的Android应用程序中使用Bob叔叔的干净架构。我研究了许多开源项目,这些项目试图展示正确的方法,并且发现了一个基于RxAndroid 的有趣实现

我注意到的是:

在每一层(表示,域和数据)中,都有一个用于同一实体的模型类(正在谈论UML)。另外,每当数据越过边界(从层到另一层)时,都有一些映射器类负责对象的转换。

题 :

当我知道如果需要所有CRUD操作时,它们都将具有相同的属性,是否需要在每个层中都有模型类?还是使用干净的体系结构是规则还是最佳实践?

Answers:


52

我认为这绝对不是它的意思。这违反了DRY。

这个想法是对中间的实体/领域对象进行建模以尽可能好地表示领域。它是一切的中心,一切都可以依赖它,因为域本身在大多数情况下都不会改变。

如果外部的数据库可以直接存储这些对象,则将它们映射为另一种格式以分离层不仅是没有意义的,而且还创建了模型的副本,而这并非意图。

首先,构建干净的体系结构时要考虑到不同的典型环境/场景。具有庞然大物的外层的业务服务器应用程序需要自己类型的特殊对象。例如,产生SQLRow对象并需要SQLTransactions返回更新项目的数据库。如果要在中间使用它们,那么您将违反依赖关系指导,因为您的核心将依赖于数据库。

使用轻型ORM来加载和存储实体对象的情况并非如此。他们在内部SQLRow域名和您的域之间进行映射。即使您需要将@EntitiyORM 的注释放入域对象中,我也会认为这不会建立外层的“提及”。因为注释只是元数据,所以没有专门寻找它们的代码不会看到它们。更重要的是,如果删除它们或将其替换为其他数据库的注释,则无需进行任何更改。

相反,如果您确实更改了域并创建了所有这些映射器,则必须进行很多更改。


修订:上面的内容有些简化,甚至可能是错误的。因为干净架构中有一部分希望您为每层创建一个表示。但这必须在应用程序的上下文中看到。

即在这里https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

重要的是,隔离的,简单的数据结构跨边界传递。我们不想欺骗并传递实体或数据库行。我们不希望数据结构具有违反“依赖关系规则”的任何依赖关系。

将实体从中心传递到外层不会违反依赖关系规则,但已提及它们。但这在设想的应用中具有一个原因。传递实体会将应用程序逻辑移到外部。外层将需要知道如何解释内部对象,它们将必须有效地完成内部层(如“用例”层)应该做的事情。

除此之外,它还使各层解耦,从而使更改核心不必更改外部层(请参见SteveCallender的评论)。在这种情况下,很容易看出对象应如何特别代表它们的用途。此外,各层还应根据专门为该通信目的制作的对象相互通信。这甚至可能意味着存在3种表示形式,每层1种,层间传输1种。

还有https://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html,该地址位于上面:

其他人担心我的建议的最终结果将是大量重复的代码,以及在系统各层之间从一个数据结构到另一个数据的很多死记硬背的复制。当然我也不想要这个。我所建议的一切都将不可避免地导致数据结构的重复和字段复制的过度。

IMO暗示对象的普通1:1复制在体系结构中是一种气味,因为您实际上并未使用适当的层和/或抽象。

后来他解释了他如何想象所有的“复制”

通过在两者之间传递简单的数据结构,可以将UI与业务规则分开。您不会让您的控制者对业务规则有所了解。相反,控制器将HttpRequest对象解压缩为简单的原始数据结构,然后将该数据结构传递给通过调用业务对象来实现用例的交互对象。然后,交互器将响应数据收集到另一个普通数据结构中,并将其传递回UI。视图不了解业务对象。他们只是查看该数据结构并提出响应。

在此应用程序中,表示形式之间存在很大差异。流动的数据不仅仅是实体。这保证并要求不同的等级。

但是,应用于像照片查看器这样的简单Android应用程序时,该Photo实体具有大约0个业务规则,并且处理它们的“用例”几乎不存在,并且实际上更关心缓存和下载(该过程应由IMO更加清晰地表示),对照片进行单独表示的点开始消失。我什至感觉到照片本身是数据传输对象,而真正的业务逻辑核心层却缺失了。

“通过在两者之间传递简单的数据结构将UI与业务规则分开”“当您要在途中将照片重命名3次时之间存在区别。

除此之外,我看到这些演示应用程序无法代表干净的体系结构的地方在于,它们为了分离层而更加强调分离层,但有效地隐藏了应用程序的工作。这与https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html中所说的相反-

软件应用程序的体系结构尖叫着关于应用程序的用例

我看不到在干净的体系结构中分离各层的重点。它是关于依赖关系的方向,并专注于代表应用程序的核心-实体和用例-在理想的纯Java中,不依赖外部。与该核心的依赖关系不大。

因此,如果您的应用程序实际上具有代表业务规则和用例的核心,并且/或者不同的人员在不同的层上工作,请以预期的方式将它们分开。另一方面,如果您只是自己编写一个简单的应用程序,请不要过度使用它。具有流畅边界的2层可能绰绰有余。以后也可以添加图层。


1
@RamiJemli理想情况下,实体在所有应用程序中都是相同的。那就是“企业范围的业务规则”和“应用程序业务规则”(有时是业务与应用程序逻辑)之间的区别。核心是实体的非常抽象的表示形式,其通用性足以使您可以在任何地方使用它。想象一下,一家银行有很多应用程序,一个用于客户支持,一个在自动取款机上运行,​​一个作为客户自己的Web ui。所有这些都可以使用相同的BankAccount规则,但是根据特定于应用程序的规则,您可以对该帐户执行哪些操作。

4
我认为干净架构中的重要一点是,通过使用接口适配器层在实体的不同层表示之间进行转换(或者说映射),可以减少对所述实体的依赖。如果用例或实体层发生了更改(希望不太可能,但是随着需求的变化,这些层也会更改),那么更改的影响将包含在适配器层中。如果您选择在整个体系结构中使用相同的实体表示形式,则此更改的影响会更大。
SteveCallender 2015年

1
@RamiJemli最好使用使生活更简单的框架,重点是,当您的体系结构依赖它们时,您应格外小心,并开始将它们置于一切的中心。这里是甚至大约RxJava的文章blog.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html -这不是说你不应该使用它。这更像是:我已经看到了这一点,一年之内会有所不同,当您的应用程序仍然存在时,您就会被它所困扰。应用普通的SOLID原理,在普通的Java中做一个细节并做最重要的事情。
zapl 2015年

1
@zapl您对Web服务层有同样的感觉吗?换句话说,您是否会将@SerializedNameGson注释放在域模型上?还是您将创建一个负责将Web响应映射到域模型的新对象?
tir38 '16

2
@ tir38分离本身并不能带来好处,而是随之而来的未来更改的成本。=>取决于应用程序。1)您花费时间来创建和维护在不同表示之间转换的添加阶段。例如,将字段添加到域中并忘记将其添加到其他地方并不是闻所未闻。简单的方法不可能发生。2)如果后来发现您需要它,则需要过渡到更复杂的设置。添加层并不容易,因此在大型应用程序中更容易证明不需要立即使用的更多层
zapl

7

您实际上是正确的。而且,由于您接受SRP,因此不会违反DRY。

例如:您有一个业务方法createX(String name),那么您可能在DAO层中有一个方法createX(String name),在business-Method中被调用。它们可能具有相同的签名,也许只有一个代表团,但它们具有不同的目的。您还可以在UseCase上使用createX(String name)。即使那样,它也不是多余的。我的意思是:相同的签名并不意味着相同的语义。选择其他名称以使其语义清晰。命名本身完全不会影响SRP。

UseCase负责特定于应用程序的逻辑,业务对象负责与应用程序无关的逻辑,而DAO则负责存储。

由于语义不同,所有层都可能具有自己的表示和通信模型。通常,您将“实体”视为“业务对象”,而常常看不到将它们分开的必要性。但是,在“巨大”的项目中,应努力正确地分离各层。项目越大,就可能需要不同层和类中表示的不同语义。

您可以想到同一语义的不同方面。用户对象必须显示在屏幕上,它具有一些内部一致性规则,并且必须存储在某处。每个方面都应以不同的类(SRP)表示。创建映射器可能会很麻烦,因此在我从事这些方面工作的大多数项目中,我都融为一体。这显然违反了SRP,但是没有人真正在乎。

我称清洁架构和SOLID的应用“在社会上不可接受”。如果允许的话,我会处理的。目前,我不允许这样做。我等一下,我们必须考虑认真对待SOLID。


我认为数据层中的任何方法都不应与域层中的任何方法具有相同的签名。在域层中,您使用与业务相关的命名约定,例如signUp或login,在数据层中,您使用保存(如果是DAO模式)或添加(如果是存储库,因为此模式使用Collection作为隐喻)。最后,我不是在谈论实体(数据)和模型(域),而是在强调UserModel及其Mapper(表示层)的无用性。您可以在演示文稿中调用域的User类,这不会违反依赖关系规则。
拉米·杰姆利

我同意Rami的观点,不需要映射器,因为您可以直接在interactor实现中进行映射。
Christopher Perry

5

不,您不需要在每一层都创建模型类。

实体DATA_LAYER)-是数据库对象的完整或部分表示。DATA_LAYER

MapperDOMAIN_LAYER)-实际上是一个将Entity转换为ModelClass的类,该类将用于DOMAIN_LAYER

看看:https : //github.com/lifedemons/photoviewer


1
当然,我并不反对数据层中的实体,但是,在您的示例中,表示层中的PhotoModel类与域层中的Photo类具有相同的属性。从技术上讲,这是同一堂课。那有必要吗?

我认为您的示例中出现了问题,因为域层不应依赖于其他层,就像您的示例中,映射器依赖于您的数据层中的IMO IMO实体,反之亦然
navid_gh
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.