【问题标题】:On observing an execution tree of interdependent models in MVC在 MVC 中观察相互依赖模型的执行树
【发布时间】:2012-11-22 01:41:29
【问题描述】:

我已经在 Yii 框架上开发了一段时间(4 个月),到目前为止,我遇到了一些关于 MVC 的问题,我想与经验丰富的开发人员分享。我将通过列出它们的复杂程度来呈现这些问题。

[Level 1] CR(创建更新)表单。首先,我们有很多表格。每个表单本身都是一个模型,因此每个表单都有一些验证规则、一些属性和一些要对属性执行的操作。在很多情况下,这些表单中的每一个都使用单个活动记录对象在数据库中更新创建记录。
-> 所以在这种复杂程度下,表单必须

  • 打开时,

    • 能够以人性化的方式显示来自数据库的数据库友好数据

    • 能够显示所有具有活动记录对象属性的表单字段。从 db 表中添加、删除、更改列必须影响表单的显示。

  • 保存时,能够在获取数据前将对人类友好的数据格式化为对db友好的数据

  • 在验证时,能够执行活动记录对象强制执行的基本验证,它还必须执行其他验证来满足一些业务规则

  • 当验证失败时,能够回滚对属性所做的更改以及对数据库所做的更改,并向用户显示他们最初输入的数据。

[2 级] 扩展 CR 表格。一种可以同时从不同表创建/更新记录的表单。不仅如此,表单是否会创建/更新其记录之一有时取决于其他条件(更多业务规则),因此表单有时可以更新表 A、B 中的记录,但不能更新 D , 有时更新 A,D 的记录而不是 B -> 所以在这种复杂程度下,我们看到一个表单必须:

  • 能够满足[Level 1]

  • 能够有条件地创建/更新某些记录,有条件地创建/更新某些记录的某些列。

[Level 3] 模型树。表单在应用程序中的角色,在许多方面,是一个让用户与您的应用程序交互的端口。为了满足请求,此端口将与许多其他对象交互,而这些对象又与更多对象交互。其中一些对象可以被视为模型。 Active Record 是一个模型,但 Mailer 也可以是一个模型,RobotArm 也是如此。这些模型相互使用以满足用户的要求。每个模型都可以执行自己的操作,并且整个树必须能够回滚在错误/失败情况下所做的任何更改。

有没有人遇到或能够解决这些问题?

我想出了很多东西,比如将模型属性封装在 ModelAttribute 对象中,以解决它们在客户端、服务器和数据库层中的存在问题。

我还认为我们应该给模型树一个 Observer 来观察并通知观察到的模型在发生错误时回滚更改。但是如果可以存在多个观察者怎么办,如果一个节点使用其父的观察者但给它的孩子另一个观察者怎么办。

工程师、开发人员、Rails、Yii、Zend、ASP、JavaEE,任何 MVC 人,为了科学,请加入这个讨论。

--更新 teresko 的回复:---
@teresko我实际上打算将服务合并到工作单元内的执行中,并且让工作单元不必担心新/更新/删除。工作单元内的每个对象都将对其状态负责,并需要实现自己的 commit() 和 rollback()。一旦发生错误,工作单元将从最新注册的对象回滚到最旧的注册对象的所有更改,因为我们不仅处理数据库,还可以有邮件程序、发布者等。否则,树执行成功,我们从最旧的注册对象调用 commit() 到最新的注册对象。这样,邮件程序可以保存邮件并在提交时发送。

使用数据映射器是个好主意,但我们仍然必须确保 db 中的列与数据映射器和域对象匹配。此外,扩展的 CR 表单或具有依赖于其他模型的属性的模型必须在验证和数据类型方面匹配它们的属性。那么也许一个属性可以是一个对象并从一个模型运送到另一个模型?一个属性还可以判断它是否被修改,应该对其执行什么验证,以及它如何对人类友好、对应用程序友好和对数据库友好。对 db 架构的任何更新都会影响此属性,从而引发异常,需要开发人员对系统进行更改以满足此更改。

【问题讨论】:

    标签: forms model-view-controller attributes tree dependencies


    【解决方案1】:

    原因

    问题的根源是滥用active record 模式。 AR 适用于仅具有基本 CRUD 操作的简单域实体。当您开始在多个表之间添加大量验证逻辑和关系时,该模式开始崩溃。

    为了简单起见,最好的活动记录是轻微的SRP 违规。当您开始承担责任时,您就会开始受到严厉的惩罚。

    解决方案

    1 级:

    最好的选择是将业务和存储逻辑分开。大多数情况下,它是通过使用domain objectdata mappers 来完成的:

    • 域对象(在其他材料中也称为业务对象或域模型对象)处理验证和特定业务规则,并且完全不知道其中的数据如何(甚至“如果”)被存储和检索。它们还让您拥有不直接绑定到存储结构(如数据库表)的对象。

      例如:您可能有一个LiveReport 域对象,它代表当前的销售数据。但它可能在 DB 中没有特定的表。相反,它可以由多个映射器提供服务,这些映射器从 Memcache、SQL 数据库和一些外部 SOAP 中汇集数据。而LiveReport实例的逻辑与存储完全无关。

    • 数据映射器知道将来自域对象的信息放在哪里,但它们不进行任何验证或数据完整性检查。认为他们可以能够处理来自低级存储抽象的异常,例如违反UNIQUE约束。

      数据映射器也可以执行事务,但是,如果需要为多个域对象执行单个事务,您应该考虑添加工作单元(下面有更多信息)。

      在更高级/复杂的情况下,数据映射器可以交互和利用DAOs 和查询构建器。但这更多的是针对情况,当您旨在创建类似 ORM 的功能时。

      每个域对象可以有多个映射器,但每个映射器应该只适用于特定类的域对象(或一个子类,如果您的代码遵循LSP)。您还应该认识到域对象和域对象集合是两个独立的东西,应该有独立的映射器。

      此外,每个域对象都可以包含其他域对象,就像每个数据映射器可以包含其他映射器一样。但是对于映射器来说,它更多的是一个偏好问题(我非常不喜欢它)。

    另一个可以缓解当前混乱的改进是防止应用程序逻辑在表示层(通常是控制器)中泄漏。相反,您将从使用 services 中受益匪浅,它包含映射器和域对象之间的交互,从而为您的模型层创建一个 public-ish API。

    基本上,您封装模型的完整部分的服务,可以(在现实世界中 - 只需少量努力和调整)在不同的应用程序中重复使用。例如:RecognitionMailerDocumentLibrary 将所有服务。

    另外,我认为我不应该,并非所有服务都必须包含域对象和映射器。一个很好的例子是前面提到的Mailer,它可以被控制器直接使用,或者(更有可能)被另一个服务使用。

    2 级:

    如果您停止使用活动记录模式,这将成为一个非常简单的问题:您需要确保只保存那些域对象中的数据,这些数据自上次保存后实际上已经发生了变化。

    在我看来,有两种方法可以解决这个问题:

    1. Quick'n'Dirty

      如果有什么变化,那就全部更新吧……

      我更喜欢的方法是在域对象中引入一个checksum 变量,该变量包含来自所有域对象变量的哈希(当然,checksum 自身除外)。

      每次要求映射器保存域对象时,它都会在该域对象上调用isDirty() 方法,该方法会检查数据是否已更改。然后映射器可以采取相应的行动。这也可以通过一些调整用于对象图(如果它们不是很广泛,在这种情况下你可能需要重构)。

      此外,如果您的域对象实际上被映射到多个表(甚至是不同形式的存储),则为每组变量设置多个校验和可能是合理的。由于映射器已经为特定类的领域对象编写,它不会加强现有的耦合。

      对于 PHP,您可以在 this ansewer 中找到一些代码示例。

      注意:如果您的实现使用 DAO 将域对象与数据映射器隔离,则基于校验和的验证逻辑将移至 DAO。

    2. Unit of Work

      这是您问题的“行业标准”,PoEAA 书中有一整章(第 11 章)处理它。

      基本思想是,您创建一个实例,在域对象和数据映射器之间充当控制器(在经典中,而不是 MVC 意义上的)。

      每次更改或删除域对象时,都会通知工作单元。每次在域对象中加载数据时,您都会要求 工作单元 执行该任务。

      有两种方法可以告诉工作单元这些变化:

      • 调用者注册: 执行更改的对象也会通知工作单元
      • 对象注册:更改的对象(通常来自 setter)通知工作单元,它已被更改

      当与域对象的所有交互都完成后,您在工作单元上调用commit() 方法。然后它找到必要的映射器并存储所有更改的域对象。

    3 级:

    在这个复杂的阶段,唯一可行的实现是使用工作单元。它还将负责使用适当的回滚子句启动和提交 SQL 事务(如果您使用的是 SQL 数据库)。

    附言

    阅读“企业应用架构模式”一书。这是你迫切需要的。它还将纠正您通过使用类似 Rails 的框架获得的关于 MVC 和受 MVC 启发的设计模式的误解。

    【讨论】:

    • 感谢您的详细回复。我希望我们可以继续这个讨论。我已将我的回复发布给您,作为对原始帖子的更新。
    • StackOverflow 是一个问答网站,而不是论坛。另外,你完全错过了我写的内容。
    • 抱歉,我可能需要更多经验才能更好地理解您的意见。我会照你说的读这本书。也许在那之后我会有更多的基础知识。
    • PoEAA 书籍并没有真正包含 OOP 的“基础知识”。为此,我建议先观看this answer 底部列出的所有讲座。
    • 谢谢!!正是我需要的。我一直在寻找这类讲座。
    猜你喜欢
    • 1970-01-01
    • 2014-10-15
    • 2013-03-26
    • 1970-01-01
    • 2016-04-06
    • 2011-01-25
    • 2013-04-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多