分层应用程序设计模式






4.48/5 (22投票s)
项目的 N 层架构
引言
在关键的应用程序开发中,最重要的是项目架构或项目框架,它包含了处理请求或业务的各种模块。
如果框架清晰且健壮,并经过充分规划而设计,那么进行编码和开发特定业务需求就非常容易。
对于任何应用程序,如果开发人员了解项目框架/架构,那么就已经完成了 50% 的工作。之后,我们只需要专注于功能,而无需担心项目中涉及的相互关联的业务组件。
N 层架构
任何项目中最重要的层是
- 应用程序层
- 业务层
- 数据层
但问题是,如果我们只使用这些层,那么这些层将直接相互通信,这可能导致框架紧密耦合。此外,我们在各个层中必须使用相同的代码进行日志记录和错误处理。
另外,如果我们想使用通用的代码逻辑,那么我们需要在各个层中编写它。
因此,为了改进这一点,我们使用以下层
- 应用程序层/表示层
- 业务层
- 数据层
- 框架层
- 服务代理层
- 服务层
各层详解
1. 数据层
此层包含数据库连接,即数据实体、数据连接等。
此数据层中的数据访问组件负责将这些数据库中存储的数据暴露给业务层。
让我们来实现我们的数据层。
添加 ADO.NET Entity Data Model,命名为 DataModel
,并将实体命名为 SampleEntities
,这将在 DataLayer
的 app.config 中反映为连接字符串名称,如下所示。
我们可以选择应用程序中需要作为实体的表。
在附加代码中,我们只使用了两个表,GI_User
和 Log
。现在我们的数据层就完成了。
app.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="SampleEntities"
connectionString="metadata=res://*/DataModel.csdl|res://*/DataModel.ssdl|res://*/
DataModel.msl;provider=System.Data.SqlClient;provider connection string="
Data Source=(local);Initial Catalog=Sample;User ID=uid;
Password=pwd;MultipleActiveResultSets=True""
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
2. 业务层
此层包含业务处理逻辑。
所有 CRUD 操作都到这里,例如
让我们来实现我们的业务层。首先,将 DataLayer
引用添加到业务层。
在此层中,我们将最终用户提供的凭据与 GI_User
表中的值进行验证。
要执行任何数据库操作,我们使用 LINQ (Language Integrated Query),要访问表实体,我们需要创建一个连接实体对象,即 SampleEntities
,如下面的代码片段所示。
现在我们的业务层也完成了。
namespace BusinessLayer
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Web;
using DataLayer;
/// <summary>
/// Authenticate class.
/// </summary>
public class Authenticate
{
/// <summary>
/// AuthenticateUser used for authenticating an user.
/// </summary>
/// <param name="userName">User name provided by user.</param>
/// <param name="password">Password provide by user.</param>
/// <returns>Authentication status.</returns>
public int AuthenticateUser(string userName, string password)
{
SampleEntities dataContext = new SampleEntities();
return (from status in dataContext.GI_User
where status.UserName.Equals(userName) && status.Password.Equals(password)
select status.UserId).FirstOrDefault();
}
}
}
3. 框架层
此层包含通用的可配置项。所有日志记录机制、缓存机制等都在这里。是时候实现我们的框架层了,它将包含所有日志记录、邮件和其他任何机制。为了日志记录,我们使用 log4net(更多信息请阅读 https://codeproject.org.cn/Articles/140911/log4net-Tutorial)。我们声明了一些日志记录机制所需的枚举。为了实现使用 log4net 的日志记录机制,我们必须添加 log4net.dll 的引用。
Enums
namespace Framework.Enums
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Runtime.Serialization;
/// <summary>
/// Framework Enumeration class.
/// </summary>
public static class Enums
{
/// <summary>
/// Framework Enumeration ErrorLevel.
/// </summary>
[DataContract(Name = "ErrorLevel")]
public enum ErrorLevel
{
/// <summary>
/// ErrorLevel FATAL.
/// </summary>
[EnumMember]
FATAL = 0,
/// <summary>
/// ErrorLevel ERROR.
/// </summary>
[EnumMember]
ERROR = 1,
/// <summary>
/// ErrorLevel WARN.
/// </summary>
[EnumMember]
WARN = 2,
/// <summary>
/// ErrorLevel INFO.
/// </summary>
[EnumMember]
INFO = 3,
/// <summary>
/// ErrorLevel DEBUG.
/// </summary>
[EnumMember]
DEBUG = 4
}
}
}
Logger
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace Framework.Logging
{
using System;
using Framework.Enums;
/// <summary>
/// Logger class.
/// </summary>
public static class Logger
{
/// <summary>
/// Logger field of Logger class.
/// </summary>
private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Logs info to DB / File based on web.config settings.
/// </summary>
/// <param name="ex">Exception object</param>
/// <param name="customMessage">Custom error message.</param>
/// <param name="errorLevel">Exception error type.</param>
public static void Log(Exception ex, string customMessage, Enums.ErrorLevel errorLevel)
{
switch (errorLevel)
{
case Enums.ErrorLevel.DEBUG:
logger.Debug(customMessage, ex);
break;
case Enums.ErrorLevel.ERROR:
logger.Error(customMessage, ex);
break;
case Enums.ErrorLevel.FATAL:
logger.Fatal(customMessage, ex);
break;
case Enums.ErrorLevel.INFO:
logger.Info(customMessage, ex);
break;
case Enums.ErrorLevel.WARN:
logger.Warn(customMessage, ex);
break;
default:
logger.Error(customMessage, ex);
break;
}
}
}
}
app.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
</configSections>
<log4net>
<root>
<level value="DEBUG"/>
<appender-ref ref="ADONetAppender"/>
</root>
<appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
<bufferSize value="1"/>
<connectionType value="System.Data.SqlClient.SqlConnection,
System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<connectionString value="server=(local); uid=uid; pwd=pwd; database=Sample"/>
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],
[Message],[Exception]) VALUES (@log_date, @thread, @log_level,
@logger, @message, @exception)"/>
<parameter>
<parameterName value="@log_date"/>
<dbType value="DateTime"/>
<layout type="log4net.Layout.RawTimeStampLayout"/>
</parameter>
<parameter>
<parameterName value="@thread"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread"/>
</layout>
</parameter>
<parameter>
<parameterName value="@log_level"/>
<dbType value="String"/>
<size value="50"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level"/>
</layout>
</parameter>
<parameter>
<parameterName value="@logger"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger"/>
</layout>
</parameter>
<parameter>
<parameterName value="@message"/>
<dbType value="String"/>
<size value="4000"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message"/>
</layout>
</parameter>
<parameter>
<parameterName value="@exception"/>
<dbType value="String"/>
<size value="2000"/>
<layout type="log4net.Layout.ExceptionLayout"/>
</parameter>
</appender>
</log4net>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
4. 服务层
此层是应用程序层和业务层之间的桥梁。
应用程序层通过此层与业务层通信。
如果需要,我们也可以将此层用作业务层和数据层之间的桥梁。
基本上,这一层是 WCF 层。
在此层中,我们也可以添加第三方服务。
添加一个 abstract
类并使其成为 BaseService
,它包含所有通用代码,其他所有服务类都应继承 BaseService
。
应用程序层和业务层之间的桥梁是服务层,所以让我们进入服务层。
添加业务层的引用。
添加新项作为 WCF 服务,并命名为 AuthenticationService
,这将生成 AuthenticationService.svc 和 IAuthenticationService.cs。
在 IAuthenticationService.cs 接口中声明 AuthenticateUser(string userName,string password)
,并在 AuthenticationService.svc 类中实现该接口方法,该方法将访问业务层方法。
BaseService.cs:
namespace Services
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Framework.Logging;
/// <summary>
/// BaseService Class.
/// </summary>
public abstract class BaseService
{
/// <summary>
/// Used for logging the web service errors.
/// </summary>
/// <param name="ex">Exception generated.</param>
/// <param name="customMessage">Custom message.</param>
/// <param name="errorLevel">Error level.</param>
/// <param name="errCode">Error code.</param>
/// <returns>Fault exception.</returns>
public System.ServiceModel.FaultException LogWcfError(Exception ex,
string customMessage, Framework.Enums.Enums.ErrorLevel errorLevel, string errCode)
{
Logger.Log(ex, customMessage, errorLevel);
return new System.ServiceModel.FaultException(
new System.ServiceModel.FaultReason(customMessage),
new System.ServiceModel.FaultCode(errCode));
}
}
}
web.config:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
</configSections>
<log4net>
<root>
<level value="DEBUG"/>
<appender-ref ref="ADONetAppender"/>
</root>
<appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
<bufferSize value="1"/>
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
<connectionString value="server=(local); uid=uid; pwd=pwd; database=Sample"/>
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],
[Message],[Exception]) VALUES
(@log_date, @thread, @log_level, @logger, @message, @exception)"/>
<parameter>
<parameterName value="@log_date"/>
<dbType value="DateTime"/>
<layout type="log4net.Layout.RawTimeStampLayout"/>
</parameter>
<parameter>
<parameterName value="@thread"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread"/>
</layout>
</parameter>
<parameter>
<parameterName value="@log_level"/>
<dbType value="String"/>
<size value="50"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level"/>
</layout>
</parameter>
<parameter>
<parameterName value="@logger"/>
<dbType value="String"/>
<size value="255"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger"/>
</layout>
</parameter>
<parameter>
<parameterName value="@message"/>
<dbType value="String"/>
<size value="4000"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message"/>
</layout>
</parameter>
<parameter>
<parameterName value="@exception"/>
<dbType value="String"/>
<size value="2000"/>
<layout type="log4net.Layout.ExceptionLayout"/>
</parameter>
</appender>
</log4net>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below
to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
<connectionStrings>
<add name="SampleEntities"
connectionString="metadata=res://*/DataModel.csdl|res://*/DataModel.ssdl|
res://*/DataModel.msl;provider=System.Data.SqlClient;provider connection string=
"Data Source=(local);Initial Catalog=Sample;User ID=uid;
Password=pwd;MultipleActiveResultSets=True""
providerName="System.Data.EntityClient" />
</connectionStrings>
</configuration>
5. 服务代理层
让我们来实现我们的服务代理层,其优势我们已经讨论过。
添加服务层的引用。
添加服务引用并点击发现,它将显示 AuthenticationService
,所以点击 OK,它将在服务代理层中添加 AuthenticationService
的服务引用。
它将自动添加所有必需的信息,如绑定、终结点等。
基本上,它包含来自服务层的服务。
如果我们不使用这一层,那么假设有人对任何服务进行了一些更改,而他忘记了检查更新后的服务响应,那么所有其他用户都会收到错误。
现在如果我们使用这一层,那么如果有人进行了更改并检查了它,如果特定用户想要该更改,他/她只需更新代理并生成解决方案。
为了实现这一点,只需添加一个类库并命名为 ServiceProxies
。
app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IAuthenticateService" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://:9321/Classes/AuthenticateService.svc"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IAuthenticateService"
contract="AuthenticateService.IAuthenticateService"
name="BasicHttpBinding_IAuthenticateService" />
</client>
</system.serviceModel>
</configuration>
6. 应用程序层
此层包含表示逻辑。
应用程序由一系列用户交互的窗体(页面)组成。每个窗体包含许多字段,这些字段显示来自较低层的信息并收集用户输入。
添加一个类并使其成为 BasePage
,它将继承自 System.Web.UI.Page
,其他所有页面都应继承 BasePage
。
拥有 BasePage
的好处是,我们不需要在所有页面中编写一些可配置项,例如设置区域性、CSRF 检查等。
现在除了我们的应用程序层之外,所有层都已实现。
添加框架层和服务代理层的引用。
将服务代理层的 app.config 中的 <system.serviceModel>
部分复制粘贴到应用程序层的 Web.config 中,以访问已实现的服务。
现在只需使用 AuthenticationService
的客户端对象访问其方法,该对象将调用业务层的功能。
目前,我们仅在应用程序层中使用日志记录机制来记录异常,但如果我们想记录任何信息性消息,也可以这样做。
在应用程序层中执行异常日志记录的主要逻辑是,最终,我们通过服务调用业务逻辑,如果其中任何一个失败,它将在调用发生的应用程序层的方法中被捕获。
此外,对于记录任何 WCF 错误/异常,我们在服务层中使用了相同的日志记录机制。
namespace ApplicationLayer.BasePage
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Globalization;
using Framework.Logging;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.IO;
/// <summary>
/// BasePage class.
/// </summary>
public class BasePage : System.Web.UI.Page
{
/// <summary>
/// OnInit predefined event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
System.Threading.Thread.CurrentThread.CurrentCulture =
new CultureInfo("en-US", true);
}
/// <summary>
/// OnPreLoad predefined event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected override void OnPreLoad(System.EventArgs e)
{
base.OnPreLoad(e);
}
/// <summary>
/// OnLoad predefined event.
/// </summary>
/// <param name="e">Event arguments.</param>
protected override void OnLoad(EventArgs e)
{
}
/// <summary>
/// LogError used for logging various types of information.
/// </summary>
/// <param name="ex">Exception occured.</param>
/// <param name="customException">Custom exception text.</param>
/// <param name="level">Error level.</param>
public void LogError(Exception ex, string customException,
Framework.Enums.Enums.ErrorLevel level)
{
Logger.Log(ex, customException, level);
}
}
}
参考文献
- http://msdn.microsoft.com/en-us/library/ff648105.aspx
- http://msdn.microsoft.com/en-us/library/ee658109.aspx
历史
- 2013 年 9 月 25 日:初始版本