N 层架构和技巧
本文旨在从各个方面阐明N层架构中的许多基本概念,并提供一些实用的技巧。
目录
概述
N层架构是一种行业证明的软件架构模型,适用于通过解决可伸缩性、安全性、容错性等问题来支持企业级客户端/服务器应用程序。.NET拥有许多工具和功能,但.NET没有预定义的指导方法来实现N层架构。因此,为了在.NET中实现良好的N层架构设计和实现,充分理解其概念非常重要。然而,我们中的许多人可能已经听过、读过或使用了多年的N层架构,但仍然或多或少地误解其概念。本文旨在从各个方面阐明N层架构中的许多基本概念,并提供一些实用的技巧。本文中的技巧基于一个团队完全控制N层架构所有层的假设。我们还有另一篇文章来详细介绍.NET中的N层架构示例:使用ASP.NET MVC3、WCF和Entity Framework的N层架构示例。
N层架构简介
一些术语的差异和关系
层(Tier)和逻辑层(Layer)
首先,我们需要弄清楚N层架构中两个术语的区别:层(Tier)和逻辑层(Layer)。层(Tier)通常指物理部署的计算机。通常,单个运行的服务器就是一个层。几台服务器也可以算作一个层,例如服务器故障转移群集。相比之下,逻辑层(Layer)通常指主要是按功能划分的逻辑软件组件组;逻辑层(Layer)用于软件开发目的。逻辑层(Layer)软件实现有很多优点,并且是实现N层架构的好方法。逻辑层(Layer)和层(Tier)可能完全匹配,也可能不完全匹配。每个逻辑层(Layer)都可以在单独的层(Tier)中运行。但是,多个逻辑层(Layer)也可能运行在同一个层(Tier)中。
一个逻辑层(Layer)也可以运行在多个层(Tier)中。例如,在下面的图2中,.NET中的持久化层(Persistence Layer)可以包含两个部分:持久化库(persistence Lib)和WCF数据服务(WCF data service)。持久化库(persistence Lib)在持久化层(Persistence Layer)中,通常与业务逻辑层(Business Layer)在同一个进程中运行,以使业务逻辑层(Business Layer)能够与WCF数据服务(WCF data service)进行适配。然而,持久化层(Persistence Layer)中的WCF数据服务(WCF data service)可以运行在单独的层(Tier)中。再举个例子:我们可以将业务逻辑层(Business Layer)中的数据验证提取到一个单独的库中(但仍保留在业务逻辑层(Business Layer)中),客户端呈现层(Client Presenter Layer)可以直接调用它,以获得更好的客户端交互性能。如果发生这种情况,那么业务逻辑层(Business Layer)中的数据验证部分将与客户端呈现层(Client Presenter Layer)在同一个进程中运行,而业务逻辑层(Business Layer)的其余部分将在单独的层(Tier)中运行。
层(Tier)与进程(Process)
如果一个逻辑层(Layer)可以在一个单独的进程中运行,那么它通常也可以在一个单独的计算机(Tier)中运行,因此可以被认为是N层架构中的一个单独的层(Tier)。然而,事实并非总是如此。例如,假设有两个逻辑层(Layer),它们被实现为在两个单独的进程中运行;它们之间也会进行通信。但是,如果这两个逻辑层(Layer)的实现方式是它们的进程间通信(IPC)完全基于非分布式方式,例如本地共享内存,那么这两个逻辑层(Layer)只能在同一台计算机的两个不同进程中运行,而不能在两个不同的计算机中运行。除非存在另一种可用的分布式IPC方式(例如套接字),否则即使这两个逻辑层(Layer)可以在同一台计算机的两个不同进程中运行,它们也只能被视为一个层(Tier)。
逻辑层(Layer)与进程(Process)
一个逻辑层(Layer)可以运行在一个进程中;几个逻辑层(Layer)也可以运行在一个进程中;一个逻辑层(Layer)也可以运行在几个进程中。如果您阅读了上面的“层(Tier)和逻辑层(Layer)的关系”部分,您将很容易理解这一点。
3层架构
我们首先介绍3层(3-Tier)的概念,以便稍后更容易理解其他层(Tier)的概念。N层架构中最简单的就是3层(3-Tier),它通常包含以下按从上到下的顺序列出的软件组件逻辑层(Layer):表示层(Presentation Layer)、应用程序层(Application Layer)和数据层(Data Layer),如图1所示。
一个逻辑层(Layer)只能直接访问其直接下方逻辑层(Layer)的公共组件。例如,表示层(Presentation Layer)只能访问应用程序层(Application Layer)中的公共组件,而不能访问数据层(Data Layer)中的公共组件。应用程序层(Application Layer)只能访问数据层(Data Layer)中的公共组件,而不能访问表示层(Presentation Layer)中的公共组件。这样做可以最大限度地减少一个逻辑层(Layer)对其他逻辑层(Layer)的依赖。这种依赖性最小化将为逻辑层(Layer)的开发/维护、升级、扩展等带来好处。这样做还可以实现层(Tier)安全性强制执行。例如,客户端逻辑层(Client Layer)不能直接访问数据层(Data Layer),而必须通过应用程序层(Application Layer)进行访问,因此数据层(Data Layer)具有更高的安全性保障。最后,这样做还可以避免软件组件之间的循环依赖。
要声称一个完整的3层架构,所有三个逻辑层(Layer)都应该能够在独立的计算机中运行。
这三个逻辑层(Layer)简要描述如下:
表示层(Presentation Layer):用户可以直接访问的逻辑层(Layer),例如桌面UI、网页等。也称为客户端(Client)。
应用程序层(Application Layer):该逻辑层(Layer)封装了业务逻辑(例如业务规则和数据验证)、领域概念、数据访问逻辑等。也称为中间层(Middle Layer)。
数据层(Data Layer):存储应用程序数据的外部数据源,例如数据库服务器、CRM系统、ERP系统、大型机或其他遗留系统等。我们今天经常遇到的是数据库服务器。对于N层架构,我们需要使用非嵌入式数据库服务器,如SQL Server、Oracle、DB2、MySQL或PostgreSQL。非嵌入式数据库服务器可以在独立的计算机中运行。而嵌入式类型的数据库,如Microsoft Access、dBase等,不能在独立的计算机中运行,因此不能用作3层架构的数据层(Data Layer)。
1层、2层、3层或更多层架构
1层(1-Tier):上述所有逻辑层(Layer)都只能运行在一台计算机上。为了实现1层(1-Tier),我们需要使用嵌入式数据库系统,它不能在独立的进程中运行。否则,至少会有2层(2-Tier),因为非嵌入式数据库通常可以在独立的计算机(Tier)中运行。
2层(2-Tier):表示层(Presentation Layer)和应用程序层(Application Layer)只能运行在一台计算机上,或者应用程序层(Application Layer)和数据层(Data Layer)只能运行在一台计算机上。整个应用程序不能运行在超过2台计算机上。
3层(3-Tier):N层架构中最简单的情况;所有这三个逻辑层(Layer)都能够运行在三台独立的计算机上。实际上,这三个逻辑层(Layer)也可以部署在同一台计算机上(3层架构,但部署为1层)。
N层(N-Tier):3层或更多层的架构。下图2描绘了一个典型的N层架构。3层架构中的一些逻辑层(Layer)可以进一步细分,分成更多的逻辑层(Layer)。这些细分的逻辑层(Layer)可能能够运行在更多的层(Tier)中。例如,应用程序层(Application Layer)可以细分为业务逻辑层(Business Layer)、持久化层(Persistence Layer)或更多。表示层(Presentation Layer)可以细分为客户端逻辑层(Client Layer)和客户端呈现层(Client Presenter Layer)。在图2中,要声称一个完整的N层架构,客户端呈现层(Client Presenter Layer)、业务逻辑层(Business Layer)和数据层(Data Layer)应该能够运行在三台独立的计算机(Tier)上。实际上,所有这些逻辑层(Layer)也可以部署在同一台计算机(Tier)上。
以下是图2中所有逻辑层(Layer)的简要概述:
客户端逻辑层(Client Layer):该逻辑层(Layer)直接与用户交互。可能存在多种类型的客户端并存,例如WPF、Windows窗体、HTML网页等。
客户端呈现层(Client Presenter Layer):包含客户端所需的表示逻辑,例如IIS Web服务器中的ASP.NET MVC。它还负责适配不同的客户端与业务逻辑层(Business Layer)进行交互。
业务逻辑层(Business Layer):处理和封装所有业务领域和逻辑;也称为领域逻辑层(Domain Layer)。
持久化层(Persistence Layer):负责将业务数据读写到数据层(Data Layer),也称为数据访问层(DAL)。
数据层(Data Layer):外部数据源,例如数据库。
有时,层(Tier)的数量可以等于或大于3,但客户端呈现层(Client Presenter Layer)、业务逻辑层(Business Layer)和数据层(Data Layer)不能运行在三台独立的计算机(Tier)上。这算N层架构吗?我们将这种情况归类为不完整的N层架构,因为它的客户端呈现层(Client Presenter Layer)、业务逻辑层(Business Layer)和数据层(Data Layer)不能运行在三台独立的计算机(Tier)上。
如果我们使用现代非嵌入式数据库(如SQL Server、Oracle等),这些数据库将始终能够在独立的计算机中运行。因此,对于图1这种情况,2层架构的标准是表示层(Presentation Layer)和应用程序层(Application Layer)只能运行在一台计算机上;完整的3层架构的标准是表示层(Presentation Layer)和应用程序层(Application Layer)可以运行在不同的计算机上。完整的N层架构的标准与3层架构相同。
不同层架构的优缺点
1层或2层架构
优点:由于进程和层(Tier)的数量较少,对于用户数量较少的情况,简单且速度快;由于硬件和网络带宽需求较少,硬件、网络、维护和部署成本低。
缺点:当用户数量增多时会出现问题;由于只能部署在1或2台计算机上,在解决安全性、可伸缩性、容错性等方面存在局限性。
N层架构
优点:具有以下一般性优点
- 可伸缩性:这是由于其多层部署能力和由此带来的层(Tier)解耦。例如,数据层(Data Tier)可以通过数据库集群进行扩展,而无需涉及其他层(Tier)。Web客户端可以通过负载均衡器轻松扩展,而不会影响其他层(Tier)。Windows服务器可以轻松地进行集群以实现负载均衡和故障转移。此外,业务逻辑层(Business Tier)服务器也可以进行集群以扩展应用程序,例如J2EE中的Weblogic集群。
- 更好的、更精细的系统整体安全性控制:如果每个层(Tier)的安全需求不同,我们可以为每个层(Tier)强制执行不同的安全措施。例如,业务逻辑层(Business Tier)和数据层(Data Tier)通常比表示层(Presentation Tier)需要更高的安全级别,那么我们可以将这两个高安全性的层(Tier)置于防火墙后面进行保护。1层或2层架构由于层(Tier)的数量有限,无法完全实现此目的。此外,对于N层架构,用户不能直接访问业务逻辑层(Business Layer)和数据层(Data Layer),所有来自用户的请求都由客户端呈现层(Client Presenter Layer)路由到业务逻辑层(Business Layer),然后再到数据层(Data Layer)。因此,客户端呈现层(Client Presenter Layer)也充当业务逻辑层(Business Layer)的代理层,业务逻辑层(Business Layer)充当数据层(Data Layer)的代理层。这些类似代理的层(Layer)为下方的层(Layer)提供了进一步的保护。
- 更好的容错能力:例如,数据层(Data Layer)中的数据库可以进行集群以实现故障转移或负载均衡,而不会影响其他层(Tier)。
- 独立层(Tier)升级和更改而不影响其他层(Tier):在面向对象的领域,接口依赖实现可以很好地解耦所有层(Layer),使得每个逻辑层(Layer)都可以独立更改,而不会过多地影响其他逻辑层(Layer)。接口依赖意味着一个逻辑层(Layer)仅依赖于其直接下方的逻辑层(Layer)的接口,而不是具体的类。此外,一个逻辑层(Layer)仅依赖于其直接下方的逻辑层(Layer)的依赖关系,也最大限度地减少了逻辑层(Layer)的更改对整个系统的影响。例如,如果保持接口不变,我们可以独立更新或替换任何逻辑层(Layer)的实现,而不会影响整个系统。由于业务需求和技术的变化,将一个逻辑层(Layer)的实现更改为另一个完全不同的实现经常发生。例如,最初我们主要使用Windows窗体,现在我们主要使用WPF。如果我们的原始系统实现了解耦的逻辑层(Layer)结构,那么我们只需要将客户端从Windows窗体更新到WPF,而无需更改服务器端逻辑层(Layer)。
- 对开发友好且高效:解耦的逻辑层(Layer)主要是按功能划分的逻辑软件组件组,它们对软件开发非常友好且高效。每个逻辑层(Layer)都可以单独分配给一个专注于特定功能领域的团队;一个专业团队可以更好地、更高效地处理相关任务。
- 易于维护:N层架构主要按功能将不同的事物分组,使事物清晰、易于理解和管理。
- 易于添加新功能:由于逻辑分组的组件以及N层架构带来的解耦,新功能可以轻松添加,而不会对整个系统产生太多影响。
- 更好的可重用性:这是由于逻辑分组的组件以及逻辑层(Layer)之间松散的耦合。松散耦合的组件组通常以更通用的方式实现,因此它们可以被其他更多应用程序重用。
N层部署的缺点
- 如果硬件和网络带宽不够好,整个应用程序的性能可能会变慢,因为涉及的网络、计算机和进程更多。
- 由于需要更多的硬件和更好的网络带宽,硬件、网络、维护和部署的成本更高。
N层部署对应用程序性能的影响是双刃剑。一方面,如果用户数量不够多,由于涉及的计算机、进程和网络更多,性能可能会变慢。也就是说,如果将所有内容放在一个层(Tier)或一个进程中,对于少量用户来说,性能会更好。但是,如果用户数量增多,那么N层带来的可伸缩性将提高整体性能,例如负载均衡和数据库集群都可以提高N层架构的性能。为什么少量用户和大量用户的性能结果不同?这是因为这两种情况下整个应用程序的瓶颈不同。对于少量用户的情况,瓶颈是数据在不同进程之间通信的时间。如果更多的计算机、更多的进程和更长的网络,那么花费的时间就更长,性能就会变差。然而,当用户数量增多时,瓶颈会转移到其他方面,因为服务器容量、例如一台计算机上的CPU和内存资源争用、服务器上的数据库阈值、Web服务器的性能限制等。只有N层架构的可伸缩性才能解决大量用户存在的这些瓶颈;通常使用服务器集群进行负载均衡来实现N层架构的可伸缩性。随着越来越多的计算机被扩展以分担大量用户的任务,性能得到提高。除了通过N层架构的可伸缩性获得性能外,我们还可以通过更好的硬件和更好的网络带宽来提高性能,以满足我们的业务需求。
N层架构中的业务数据验证
数据验证很重要,在N层架构中是必须的,以保持整个业务系统的健康和完整性。关于业务数据验证的第一个问题是:应该在哪个层(Layer)或哪些层(Layer)处理数据验证?有一些规则和事实如下,它们将为我们提供一些技巧,也能回答这个问题:
- 数据验证可以在任何逻辑层(Layer)进行检查。通常,验证越靠近客户端逻辑层(Client Layer),性能就越高效。验证越远离客户端逻辑层(Client Layer),应用程序就越可靠和健壮。当验证在业务逻辑层(Business Layer)或持久化层(Persistence Layer)中检查时,可以保证所有类型的客户端都能获得验证,无论客户端是否会检查验证。
- 当决定哪个逻辑层(Layer)应该进行验证时,我们需要在性能、可靠性和健壮性之间取得平衡的结果,并且需要根据实际情况做出决定。如果我们完全控制所有逻辑层(Layer),我们可以只让所有验证发生在客户端/客户端呈现层(Client/Client Presenter Layers)中以获得性能。然而,如果业务逻辑层(Business Layer)也暴露给一些我们无法控制的客户端/客户端呈现层(Client/Client Presenter Layers),那么业务逻辑层(Business Layer)或更低的逻辑层(Lower Layer)必须进行所有验证以获得可靠性,而不管我们的客户端是否进行相同的验证。
- 客户端验证是高效的,例如网页中的JavaScript验证。然而,用户可能很容易地故意绕过客户端验证,例如网页黑客攻击。因此,需要在客户端和服务器端都进行数据验证,以同时实现性能和可靠性。业务逻辑层(Business Layer)和其他更低的逻辑层(Lower Layers)通常属于服务器端。客户端呈现层(Client Presenter Layer)可能在服务器端,也可能不在服务器端;Web服务器的客户端呈现层(Client Presenter Layer),例如ASP.NET,在服务器端。WPF的客户端呈现层(Client Presenter Layer)可能不在服务器端。
- 更实用的方法是,为了性能,在客户端进行简单的数据验证,然后为了可靠性,在服务器端进行完整的验证。简单数据验证主要是对实体实例的单个属性进行检查。完整验证包括简单数据验证和一些复杂的、跨越实体实例多个属性或跨越相似或不同类型多个实体实例的数据验证。
- 对于一些交互式客户端应用程序,无论是否在服务器端进行验证,我们都需要进行客户端验证以获得可接受的交互性能。一些游戏应用程序属于此类。
- 我们应该在一个地方实现和维护一个版本的验证逻辑,无论验证将在哪里被检查。所有逻辑层(Layer)都应该共享这个版本的验证逻辑。为什么?这样做具有更好的可重用性;它可以避免在许多地方重复和冲突的验证逻辑,并使开发、维护和部署更容易;它使整个验证逻辑在整个应用程序中保持一致。此外,验证的检查点可能会随着业务的改变和增长而改变,因此验证应该保留在一个地方,使用一个版本,但如果需要,可以灵活地被任何逻辑层(Layer)调用。
如何正确部署N层应用程序
更多的层(Tier)会带来额外的复杂性、额外的部署/维护工作和额外的成本。因此,层(Tier)的数量应保持在足以解决可伸缩性、安全性、故障转移等问题的最低限度。如果这些问题已按需解决,则不要进一步部署更多层(Tier)。但是,为了尽可能好地解决这些问题,通常至少需要3层(3-Tier)。如果某些情况下根本不关心这些问题,那么我们可以选择1层或2层架构,或者1层或2层部署的N层架构来获得性能。什么是最佳的层(Tier)数量?没有固定的答案。为了满足我们的业务需求,我们需要选择层(Tier)的数量,以在N层架构的优点和缺点之间取得最佳平衡结果。
我们应该区分以下两种情况:a)所有逻辑层(Layer)运行在一个计算机的同一个进程中,b)所有逻辑层(Layer)运行在同一台计算机的不同进程中。第一种情况实际上是1层架构;第二种情况通常是N层架构,但只部署在1层中。即使都在同一台计算机上,由于涉及的进程更少,1层架构的性能会更好。与单个进程相比,跨越进程边界的通信更加复杂且速度较慢,无论使用何种IPC(进程间通信)技术:TCP/IP、命名管道、消息队列或共享内存等。因此,在已部署的计算机上,我们需要保持应用程序进程的数量尽可能少,以提高性能。如何实现这一点?N层架构的实现方式可以使其通过仅更新配置文件即可轻松切换到不同的层架构。这在我们示例应用程序的文章中已详细介绍:使用ASP.NET MVC3、WCF和Entity Framework的N层架构示例。
实际上,N层架构有许多变体,它们的存在是有原因的。例如,一种可能性是将客户端呈现层(Client Presenter Layer)和业务逻辑层(Business Layer)放在同一个进程中,以实现更好的交互性能。如果您对此感兴趣,可以自行进一步探索和研究这个主题。
此外,3层架构可以部署为3层或更少层。但2层架构不能部署为3层;否则,它应该被称为3层架构,而不是2层。
如何通过软件技术实现N层部署能力
N层的主要特点是能够将一个或多个逻辑层(Layer)部署到不同的计算机上,以处理可伸缩性、安全性、容错性等问题;两个相关的层(Tier)需要相互通信。如何实现这一点?运行在一个层(Tier)中的应用程序位于进程中。因此,两个层(Tier)之间的通信实际上归结为IPC(进程间通信)问题。分布式IPC方法可以支持两台不同计算机上的两个进程相互通信,例如套接字、分布式消息队列等。因此,从本质上讲,这些分布式IPC方法可以支持N层部署能力。例如,如果两个逻辑层(Layer)使用TCP/IP套接字作为通信方式实现,那么这两个逻辑层(Layer)可以部署在两台不同的计算机(Tier)上来相互通信。在.NET中,WCF可以轻松实现N层部署要求,因为WCF支持进程间的通信——无论是在同一台计算机上还是在不同的计算机上;WCF建立在基本的IPC方法之上。使用WCF实现N层架构的另一个优点是,WCF能够以逻辑层(Layer)之间非常松散的依赖关系实现SOA(面向服务架构)结果。
N层架构开发的一些实用技巧
设计、实现、部署和维护N层架构是一项艰巨的任务。如果您一开始没有清晰的思路,您可能会因为绕弯路和到处碰壁而浪费大量时间。我们已经在上面讨论了一些关于部署和数据验证的技巧,这里,我们将提供一些关于N层架构开发的额外实用技巧。本文中的技巧基于一个团队完全控制N层架构所有层的假设。
- 通过一些松散耦合技术,例如SOAP XML和接口等,尽可能将一个逻辑层(Layer)与另一个逻辑层(Layer)解耦。在面向对象的世界中,每个逻辑层(Layer)都应该仅通过接口依赖于其直接下方的逻辑层(Layer),而不是依赖于具体的类。通过这样做,我们可以实现两个逻辑层(Layer)之间的最大解耦,这种解耦将为开发、单元测试、维护、升级、可互换性、可重用性等带来诸多好处。
- 尽可能多地自动生成和维护一个版本的POCO业务实体类,这些类可以在整个应用程序中重用,为什么?业务实体类是N层架构的基础,它们将信息从最顶层的逻辑层(Layer)传递到底层的逻辑层(Layer)。现代应用程序往往会不断增长,因此,手动创建大量的实体类是艰巨且容易出错的,特别是为不同逻辑层(Layer)创建不同版本,正如一些人所偏爱的那样。因此,我们建议在整个应用程序中,我们应该尝试只使用一个轻量级的POCO版本的实体类,这些类应该由代码生成器自动生成。这样做可以节省我们大量的工作,并消除映射的烦恼以及不同版本实体类的不一致性。现在有许多代码生成器可用于此目的,例如Entity Framework的代码生成器。当然,不同的逻辑层(Layer)可能对实体类有不同的要求,如果这样,我们可以使用System.ComponentModel.Annotation中的注解功能和C#中的部分类功能来限制或扩展这些自动生成的实体类以满足我们在特定逻辑层(Layer)中的特殊需求。 如果存在强烈的理由必须在不同逻辑层(Layer)中使用不同版本的某些实体类,我们可以使用数据传输对象(DTO)来映射这些特定的实体,同时仍然保持大多数实体类为单一版本。
- 使用一些自动工具或包来生成业务实体类与传统关系数据库(数据层)之间的映射。现代业务和数据库越来越庞大,因此手动创建这些映射并不容易且容易出错。有许多现有的包或工具可以帮助我们,例如.NET的Entity Framework和NHibernate,以及Java的Hibernate。
- 尽可能多地使用代码生成器来处理其他大规模和风格相似的代码。如果您找不到任何现有的代码生成器来实现您的目的,请自行开发一个。通过使用面向对象编程语言(如C#或Java)以及XSLT(可扩展样式表语言转换),开发满足您特殊需求的通用代码生成器并不难。XSLT是基于XML的,对于通用代码生成目的非常有用且灵活,因为它很容易将任何XML文档转换为任何文本文档。
- 业务逻辑层(Business Layer)很容易与持久化层(Persistence Layer)紧密耦合;我们应该避免这种情况。例如,在.NET中,WCF业务服务可能直接访问Entity Framework。这种情况非常普遍。但是,这样做会存在业务逻辑层(Business Layer)和持久化层(Persistence Layer)紧密耦合的问题。这种紧密耦合会在单元测试、升级、可互换性等方面给逻辑层(Layer)带来许多问题。通常我们需要一个适配器层(Adapter Layer)在这两者之间,以便它们仅通过接口进行松散耦合。在我们N层示例文章中有一个物理示例:使用ASP.NET MVC3、WCF和Entity Framework的N层架构示例。
- 在客户端呈现层(Client Presenter Layer)中,我们应该尽可能将所有客户端的通用代码放入一个单独的库中,以最大化所有类型客户端的代码可重用性。
- 可以向任何现有逻辑层(Layer)添加缓存层(Cache Layer)以提高性能。例如,Varnish加速器可以用于ASP.NET、Drupal或其他Web应用程序,作为一个介于客户端逻辑层(Client Layer)和客户端呈现层(Client Presenter Layer)之间的缓存层(Cache Layer)来提高性能。Memcached和APC Cache是PHP缓存包,可以添加到许多PHP逻辑层(Layer)中作为额外的缓存层(Cache Layer)来缓存业务数据。请求将首先到达缓存层(Cache Layer),如果缓存中有有效数据,则请求将返回,不再向下继续到达更低的逻辑层(Layer),从而提高了性能。通常,缓存数据的更新或过期会使缓存中的旧数据失效。在.NET 4中,可以使用System.Web.Caching命名空间进行ASP.NET缓存;可以使用System.Runtime.Caching命名空间进行任何地方的缓存;Enterprise Library中的Caching Application Block也是缓存的一个选项。
- 为了适应不断变化的业务需求和技术,最好以一种易于灵活适应任何类型部署的方式来实现N层架构,包括2层架构部署。例如,N层架构可以实现为仅通过更新配置文件中的参数值就可以简单地切换不同的层架构。在我们N层示例文章中有一个实际的示例实现:使用ASP.NET MVC3、WCF和Entity Framework的N层架构示例。
结论
- 一个完整的3层架构应该能够将其表示层(Presentation Layer)、应用程序层(Application Layer)和数据层(Data Layer)运行在3台独立的计算机上(参考图1)。一个完整的N层架构应该能够将其客户端呈现层(Client Presenter Layer)、业务逻辑层(Business Layer)和数据层(Data Layer)运行在至少3台独立的计算机上(参考图2)。一个逻辑层(Layer)只能直接访问其直接下方逻辑层(Layer)的公共组件;这样做可以最大限度地减少一个逻辑层(Layer)对其他逻辑层(Layer)的依赖,并且还可以实现层(Tier)安全性强制执行。完整的N层架构在处理可伸缩性、安全性、容错性等方面具有最佳能力。
- 层(Tier)通常指物理部署的计算机;逻辑层(Layer)通常指主要是按功能划分的逻辑软件组件组。逻辑层(Layer)实现是实现N层架构的常见且最佳方式。逻辑层(Layer)和层(Tier)可能完全匹配,也可能不完全匹配。一个逻辑层(Layer)可以作为一个单独的层(Tier)运行;一个逻辑层(Layer)也可以运行在多个层(Tier)中;多个逻辑层(Layer)也可以只作为一个层(Tier)运行。
- 如果一个逻辑层(Layer)可以在一个单独的进程中运行,那么它通常也可以在一个单独的计算机(Tier)中运行。然而,事实并非总是如此。如果该进程仅通过非分布式IPC(进程间通信)方法与其他逻辑层(Layer)进程通信,则该进程将无法在独立的计算机(Tier)中运行。
- N层架构具有以下优点:更好的可伸缩性、更好的精细安全性控制、更好的容错能力、独立的层(Tier)升级和更改能力而不影响其他层(Tier)、对开发友好且高效、易于维护、易于添加新功能、更好的可重用性等。
- 如果使用现代非嵌入式数据库(如SQL Server、Oracle等)作为数据层(Data Layer),该数据库将始终能够在独立的层(Tier)中运行。对于图1中的这种情况,2层架构的标准是表示层(Presentation Layer)和应用程序层(Application Layer)只能运行在一台计算机上;完整的3层或任何其他完整的N层架构的标准是表示层(Presentation Layer)和应用程序层(Application Layer)可以运行在独立的计算机上。
- 更多的层(Tier)会带来额外的复杂性、额外的部署/维护工作和额外的成本。因此,层(Tier)的数量应保持在足以解决可伸缩性、安全性、故障转移等问题的最低限度。如果这些问题已按需解决,则不要进一步部署更多层(Tier)。此外,在已部署的计算机上,我们需要保持进程的数量尽可能少,以提高性能;良好的架构设计可以轻松实现这一点。
- 分布式IPC(进程间通信)方法(如套接字)可以使一个逻辑层(Layer)的进程能够部署在一个层(Tier)中。在.NET中,WCF是实现N层架构部署能力的一个好方法。
- 关于N层架构中的数据验证,有以下事实和规则:
- 数据验证可以在任何逻辑层(Layer)进行检查。通常,验证越靠近客户端逻辑层(Client Layer),性能就越高效。验证越远离客户端逻辑层(Client Layer),应用程序就越可靠和健壮。当验证在业务逻辑层(Business Layer)或持久化层(Persistence Layer)中检查时,可以保证所有类型的客户端都能获得验证,无论客户端是否会检查验证。
- 当决定哪个逻辑层(Layer)应该进行验证时,我们需要在性能、可靠性和健壮性之间取得平衡的结果,并且需要根据实际情况做出决定。
- 客户端验证是高效的,例如网页中的JavaScript验证。然而,用户可能很容易地故意绕过客户端验证,例如网页黑客攻击。因此,需要在客户端和服务器端都进行数据验证,以同时实现性能和可靠性。
- 更实用的方法是,为了性能,在客户端进行简单的数据验证,然后为了可靠性,在服务器端进行完整的验证。简单数据验证主要是对实体实例的单个属性进行检查。完整验证包括简单数据验证和一些复杂的、跨越实体实例多个属性或跨越相似或不同类型多个实体实例的数据验证。
- 对于一些交互式客户端应用程序,无论是否在服务器端进行验证,我们都需要进行客户端验证以获得可接受的交互性能。一些游戏应用程序属于此类。
- 我们应该在一个地方实现和维护一个版本的验证逻辑,无论验证将在哪里被检查。所有逻辑层(Layer)都应该共享这个版本的验证逻辑。这样做具有更好的可重用性,并且使开发、维护和部署更容易,同时使整个应用程序中的验证逻辑保持一致。
- 以下是一些关于N层架构开发的额外实用技巧,基于一个团队完全控制所有层的假设:
- 通过一些松散耦合技术,例如SOAP XML和接口等,尽可能将一个逻辑层(Layer)与另一个逻辑层(Layer)解耦。在面向对象的世界中,每个逻辑层(Layer)都应该仅通过接口依赖于其直接下方的逻辑层(Layer),而不是依赖于具体的类。通过这样做,我们可以实现两个逻辑层(Layer)之间的最大解耦,这种解耦将为开发、单元测试、维护、升级、可互换性、可重用性等带来诸多好处。
- 尽可能多地自动生成和维护一个版本的POCO业务实体类,这些类可以在整个应用程序中重用。这将消除版本冲突、映射、手动编码错误等所有麻烦。在某些特殊情况下,我们可以将一个版本的实体类与一些数据传输对象(DTO)混合使用,这些DTO与一些特殊的实体类进行映射。
- 使用一些自动工具或包来生成业务实体类与传统关系数据库(数据层)之间的映射,例如.NET的Entity Framework和NHibernate,以及Java的Hibernate。
- 尽可能多地使用代码生成器来处理其他大规模和风格相似的代码。如果您找不到任何现有的代码生成器来实现您的目的,请自行开发一个。C#、Java、XSLT等可以轻松帮助开发代码生成器。
- 业务逻辑层(Business Layer)很容易与持久化层(Persistence Layer)紧密耦合;我们应该通过在这两个逻辑层(Layer)之间使用适配器层(Adapter Layer)来避免这种情况,以便它们仅通过接口进行松散耦合,例如在WCF业务逻辑层(Business Layer)和Entity Framework之间使用持久化适配器(Persistence Adapter)。
- 在客户端呈现层(Client Presenter Layer)中,我们应该尽可能将所有客户端的通用代码放入一个单独的库中,以最大化所有类型客户端的代码可重用性。
- 可以向任何现有逻辑层(Layer)添加缓存层(Cache Layer)以提高性能。
- 为了适应不断变化的业务需求和技术,最好以一种易于灵活适应任何类型部署的方式来实现N层架构,包括2层架构部署。例如,N层架构可以实现为仅通过更新配置文件中的参数值就可以简单地切换不同的层架构。