Tho*_* Vo 2 forms model-view-controller tree dependencies attributes
我已经在Yii框架上开发了一段时间(4个月),到目前为止,我遇到了一些MVC问题,我想与经验丰富的开发人员分享.我将通过列出它们的复杂程度来介绍这些问题.
[等级1] CR(创建更新)表格.首先,我们有很多形式.每个表单本身都是一个模型,因此每个表单都有一些验证规则,一些属性和一些要对属性执行的操作.在很多情况下,这些表单中的每一个都使用单个活动记录对象在数据库中更新和创建记录.
- >因此,在这种复杂程度上,表格必须如此
打开时,
能够以人性化的方式显示db中的db友好数据
能够显示具有活动记录对象属性的所有表单字段.添加,删除,更改db表中的列必须影响表单的显示.
保存时,能够在获取数据之前将人性化数据格式化为数据库友好数据
在验证时,能够执行由活动记录对象强制执行的基本验证,它还必须执行其他验证以满足某些业务规则.
验证失败时,能够回滚对属性所做的更改以及对数据库所做的更改,并向用户显示其最初输入的数据.
[等级2] 扩展CR表格.一种可以同时从不同表执行记录创建/更新的表单.不仅如此,表单是否会创建/更新其中一个记录有时可能取决于其他条件(更多业务规则),因此表单有时可以更新表A,B但不是D的记录,有时更新A处的记录,D但不是B - >所以在这种复杂程度,我们看到一个表格必须:
能够满足[等级1]
能够有条件地创建/更新某些记录,有条件地创建/更新某些记录的某些列.
[等级3] 模型树.在许多方面,表单在应用程序中的作用是允许用户与您的应用程序交互的端口.为了满足请求,该端口将与许多其他对象进行交互,这些对象又与更多对象进行交互.其中一些对象可以看作是模型.Active Record是一个模型,但是Mailer也可以是模型,因此是RobotArm.这些模型彼此使用以满足用户的请求.每个模型都可以执行自己的操作,整个树必须能够回滚在出错或失败的情况下所做的任何更改.
有没有人遇到或能够解决这些问题?
我想出了很多东西,比如在ModelAttribute对象中封装模型属性,以解决它们在客户端,服务器和数据库层中的存在问题.
我还认为我们应该给模型树提供一个Observer来观察并通知观察到的模型在发生错误时回滚更改.但是如果多个观察者可以存在,如果一个节点使用其父节点的观察者但给其子节点另一个观察者,该怎么办呢?
工程师,开发人员,Rails,Yii,Zend,ASP,JavaEE,任何MVC人员,请加入这个讨论,以便科学.
- 更新teresko的回复: ---
@teresko我实际上打算将服务合并到一个工作单元内的执行中,让工作单元不用担心新的/更新/删除.工作单元内的每个对象都将负责其状态,并且需要实现自己的commit()和rollback().一旦发生错误,工作单元将回滚从最新注册对象到最旧注册对象的所有更改,因为我们不仅处理数据库,还可以使用邮件程序,发布者等.否则,树将成功执行,我们从最旧的注册对象调用commit()到最新的注册对象.这样邮件程序可以保存邮件并在提交时发送.
使用数据映射器是一个好主意,但我们仍然必须确保数据库中的列与数据映射器和域对象匹配.此外,扩展CR表单或具有依赖于其他模型的属性的模型必须在验证和数据类型方面匹配它们的属性.那么也许一个属性可以是一个对象并从一个模型运送到另一个模型?属性还可以判断它是否已被修改,应该对其执行什么验证,以及它如何对人类友好,应用程序友好和数据库友好.对数据库模式的任何更新都将影响此属性,从而抛出异常,要求开发人员对系统进行更改以满足此更改.
ter*_*ško 16
您的问题的根源是滥用活动记录模式.AR适用于仅具有基本CRUD操作的简单域实体.当您开始在多个表之间添加大量验证逻辑和关系时,模式开始分崩离析.
为简单起见,最佳活动记录是轻微的SRP违规.当你开始积累责任时,你开始受到严厉的惩罚.
最佳选择是将业务和存储逻辑分开.大多数情况下,它是通过使用域对象和数据映射器完成的:
域对象(在其他材料中也称为业务对象或域模型对象)处理验证和特定业务规则,并且完全不知道,如何(或甚至"如果")存储和检索它们中的数据.它们还允许您拥有不直接绑定到存储结构(如DB表)的对象.
例如:您可能有一个LiveReport域对象,它表示当前的销售数据.但它可能在DB中没有特定的表.相反,它可以由几个映射器提供服务,这些映射器汇集来自Memcache,SQL数据库和一些外部SOAP的数据.和LiveReport实例的逻辑是完全无关的存储.
数据映射器知道从域对象放置信息的位置,但它们不进行任何验证或数据完整性检查.认为他们可以能够处理从低级别的存储抽象锥,像违反异常UNIQUE约束.
数据映射器也可以执行事务,但是,如果需要为多个域对象执行单个事务,则应该寻找添加工作单元(更多关于它的更低).
在更高级/更复杂的情况下,数据映射器可以交互并使用DAO和查询构建器.但是,当您的目标是创建类似ORM的功能时,这更适用于这种情况.
每个域对象可以有多个映射器,但每个映射器只能用于特定类的域对象(或者如果您的代码遵循LSP,则为子类的一个).您还应该认识到域对象和域对象的集合是两个独立的东西,应该有单独的映射器.
此外,每个域对象都可以包含其他域对象,就像每个数据映射器可以包含其他映射器一样.但是对于地图制作者来说,这更像是一种偏好(我非常不喜欢它).
可以缓解当前混乱的另一个改进是防止应用程序逻辑泄漏到表示层(最常见的是 - 控制器).相反,您将在很大程度上受益于使用包含映射器和域对象之间交互的服务,从而为您的模型层创建公共 API.
基本上,您封装了模型的完整部分的服务,可以(在现实世界中 - 通过微小的努力和调整)在不同的应用程序中重用.例如:Recognition,Mailer或DocumentLibrary将所有的服务.
此外,我认为我不应该,并非所有服务都必须包含域对象和映射器.一个很好的例子是前面提到的Mailer,可以直接由控制器使用,或者(更有可能)由另一个服务使用.
如果您停止使用活动记录模式,这将成为一个非常简单的问题:您需要确保只保存自上次保存以来实际已更改的域对象中的数据.
在我看来,有两种方法可以解决这个问题:
Quick'n'Dirty
如果发生了变化,只需将其全部更新......
我喜欢的方式是checksum在域对象中引入一个变量,该变量包含来自所有域对象变量的哈希值(当然,除了checksum它自身).
每次要求映射器保存域对象时,它都会调用isDirty()此域对象上的方法,该方法检查数据是否已更改.然后mapper可以采取相应的行动.通过一些调整,这也可用于对象图(如果它们不是广泛的,在这种情况下,您可能还需要重构).
此外,如果您的域对象实际上映射到多个表(甚至不同的存储形式),则为每组变量设置多个校验和可能是合理的.由于映射器已经为特定的域对象类编写,因此不会加强现有的耦合.
对于PHP,您将在本文中找到一些代码示例.
注意:如果您的实现使用DAO将域对象与数据映射器隔离,那么基于校验和的验证逻辑将被移动到DAO.
这是您的问题的"行业标准",在PoEAA书中有一整章(第11章)处理它.
基本思路是这样,你创建一个实例,它就像你的域对象和数据映射器之间的控制器(在经典中,而不是在MVC意义上).
每次更改或删除域对象时,都会通知工作单元.每次在域对象中加载数据时,都要求工作单元执行该任务.
有两种方法可以告诉工作单位有关更改的信息:
完成与域对象的所有交互后,您可以commit()在工作单元上调用方法.然后它找到必要的映射器并存储所有已更改的域对象.
在复杂的这个阶段,唯一可行的实施是使用工作单元.它还负责启动和提交SQL事务(如果您使用的是SQL数据库),并使用相应的回滚子句.
阅读"企业应用程序架构模式"一书.这是你迫切需要的.它还可以纠正您对MVC和MVC启发的设计模式的误解,您通过使用类似Rails的框架获得了这些误解.