【问题标题】:Breeze suitability in a SPA Architecture QuestionsSPA 架构问题中的微风适用性
【发布时间】:2013-11-02 02:12:29
【问题描述】:

请坚持我,因为这个问题可能很长!我们目前正在为我工​​作的基于 Web 的应用程序进行架构和原型设计。这是一个包含大量错综复杂和大量数据的大型应用程序。

在前端技术方面,我们将使用 Durandal、Knockout 和 TypeScript(编写我们所有的 JavaScript 和 jQuery 代码)构建一个 SPA。

后端经过了非常仔细的考虑和架构。简而言之,我们将拥有一套应用程序服务(使用 nHibernate、AutoFac 等),它们将使用精心设计的域模型。然后,WebAPI 将使用应用程序服务中的方法将数据呈现回前端。

在构建具有大量数据库交互的 SPA 时,一个明显的想法是考虑 Breeze。 Breeze 出色的原因有很多。问题是,我们不确定它是否适合我们的架构。除了一些简单的原型之外,我们没有人使用过 Breeze,所以如果有人可以帮助回答以下问题,我们将非常感激!

1 - 我们假设默认使用 Breeze 会绕过我们的业务逻辑并直接进入 nHibernate 或 DB 是否正确?如果这个假设是正确的,有没有办法绕过它?任何建议/链接?我们的想法是,我们必须为 Breeze 编写一个适配器来路由到我们的业务逻辑并将元数据提供回 Breeze。然后这会带来其他问题,例如在系统的不同部分拥有部分和完全水合的模型,投影外键。这对我们来说是一个相当有争议的问题!

2 - 使用 TypeScript 的众多原因之一是通过智能感知为我们提供静态类型对象。我们是否正确假设如果我们使用 Breeze,我们就不会开箱即用?

3 - 我们根本不想要任何缓存。是否可以完全关闭微风的这一部分?

4 - 如果我们不使用查询功能(我们肯定不会,我们有一个搜索计划,这意味着不能同时使用 Breeze 查询功能),我们不会使用缓存功能并且我们可以放置一些东西(例如 T4 模板以从 DTO 自动生成 TypeScript 对象)以帮助 Breeze 允许的开发速度和减少代码,我们是否否定了使用 Breeze 的意义?

我做了很多搜索,但我只能找到为什么应该使用 Breeze。虽然我承认它很棒,但根据我目前的理解,我只是不确定它是否适合我们的应用程序。任何建议或推荐阅读将不胜感激。由于 Breeze 在现场相当新,我很难找到很多信息!

【问题讨论】:

    标签: c# architecture breeze single-page-application


    【解决方案1】:

    1) 您可以拦截任何查询或保存在服务器上的 Breeze 中。对于保存,这是通过 ContextProvider 上的 BeforeSaveEntities 和 BeforeSaveEntity 可覆盖方法完成的。对于查询,只需通过服务器端端点方法中的自定义代码即可完成。这两种技术都在 Breeze 文档中进行了描述,并且 Breeze zip 中的 DocCode 示例中有示例。

    2) 我们有客户使用 Breeze 和 Typescript,并使用 Breeze 元数据生成 Typescript 类定义。我们计划在以后制作此类代码的一个版本以供一般使用。 (我们目前为 Typescript 相关任务提供咨询支持。请通过微风@ideablade.com 与我们联系。)

    3) 我们计划在一个月内为 Breeze 的下一个版本提供 noTracking 选项。目前,只需执行“投影”查询即可重现相同的效果。

    4) 在 Breeze 中使用您自己的查询系统没有问题。请参阅 Breeze 文档中对“namedQuery”的讨论。如果您想通过额外的客户端过滤来增强基本查询,您甚至可以混合和匹配。

    更重要的一点。如果您不打算使用任何缓存或任何微风查询增强功能,那么微风可能很适合。

    但是,缓存的价值是您真正需要评估的。如果您打算这样做,您可能需要自己重新创建此功能

    1. 以断开连接的方式运行您的应用程序。这包括本地存储的序列化和反序列化。

    2. 减少客户端和服务器之间已检索数据的往返次数。随着应用变得越来越大,这可能会显着提升应用的性能。

    3. 能够查询当前机器内存中的内容。

    4. 在单独检索时自动连接彼此相关的实体图。

    5. 更改跟踪和将实体恢复到其最初查询状态的能力。

    还有其他几个问题,我有点懒得一一列举。

    希望这会有所帮助!

    【讨论】:

    • 感谢周杰伦的回答,非常感谢。我认为我们真正需要在这里回答的问题是关于缓存的。我们正在考虑单独的移动版本,我认为在这种情况下我们可能会使用 Breeze。我会将您的所有观点提交给团队进行进一步讨论。再次感谢!
    【解决方案2】:

    我同意 Jay 的回答,并将继续努力。 Jay 没有提及,但现在已经支持 NHibernate。

    我对跳过 IQueryable 支持的任何人都没有意见。 Breeze对此很酷。您仍然可以使用轻量级的 EntityQuery 来访问任何 GET 端点并根据需要向其传递参数;那是在您“进阶”并开始调整 ajax 适配器之前。

    或者您可以使用任何您喜欢的 AJAX 设备来检索/保存数据并将微风融入您的流程。在即将发布的版本中,无论您如何获取这些数据,将 JSON 数据作为实体合并到缓存中都会变得更加容易。

    许多人会选择一种混合方法,在这种方法中,他们使用香草微风来处理简单的东西,比如倾向于主导 API 的参考列表。但对于需要特殊处理的关键“20%”API,他们会改用自定义方法。

    我很想知道为什么您根本不想使用缓存。我明白为什么您可能不希望它用于 一些 查询。但是从来没有

    您在收到客户端数据的那一刻就意识到客户端数据与服务器不同步。这真的只是时间问题。缓存会延长数据逐渐过时的时间。但它在离开服务器的那一刻就已经腐烂了。

    缓存不是永远的。您可以随时刷新缓存中的单个实体。您可以随时清除缓存。并且根据数据的波动性或响应来自服务器的通知(您可能对 SignalR 执行的边带操作),尽可能多地执行这两项操作。

    缓存中的完整实体对于体验 Breeze 的许多好处确实是必不可少的:

    • 在多个视图之间轻松共享公共数据
    • 跨视图公开实体数据的当前状态(包括验证错误消息)。
    • 跟踪脏状态(EntityState.Modified
    • 属性更改时自动验证
    • 导航到相关实体:order.lineitem[0].product.name
    • 缩小查询负载,这要归功于自组装实体图(即,当 Breeze 收到查询的产品的数据时,它会自动将导航属性构建到产品的相关参考实体,例如 shippermanufacturerproductTypeproductPricing ...假设这些都在缓存中。
    • 恢复挂起的更改
    • 查询缓存
    • 在本地存储更改并与保留的更改状态一起恢复。

    您可以根据需要拥有任意数量的不同缓存,每个缓存都有自己的生命周期。

    记住……控制着缓存。

    那么你对缓存有什么看法?

    【讨论】:

    • 感谢您提供所有这些信息。这一切真的很有帮助。我们不使用缓存的原因是我们呈现的数据需要始终尽可能新鲜。正如你所说,它从离开服务器的那一刻起就变得陈旧,但这是我们可以允许的那样陈旧。每次加载视图时,数据都需要从服务器更新。我们很确定在某些情况下我们会使用 SignalR 来确保某些数据始终是完全新鲜的。
    • 您描述的是一个常见且重要的用例。请参阅我的更新答案。
    【解决方案3】:

    保持新的缓存

    您在 10 月 31 日的评论提醒我,您可以保持资源新鲜并使用缓存。这不是“非此即彼”!

    只需执行以下操作:

    1. 始终在使用该资源之前重新查询它(例如,当您的 ViewModel 加载视图时)
    2. 在发出该查询之前清除该资源类型的所有实例的缓存。

    您可以将这两种想法都封装在您的datacontext.getXXX 方法中。这就是我的意思:

    // 清除发票缓存后获取新发票,可选择过滤 函数getInvoices(可选谓词){ clearCachedInvoices(); var 查询 = 微风.EntityQuery.from('Invoices'); if (optionalPredicate) { query = query.where(predicate); } 返回 manager.executeQuery(query).then(_logSuccess, _queryFailed); } // 刷新特定发票 功能刷新发票(发票){ // Todo: 添加参数错误检查? var 查询 = 微风.EntityQuery.fromEntities([invoice]); 返回 manager.executeQuery(query).then(success, _queryFailed); 功能成功(数据){ _logSuccess(数据); 返回数据.结果[0]; // 查询返回数组;来电者想要第一个。 } } 函数 clearCachedInvoices() { var cachedInvoices = manager.getEntities('Invoice'); // 缓存中的所有发票 // Todo: 这应该是 Breeze EntityManager 本身的一个函数 cachedInvoices.forEach(function (entity) { manager.detachEntity(entity); }); }

    关于缓存清除的重要注意事项

    我首先清除缓存的原因是另一个用户可能已经删除了您之前检索到的一些发票。我假设您希望将它们从您的缓存中删除,以便用户只能看到实时发票。

    如果无法删除发票(例如,您通过将发票标记为“无效”来进行“软删除”),则不需要此缓存清除步骤(以及随之而来的问题)。我个人对删除非常警惕,因为它们会导致各种问题。我更喜欢软删除。

    您需要确保 UI 没有保留以前的发票实体。你已经要求经理重新开始。这意味着每个现有的发票实体引用都指的是一个分离的实体。查询后,每个缓存的发票都是一个新实例。

    清除缓存的另一个危险是它会清除所有待处理的发票更改。如果您可能有未保存的发票更改(新的、更新的或计划的删除),您不希望运行此方法。您可能希望添加保护逻辑以防止丢失未保存的更改。该逻辑究竟是什么将是特定于应用程序的。这可能会涉及到对manager.hasChanges('Invoice') 的调用。

    如果您始终刷新与发票有关的所有内容,您不必担心丢失参考资料。

    这些约束应该很容易满足。它们与您最初所说的非常一致:您真的不想缓存。所以这应该是小菜一碟......只需使用上面显示的代码,您就可以轻松获得缓存的好处。

    啊......但我不禁想到真正想要刷新实体对象的人而不是完全替换它们。也许她想在用户有未保存的更改时刷新。然而,她想删除已被其他用户删除的实体。

    嗯,我也有她的食谱。

    功能 refreshAllInvoices(已删除){ // 移除的是调用者的数组,应该填充实体 // 我们从缓存中删除;它填充在下面的成功方法中。 var cached = manager.getEntities('Invoice'); // 获取缓存中的所有发票 return 微风.EntityQuery.from('Invoices') .using(manager).execute(成功,_queryFailed); 函数成功(){ 删除.长度 = 0; //清空数组 var 结果 = data.results; // 查询结果 // 从“缓存”数组中删除每个结果 results.forEach(函数(实体){ var ix = cached.indexof(entity); if (ix > -1) { 缓存[ix] = null; } }); // 剩下的必须在服务器上删除 // 或者是一个我们还没有保存的新实体 // 循环遍历,分离我们认为已经被删除的那些。 cached.forEach(函数(实体){ 如果(实体!== null && !entity.entityAspect.entityState.isAdded()) { 移除.push(实体); // 让调用者知道这个 manager.detachEntity(实体); } }); 返回结果; } }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-17
      • 2021-02-10
      • 2013-10-30
      • 2013-05-23
      • 1970-01-01
      • 2020-03-27
      • 1970-01-01
      相关资源
      最近更新 更多