Run*_*ing 4 .net architecture distributed data-driven crud
上下文:在.NET平台上构建智能客户端应用程序,其中包含涉及大量列的复杂数据库模型.自然的应用程序风格是典型的数据驱动CRUD.在某些情况下,还有一些服务器端逻辑,以及一些复杂的验证.您可以完全控制客户端和服务器,因此对互操作性的需求至少是必需的.
这个问题有很多细节,为此道歉,但这是因为我想为答案设置适当的背景.
还有一些假设
- 在微软世界中并不罕见,大多数以前的应用程序都是用DataSet编写的,因此它是所涉及开发人员最熟悉的技术.但是,让我们说开发人员也非常精通OO思维.
- 您需要在客户端和服务器上运行验证.
- 您不以表格形式显示大多数数据.
- 这不是内联网应用程序,因此您不能过多地考虑带宽
最大的问题:数据集还是对象?
如果你选择数据集,你会有一些积极的和消极的
- 在积极方面:你从数据库中获取数据,通过网络获取数据并通过网络返回更改的数据方面获得了一些Microsoft支持较小的块 - 因为您只能指定发送更改.发送较少的数据是好的,因为可能涉及相当多的数据.
- 否定的是:在验证,业务逻辑等方面,你得到了一个程序化的代码形式,你没有得到面向对象代码的好处 - 行为和数据在一起,更自然的工作和思考方式你正在做什么,可能更接近验证逻辑.您还可以放弃将数据集放在网格中的好处,因为这不是常见的用例.
如果你去寻找对象,那就是同样的练习,但还有更多的选择:
肯定:行为和数据在一起.验证逻辑更接近.更容易查看和理解对象之间的关系.更易读的代码.更容易进行单元测试.但是,您还需要做很多选择和工作:
OR/Mapping
- 从关系模型获取数据到对象.OR映射器并不复杂,并且能够很好地处理它.但它增加了开发时间.
合同映射
- 将数据从服务器端对象映射到合同对象(通常是DTO)通常是一种很好的做法.由于这是一个非常适合CRUD风格架构的应用程序,因此DTO并没有真正为图片增加太多价值,只是映射工作.
共享代码
- 您可以使用共享代码方案,其中包含域数据和逻辑的程序集在客户端和服务器端都可用.这是紧密耦合,但当你有一个自然紧密耦合的客户端 - 服务器应用程序时,它并不一定是坏的.
无论您选择是否添加合同层,您都必须通过线路发送大型对象结构因为我们控制客户端和服务器,所以传输和编码应该是TCP上的二进制编码.那会有所帮助.使用数据集,您可以选择仅发送更改.前后发送整个对象结构可能是性能问题.发送整个对象结构的选项是以某种方式识别所涉及的更改(创建,更新,删除),并仅发送有关该更改的信息.理论上,将聚合根ID发送到服务器以及更改并不太难,要求服务器延迟加载聚合根,执行所做的更改,然后再次保存.但涉及的最大复杂性是确定所做的改变.你有没有采用这种方法?为什么?你究竟是怎么做到的?
演示文稿
确切的UI技术对于这个问题并不是那么重要,WinForms,Silverlight或WPF是可能的.让我们假设我们正在使用WPF,因为它是一个新的智能客户端.这意味着我们有两种方式绑定,可以正确使用MVVM.
绑定到用户界面的对象需要实现INotifyPropertyChanged并在每次更新属性时引发事件.你是如何解决这个问题的?如果您选择共享代码方案,则可以将其添加到域对象中,但这将涉及在服务器端添加从未在那里使用的代码和逻辑.如果你去合同对象,分离是更自然的,但是添加一层映射并不是很多增值.
技术
有一些技术可以帮助解决一些问题,但这通常会使其他问题复杂化.你是使用它们,还是自己从头开始构建东西?
**
- CSLA是可能的,但它使单元测试更加困难,并且似乎为数据访问增加了更紧密的耦合.它确实对许多问题有所帮助,但我个人对这项技术没有任何能力,所以它是否适合它是有点难以说的.
- WCF RIA服务可用于Silverlight解决方案,但肯定存在限制.数据大小是一个.
- WCF数据服务是另一种快速获取内容的方法,但REST没有太大帮助,而且您还缺乏RIA服务中的验证支持.
总结
如果你已经走到这一步,我希望你能够了解我的目标.我试图将其缩小以避免一次性讨论所有内容,但分布式开发很复杂,因此您必须考虑许多部分.
更新
谢谢你们的回应!我试图提出足够开放的问题以获得不同的答案,但具体到足以处理一些不常见的要求.
有不同的考虑因素有不同的优点和缺点,并且因系统而异.每种方法通常都会增加寻找解决方案的复杂性.这个问题的一个要点是特别提出一些额外的要求,而这些要求并不是直接适合今天通常是正确的答案 - 基于任务的UI.如果你愿意,我不是一个"CRUD-guy".但是由于各种原因(通常是遗留的),一些系统非常适合CRUD.
许多商业应用程序都有类似的需求,可以引导不同的方
业务相关
- 查看:向用户显示数据并更新相同的数据(读取和CUD - 创建,更新,删除)
- 验证:业务规则
UI相关
- 验证:UI规则
- UI更新:特定于仅使UI更新对象更改的代码(INotifyPropertyChanged)
网络相关
- 数据大小:通过网络发送的数据量
DB相关
- 延迟加载
SRP /重用相关
- 映射:由多层对象/分离问题引起
维护/更改相关
- 更改:添加新信息(列/字段)
- 代码量
- 重用和"更改原因"
技术限制
- 变更跟踪
但这些只是一些非常具体的.您始终需要知道哪些"能力"最重要,因此您需要多大程度的可扩展性,可用性,可扩展性,互操作性,可用性,可维护性和可测试性.
如果我想在大多数情况下尝试概括,我会说:
客户端
- 使用MVVM实现分离和可测试性
- 在DTO之上创建VM - 在VM中
实现INotifyPropertyChanged.
- 使用XamlPowerToys,Postsharp或其他一些帮助这一点的方法是值得的
- 在UI中单独读取和CUD
- 使CUD基于任务,并使用命令或类似命令将这些操作发送到服务器端
服务器
- 为每个屏幕定制一个dto
- 或者使用Ayende在http://msdn.microsoft.com/en-us/magazine/ff796225.aspx中描述的多查询方法
- 使用自动化来避免繁琐,手动和与您尝试解决的问题完全无关,该映射是
- 让域模型主要关注业务操作,包括与CUD相关的操作,而不是读取
- 避免可重用性增加了更改原因的数量
-避免封装问题
- (通过它可以启用CQRS样式架构,并可能及时分离读取和CUD的缩放)
- 尝试找到一种与应该做的事情完全吻合的验证方法(好读:http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/02/15/validation-in-a-ddd-world.aspx)
这是否是我在这种特殊情况下采取的方法?
嗯,这就是我想开始讨论:)但似乎比我希望的更难(除了你们两个).
小智 8
我只能回答我们自己的经验.我们尝试了不同的框架(WCF RIA,Ideblade)并得出结论,框架只会让事情变得更糟.我将进一步解释.
首先你应该忘记CRUD.只有演示应用程序有CRUD - 真实世界的应用程序有行为.
我不建议在客户端模仿整个实体图.他们是两个分开的问题.
您应该为每个上下文创建量身定制的Dto.例如,假设您有一个OrderSearchView,那么您可以创建一个OrderSearchDto并仅映射您需要的字段.在EditOrderView中,您将使用EditOrderDto - 它只包含您需要的字段.
我真的不建议在实体和dto之间使用自动化工具.因为dto和实体之间通常没有一对一的关系.dto通常由不同的多个后端实体构建.无论如何,映射是如此简单,所以我没有看到映射框架的重点.并且工作不是映射 - 它正在编写单元测试 - 无论如何你都必须这样做(有或没有映射框架).
Dtos应该对客户端技术不了解.在dto上实现INotifyPropertyChanged打破了单一责任原则.他们称之为数据传输对象.而是在客户端创建Presenters.您创建一个EditOrderPresenter,它是EditOrderDto的包装器.所以dto只是EditOrderPresenter中的私有成员字段.Presenter是为在客户端层编辑而定制的 - 因此它通常会实现INotifyPropertyChanged.EditOrderPresenter通常具有与dto相同的属性名称.
您应该将客户端验证与服务器端的实体验证分开.小心分享!我认为客户端验证只是GUI调整 - 使gui体验更好.不要在dto和实体之间共享验证代码 - 这可能会导致更多的麻烦而不是有用.无论在客户端进行何种验证,只需确保始终在服务器端进行验证.有两种验证:简单属性验证和整体实体验证(dto也是如此).实体验证应仅在状态转换时执行.查看Jimmy Nilssons领域驱动设计的背景知识.我不建议使用验证规则引擎 - 只需使用状态模式.
那么更新,插入,删除呢?在我们的实现中,我们使用WCF,而WCF API只有一个方法:IResponse [] Process(params IRequest [] requests); 这究竟意味着什么?这意味着客户端向服务器发出一批请求.在服务器端,您为系统中定义的每个请求实现RequestHandler.然后返回响应列表.确保Process()方法是一个工作单元(〜一个事务).这意味着,如果批处理中的请求失败 - 所有这些请求都将失败 - 这将导致事务回滚 - 并且不会对数据库造成任何损害.(不要在responsehandler中使用错误代码 - 而是使用强制转换异常.)
我建议您查看Agatha消息服务器.Davy Brion有很多关于消息传递层的博客文章.在我们公司,我们选择实现自己的消息服务器 - 因为我们不需要Agatha提供的所有内容,我们进行了一些语法改进.无论如何,实现消息服务器并不是很困难 - 这是一个很好的学习体验.链接http://davybrion.com/blog/
那么你对Dto做什么呢?好吧,你永远不会更新它们,但你在客户端更改它们以获得对gui的正确反馈.因此,您让演示者以正确的顺序跟踪发生在dto(reqest)的所有事情.这将是您的requestBatch.然后将请求标记发送到WCF上的process-command - 然后请求将在服务器端"重放"并由请求处理程序处理.这实际上意味着你永远不会更新dto的.但主持人可能会在客户端编辑dto,以便提供适当的gui反馈.演示者的工作也是跟踪所做的所有编辑,以便将它们作为请求批发送回服务器(按照与编辑时相同的顺序重新发送).考虑以下场景,您检索现有订单,编辑,然后将更改提交回db.这将导致两个批次,一个用于获取订单,一个用于提交更改.
RequestBatch 1:GetOrderByIdRequest
(..然后用户编辑数据..)
ReqeuestBatch 2:
StartEditOrderRequest,状态更改为编辑模式,轻松验证
AddConsigneeToOrderRequest
ChangeEarliestETDOnOrderRequest,无需再次验证最新的ETD!
DeleteOrderlineRequest
ChangeNumberOfUnitsOnOrderlineRequest
EndEditOrderRequest,状态更改为原始状态,在此执行实体验证!
GetOrderByIdRequest,以便用最新的更改更新gui.
在serveide上我们使用NHibernate.Nhibernate使用第一级缓存来避免繁重的数据库负载.因此,同一工作单元(requestbatch)中的所有请求都将使用缓存.
每个请求应该只包含执行该作业的最小数据量.这意味着使用OrderId +一些其他属性而不是整个dto.关于乐观更新,您可以发送一些oldValues以及请求 - 这称为并发集.请记住,并发集通常不包含很多字段.因为在此期间更改已更改的订单并不一定意味着您将提高条件.例如.添加和订购,而在此期间,另一个用户编辑收货人并不意味着你有加薪条件.
嗯,这不会导致大量的工作.你肯定会有更多的课程,但每个课程都很小,只有一个责任.
顺便说一句,我们在中型项目中尝试了WCF RIA服务.并没有那么顺利.我们必须找到围绕框架的方法(黑客)来做我们想要的事情.它也基于代码生成 - 这对构建服务器来说非常糟糕.此外,您永远不应该通过图层进行查看.您应该能够在不影响客户端层的情况下更改支持的实体.有了RIA,这很难.我认为OData与WCF RIA属于同一类别.
如果您需要在客户端构建查询,则使用规范模式 - 不要使用iqueryable - 那么您将独立于后端实体.
祝好运.
twitter:@lroal
有趣的问题:)
如果您从以下几条原则开始:
基于此我会:
在我们的项目中,与企业库和数据集相比,使用实体框架节省了时间。
在服务器端和客户端对象上,您可以尝试: