企业 SPA(单页应用程序)
如何编写跨平台的业务线应用程序,而无需雇佣一大批开发人员为多个客户端重写代码?这个示例应用程序展示了一种解决方案——使用 JavaScript。
引言
TempHire 是一个参考应用程序,它从头开始设计,以满足中大型企业应用程序的需求。它演示了广泛的架构和设计模式,包括存储库模式、模型-视图-视图模型 (MVVM) 表示样式、视图组合、多屏幕导航、数据绑定、可重用 UI 组件、验证以及用于沙盒编辑和缓存管理的多个 EntityManager。
作为一个参考应用程序,我们保持 TempHire 的可管理性,并将其范围限制在能够突出我们的企业设计决策而不过多分散注意力的范围内。TempHire 演示了一个正在运行的资源管理模块,但您可以看到如何在其框架上轻松构建其他模块(计划、客户管理等)。
挑战
企业应用程序(业务线应用程序或 LOB)通常具有面向任务的 UI 和复杂、跨职能的工作流。它们需要多个视图,并且需要大量的导航。仅凭少数屏幕,屏幕组合、分解以及与复杂数据模型的绑定就可能很快变得难以控制。
同样,LOB 需要能够读取、修改和创建数据,因此确定如何将数据在 UI 和后端之间传输就成为另一个挑战。将所有原始数据转换为业务对象并将其从客户端移入移出需要大量的繁琐代码。
在传统的、相对静态的桌面客户端上将所有这些部分组合在一起已经足够困难了;但越来越多的产品需求要求这些应用程序实现跨平台,并在桌面、平板电脑和多种移动设备上运行。
如何设计和编写能够处理真实世界复杂性的 LOB 应用程序?如何构建可以在多年内轻松维护和扩展的应用程序?如何在不雇佣一大批开发人员为多个客户端重写代码的情况下实现跨平台?
解决方案
TempHire 被设计为一个完全模块化的单页应用程序 (SPA)。它使用 JavaScript 编写,因此可以在您的客户今天使用的平台以及将来仍将使用的平台上运行。
TempHire 通过利用 Durandal 等框架进行表示,以及 Breeze 等库进行数据管理来降低复杂性,从而使开发人员能够专注于解决业务问题,而不是基础设施的搭建。
更妙的是,通过使用经过验证的架构和设计模式,多位开发人员可以独立地处理特定的视图、模型和工作流,而不会影响其他模块。
TempHire 幕后
那么,企业 JavaScript 应用程序的内部到底是什么样的?TempHire 是一种实现方式——一种我们成功过并乐于与您分享的方式。
TempHire 由客户端应用程序(JavaScript、CSS、HTML 等)、域模型(实体和业务逻辑)以及各种服务器端组件(服务、控制器等)组成。
域模型
DomainModel
是所有应用程序数据的主要模型。TempHire 的域模型包含其所有实体类、一个实体基类和一个 DbContext 文件。
实体类
让我们以 AddressType 为例,了解 TempHire 的一个实体类。它是一个 Code First 类,有四个属性。
EntityBase
TempHire 使用一个所有实体(直接或间接)继承的基类(EntityBase.cs)。Code First 的好处在于 TempHire 可以将通用功能添加到基类中,这些功能将应用于域模型中的所有实体。由于这些函数位于基类中,TempHire 无需将它们添加到派生类中。一切都是可继承的。
通过 TempHire 处理并发检查的方式可以看到这一点。它位于基类中,并由每个实体继承。
DbContext
TempHireDbContext
演示了如何使用 EntityFramework Code First 将 TempHire 的域模型映射到数据库。TempHire 告诉 EF 其实体集是什么,并设置了一些初始化策略(例如 dropcreatedatabaseifmodelchanges
)。为了清楚起见,我们将 TempHire 构建为一个演示应用程序……在这种情况下,这是合理的。切勿在生产应用程序中删除数据库!
投影
TempHire 在适用时使用投影和 DTO 来提高性能,并将复杂查询移至服务器,在服务器上使用 LINQ 实现要容易得多。您可以在主详细信息屏幕上看到这一点。
您经常会看到像这样的网格由实体填充,但我们在这里不这样做。相反,此网格使用投影查询,该查询精心挑选对象图中的信息,进行压缩,然后通过网络发送。投影是提高性能的简单方法,您的通过 EDGE 网络连接的客户会很满意。
TempHire 应用
在了解了域模型之后,让我们看看应用程序本身。
App
App 文件夹包含 TempHire 客户端的核心组件:Durandal、Client Services、ViewModel 代码、HTML Views 以及 main.js,该脚本将应用程序的脚本捆绑成一个单一包。
我们假设您已经熟悉基础知识,因此这里最有趣的组件可能是 App/Durandal 和 App/ Services。
Durandal
Durandal 是 Rob Eisenberg 出色的 SPA 框架,它利用 Knockout 和 Require。Durandal 负责前端的连接工作,使屏幕组合和管理快速简便。更妙的是,Durandal 提倡的架构实践将对可伸缩性和长期维护产生积极影响。简而言之,Durandal 在架构健全的前端开发方面节省了大量时间和精力。
服务
Services 包含为 TempHire 的主要服务提供动力的 JavaScript……其中大多数围绕 TempHire 对工作单元 (UOW) 模式的依赖。
Entitymangerprovider.js 提供了一个 CreateManager 方法,供 TempHire 在需要新的 EntityManager 实例时调用——这是它频繁执行的操作,因为每个 UOW 都必须启动一个新的 EntityManager。Logger.js 负责 TempHire 的日志记录功能,Repository.js 负责 UOW 存储库的配置,Unitofwork.js 负责 UOW 本身的配置。
App_Start
App_Start 包含 BreezeWebApiConfig.cs、BundleConfig.cs 和 InfrastructureConfig.cs。这些文件在服务器启动序列的开始时运行,并注册其适用的路由。BreezeWebApiConfig 将 Breeze 客户端请求路由到 Breeze 控制器,而 InfrastructureConfig
通过 BundleConfig
帮助类注册资源包。以后可以在此处添加其他基础设施配置。
Content
TempHire 的所有内容文件(CSS 和图像)都存储在命名适当的 Content 文件夹中。现在是时候提到 TempHire 使用 Twitter Bootstrap 了,这是一个快速构建现代前端的出色模板。
HTML、CSS、UI 元素、响应式设计——是的,Bootstrap 负责所有这些。
控制器
DefaultController.cs
默认控制器负责提供通用元数据。查询和保存逻辑路由到相应的模块控制器。
LookupBundle.cs
LookupBundle 是一个 DTO,用于一次性将全局只读实体(如查找数据)传输到客户端。客户端在开始时请求一次此数据,并将其保存在全局缓存中。每个 EntityManager
在首次创建时都预先填充了此全局查找数据。
ResourceMgtController.cs
ResourcMgtController
定义了资源管理模块的查询和保存端点。每个模块都有自己的控制器,以保持其大小的可管理性,并将相关功能集中在一个地方。
与我们在客户端所做的类似,服务器端也使用工作单元模式来构建查询和保存逻辑,并将其保留在控制器外部。这样,控制器就可以保持足够小,业务逻辑则封装在相应的服务器端工作单元中。
脚本
TempHire 由八个 JavaScript 库提供支持。如果您不熟悉 JavaScript,并且看到这么多库感到担忧,请停止阅读本文,然后阅读 John Papa 的 为什么需要这么多 JavaScript 库。现在感觉好些了吗?使 TempHire 正常运行的库包括:
Bootstrap 通过各种小部件、过渡、按钮等为前端增添光彩。不用说,它们都能与 Twitter Bootstrap CSS(参见 Content)无缝协作。各种 GUI 元素在 twitter.github.io 上有文档。
Breeze 在数据管理方面表现出色,并负责模型——MVVM 中的 M。Breeze 查询、保存并管理客户端与服务器之间的所有数据交互。Breeze EntityManagers 使编写 TempHire 的工作单元模式变得更加容易。
Breeze 会自动创建与远程服务返回的数据形状匹配的 JavaScript 模型对象(实体)。它添加了业务规则和基础设施,支持验证、更改跟踪以及导航到相关实体。Breeze 的导航属性可以自动遍历关系模型中隐式存在的对象图,因此您可以从客户走到订单,从订单走到行项目。Breeze 会跟踪用户的更改,并根据规则进行验证,其中一些规则可能已从服务器传播到客户端。
如果您将数据存储在数据库中,以复杂对象图的形式查询和保存数据,并在多个视图之间共享对象图——并且希望以 JavaScript 的方式实现这一点——那么 Breeze 是最佳选择。
jQuery 是 TempHire 的一些库和模板的依赖项。Bootstrap、Breeze、Durandal 和 Sammy 都依赖于 jQuery 的某个部分。
Knockout 管理表示层——ViewModel——MVVM 中的 VM 的数据绑定和依赖项跟踪。更妙的是,它通过 Durandal 框架与 Require 和 Sammy 紧密集成,大大简化了许多前端连接工作。
Moment 是我们在 JavaScript 中处理日期和时间(解析、验证、操作和格式化)的首选库。
q 通过 CommonJs Promise 帮助管理异步操作。
Sammy 是一个小型但功能强大的框架,用于构建像 TempHire 这样的 SPA。(Sammy 将它们称为单页 AJAX 应用程序,但 SPAJAX 有点拗口。)TempHire 主要使用 Sammy 进行导航和路由功能。
Toastr 在“吐司”窗口中显示进程和错误通知,这些窗口从右下角弹出,让您随时了解 TempHire 的操作。
服务
TempHire 的所有持久化无关性都内置于服务器端服务中,使用了工作单元 (UOW) 模式。
工作单元
UOW 是一种设计模式,封装了从简单任务到复杂工作流的任何内容。UOW 可以是短暂的或长期的,可能涉及检索数据、修改数据、创建新数据、执行业务逻辑、检查业务规则以及保存或回滚更改。
每个 UOW 会启动一个 EntityManger
,负责配置、身份验证和连接详细信息,而 UOW 的 Repository 则负责管理特定类型实体访问的业务逻辑。
UOW 使得能够高度优化给定实体类型的获取策略。
例如:一个用于 Color 等静态类型的 Repository 可以优化为在第一次请求时加载所有颜色,并在后续请求中直接从缓存提供服务,而不是进行额外的服务器往返。
如果不同 UI 部分处理相同的数据,UOW 可以由 ViewModel 共享,但每个 UOW 仍然是一个独立的沙盒。
TempHire 使用 UOW 作为事务边界——每个 UOW 都具有定制的职责,从而形成一个有组织的、更易于维护和改进的代码库。
后端呢?
TempHire 是一个 ASP.NET Web 应用程序。服务器托管所有客户端资产以及一个 ASP.NET MVC4 Web API 服务,该服务在 Entity Framework Code First 模型的支持下查询和保存到 SQL Server 数据库。我们使用 ASP.NET 是因为它对于熟悉 Microsoft 技术栈的企业开发人员来说既快速又有效。Breeze 提供了简化 Web API 和 Entity Framework 后端开发的组件,这也是非常有用的。
如果您更喜欢 Rails 或 Java 后端,或者 NoSQL 数据库,也没关系。TempHire 是一个 JavaScript 应用程序,几乎可以配置为在任何能够提供 Web 资产和数据服务的服务器堆栈上运行。
了解更多
除了 TempHire 示例,您还可以在 www.breezejs.com 找到有关构建 SPA 的更多信息,包括:
需要更多帮助?
我们将与您的团队合作,构建一个 定制培训课程,该课程将根据您团队和项目的需求量身定制。
关于
Breeze 由 IdeaBlade actively 开发。在 Twitter 上关注 @BreezeJS,并在 Facebook 上 点赞我们。