原文出处:http://kb.cnblogs.com/page/52615/
上一篇文章主要讨论的是PetShop的模块划分,在这一篇文章中我们来讨论在一个模块中如何进行层次划分。模块划分应该是基于功能的,一个模块可以看成是服务于某项功能的所有资源的集合;层次划分侧重于关注点分离(SoC:Separation of Concern ),让某一层专注于某项单一的操作,以实现重用性、可维护性、可测试性等相应的目的。Source Code从这里下载。

  一、基本的层次结构

  我们接下来将目光聚焦到模块内部,看看每一个模块具体又有怎样的层次划分。我们将Infrastructures、Products和Orders目标展开,将会呈现出如图1所示的层次结构。

clip_image002

图1 从解决方案的结构看模块的层次结构

以Products模块为例,它由如下的项目组成:

  • Products:对于整个应用来说,Products是最终基于该模块功能的提供者;
  • Products.Interface: 模块提供给其他模块的服务接口,本项目被Products项目引用;
  • Products.Service.Interface:模块客户端和服务端进行服务调用的WCF服务契约,Products项目最为WCF服务的客户端通过该接口进行服务调用;
  • Products.Service:实现了上述服务契约的WCF服务,引用了Products.Service.Interface项目;
  • Products.BusinessComponent:也可以称为业务逻辑层,实现了真正的业务逻辑;
  • Products.DataAccess:数据访问层,在这里主要提供对数据库的访问;
  • Products.BusinessEntity:提供的业务实体(BusinessEntity)类型的定义。一般来讲,业务实体和数据契约(DataContract)是不同的,前者主要对本模块,后者则对外,在这里为了简单起见,将两者合二为一。

  从部署的角度讲,Products和Products.Interface部署与于Web服务器;Products.Service、Products.BusinessComponent和Products.DataAccess则部署于应用服务器;Products.Service.Interface和Products.BusinessEntity则同时被部署于Web服务器和应用服务器。整个层次结构大体上如图2所示。

clip_image004

图2 逻辑层次和物理部署

  二、数据库设计

  整个应用主要涉及4个表,其中3个用于存储业务数据(产品表、订单表和订单明细表),另一个用于存储简单的审核信息(审核表)。4个表的结构可以分别参考相应的SQL脚本。

产品表(T_PRODUCT)

   1: CREATE TABLE [T_PRODUCT] (
   2:   [PRODUCT_ID]         [VARCHAR](50)          NOT NULL,
   3:   [PRODUCT_CATEGORY]   [NVARCHAR](128)        NOT NULL,
   4:   [PRODUCT_NAME]       [NVARCHAR](256)        NOT NULL,
   5:   [PRODUCT_PIC]        [NVARCHAR](512),
   6:   [PRODUCT_DESC]       [NVARCHAR](800),
   7:   [PRODUCT_UNIT_PRICE] [DECIMAL](10,2)        NOT NULL,
   8:   [PRODUCT_INVENTORY]  [INT]                  NOT NULL,
   9:   
  10:   [VERSION_NO]         [TIMESTAMP]            NOT NULL,
  11:   [TRANSACTION_ID]     [VARCHAR](50)          NOT NULL,
  12:   [CREATED_BY]         [NVARCHAR](256)        NOT NULL,
  13:   [CREATED_TIME]       [DATETIME]             NOT NULL,
  14:   [LAST_UPDATED_BY]    [NVARCHAR](256)        NOT NULL,
  15:   [LAST_UPDATED_TIME]  [DATETIME]             NOT NULL
  16:   
  17:   CONSTRAINT [C_PRODUCT_PK]            PRIMARY KEY CLUSTERED    ( [PRODUCT_ID] ASC ) ON [PRIMARY]) ON [PRIMARY]

订单表(T_ORDER)

   1: CREATE TABLE [T_ORDER] (
   2:   [ORDER_ID]          [VARCHAR](50)         NOT NULL,
   3:   [ORDER_DATE]        [DATETIME]            NOT NULL,
   4:   [ORDER_TOTAL_PRICE] [DECIMAL](38,2)       NOT NULL,
   5:   [ORDERED_BY]        [NVARCHAR](256)       NOT NULL,
   6:   
   7:   [VERSION_NO]        [TIMESTAMP]           NOT NULL ,
   8:   [TRANSACTION_ID]    [VARCHAR](50)         NOT NULL ,
   9:   [CREATED_BY]        [NVARCHAR](256)       NOT NULL ,
  10:   [CREATED_TIME]      [DATETIME]            NOT NULL ,
  11:   [LAST_UPDATED_BY]   [NVARCHAR](256)       NOT NULL ,
  12:   [LAST_UPDATED_TIME] [DATETIME]            NOT NULL 
  13:   
  14:   CONSTRAINT [C_ORDER_PK]                PRIMARY KEY CLUSTERED ( [ORDER_ID] ASC ) ON [PRIMARY]) ON [PRIMARY]

订单明细表(T_ORDER_DETAIL)

   1: CREATE TABLE [T_ORDER_DETAIL] (
   2:   [ORDER_ID]          [VARCHAR](50)         NOT NULL,
   3:   [PRODUCT_ID]        [VARCHAR](50)         NOT NULL,
   4:   [QUANTITY]          [INT]                 NOT NULL,
   5:   
   6:   [VERSION_NO]        [TIMESTAMP]           NOT NULL ,
   7:   [TRANSACTION_ID]    [VARCHAR](50)         NOT NULL ,
   8:   [CREATED_BY]        [NVARCHAR](256)       NOT NULL ,
   9:   [CREATED_TIME]      [DATETIME]            NOT NULL ,
  10:   [LAST_UPDATED_BY]   [NVARCHAR](256)       NOT NULL ,
  11:   [LAST_UPDATED_TIME] [DATETIME]            NOT NULL 
  12:   
  13:   CONSTRAINT [C_ORDER_DETAIL_PK]        PRIMARY KEY CLUSTERED ( [PRODUCT_ID]  ASC,[ORDER_ID] ASC ) ON [PRIMARY]) ON [PRIMARY]

审核表(T_AUDIT)

   1: CREATE TABLE [T_AUDIT](
   2:     [TRANSACTION_ID] [varchar](50)    NOT NULL,
   3:     [OPERATION] [nvarchar](256)       NOT NULL,
   4:     [OPERATOR] [varchar](50)          NOT NULL,
   5:     [OPERATION_TIME] [datetime]       NOT NULL,
   6:     CONSTRAINT [C_AUDIT_PK]           PRIMARY KEY CLUSTERED ( [TRANSACTION_ID]  ASC) ON [PRIMARY])    ON [PRIMARY]

注:对于每一个业务表,我都添加了如下6个系统字段:VERSION_NO(TIMESTAMP)用于进行并发验证;TRANSACTION_ID代表最后一次操作该纪录的事务ID;CREATED_BY、CREATED_TIME、LAST_UPDATED_BY和LAST_UPDATED_TIME分别表示创建记录的创建者和创建时间,以及最后一次操作的操作者和操作时间。

  在PetShop中,我们将事务作为审核的基本单元,而每一个事务由上述的TRANSACTION_ID作为唯一标识。简单起见,在这里仅仅记录一些数据最基本的信息:操作的名称、操作者和操作时间。

  介绍了表的定义,接下来简单介绍相关存储过程的定义。首先是用于筛选产品的两个存储过程:P_PRODUCT_GET_ALL和P_PRODUCT_GET_BY_ID,前者获取所有的产品,后者根据ID获取相应产品信息。

P_PRODUCT_GET_ALL

   1: CREATE Procedure P_PRODUCT_GET_ALL
   2: AS
   3: SELECT [PRODUCT_ID]
   4:       ,[PRODUCT_CATEGORY]
   5:       ,[PRODUCT_NAME]
   6:       ,[PRODUCT_PIC]
   7:       ,[PRODUCT_DESC]
   8:       ,[PRODUCT_UNIT_PRICE]
   9:       ,[PRODUCT_INVENTORY]
  10:       ,[VERSION_NO]
  11:       ,[TRANSACTION_ID]
  12:       ,[CREATED_BY]
  13:       ,[CREATED_TIME]
  14:       ,[LAST_UPDATED_BY]
  15:       ,[LAST_UPDATED_TIME]
  16:   FROM [dbo].[T_PRODUCT]
  17: GO
   1: CREATE Procedure P_PRODUCT_GET_BY_ID
   2: (
   3:     @p_product_id VARCHAR(50)
   4: )
   5: AS
   6:  
   7: SELECT [PRODUCT_ID]
   8:       ,[PRODUCT_CATEGORY]
   9:       ,[PRODUCT_NAME]
  10:       ,[PRODUCT_PIC]
  11:       ,[PRODUCT_DESC]
  12:       ,[PRODUCT_UNIT_PRICE]
  13:       ,[PRODUCT_INVENTORY]
  14:       ,[VERSION_NO]
  15:       ,[TRANSACTION_ID]
  16:       ,[CREATED_BY]
  17:       ,[CREATED_TIME]
  18:       ,[LAST_UPDATED_BY]
  19:       ,[LAST_UPDATED_TIME]
  20:   FROM [dbo].[T_PRODUCT]
  21:   WHERE [PRODUCT_ID] = @p_product_id
  22: GO

  而下面的两个存储过程P_ORDER_INSERT和P_ORDER_DETAIL_INSERT则用于添加订单记录。

P_ORDER_INSERT

   1: CREATE Procedure P_ORDER_INSERT
   2:     (
   3:         @p_order_id            VARCHAR(50),
   4:         @p_ordered_by          VARCHAR(50),
   5:         @p_total_price         DECIMAL,
   6:         @p_user_name           NVARCHAR(50),
   7:         @p_transacion_id       VARCHAR(50)
   8:     )
   9:  
  10: AS
  11: INSERT INTO [PetShop].[dbo].[T_ORDER]
  12:            ([ORDER_ID]
  13:            ,[ORDER_DATE]
  14:            ,[ORDER_TOTAL_PRICE]
  15:            ,[ORDERED_BY]
  16:            ,[TRANSACTION_ID]
  17:            ,[CREATED_BY]
  18:            ,[CREATED_TIME]
  19:            ,[LAST_UPDATED_BY]
  20:            ,[LAST_UPDATED_TIME])
  21:      VALUES
  22:            (@p_order_id
  23:            ,GETDATE()
  24:            ,@p_total_price
  25:            ,@P_ordered_by
  26:            ,@p_transacion_id
  27:            ,@p_user_name
  28:            ,GETDATE()
  29:            ,@p_user_name
  30:            ,GETDATE())
  31: GO

P_ORDER_DETAIL_INSERT

   1: CREATE Procedure P_ORDER_DETAIL_INSERT
   2:     (
   3:         @p_order_id        VARCHAR(50),
   4:         @p_product_id      VARCHAR(50),
   5:         @p_quantity        INT,
   6:         @p_user_name       NVARCHAR(50),
   7:         @p_transacion_id   VARCHAR(50)
   8:     )
   9: AS
  10: INSERT INTO [PetShop].[dbo].[T_ORDER_DETAIL]
  11:            ([ORDER_ID]
  12:            ,[PRODUCT_ID]
  13:            ,[QUANTITY]
  14:            ,[TRANSACTION_ID]
  15:            ,[CREATED_BY]
  16:            ,[CREATED_TIME]
  17:            ,[LAST_UPDATED_BY]
  18:            ,[LAST_UPDATED_TIME])
  19:      VALUES
  20:            (@p_order_id
  21:            ,@p_product_id
  22:            ,@p_quantity
  23:            ,@p_transacion_id
  24:            ,@p_user_name
  25:            ,GETDATE()
  26:            ,@p_user_name
  27:            ,GETDATE())
  28: GO

  三、业务实体(数据契约)设计

  我们将对内的业务实体(Business Entity)和对外的数据契约合二为一,定义成WCF的数据契约(Data Contract)。所有的业务实体类型定义在相应模块的{Module}.BusinessEntity项目之中。在Products.BusinessEntity定义了Product数据契约表示,产品相关信息;在Orders.BusinessEntity中定义了Order和OrderDetail数据契约,表示提交的订单和订单明细。

  注:如果采用领域模型(Domain Model)来设计业务逻辑层,整个模型通过以一个个面向业务逻辑(而不是数据存储)的对象构成。而这些对象是完全基于OO的对象,即数据(或者状态)和行为(或者方法)的封装。如果业务逻辑层对外提供服务,我们需要将数据封装成为数据传输对象(DTO:Data Transfer Object)。在理想的情况下,我们需要一个额外的层次实现领域对象与数据传输对象之间的转换,但是在实际项目开发中,这会带来很多额外的成本。对于本例,我们大体上可以看成是将数据传输对象和领域对象的数据部分合二为一(PetShop并没有完全按照领域模型来设计)。

Product

   1: using System;
   2: using System.Runtime.Serialization;
   3: namespace Artech.PetShop.Orders.BusinessEntity
   4: {
   5:     [DataContract(Namespace="http://www.artech.com/petshop/")]
   6:     public class Product
   7:     {
   8:         [DataMember]
   9:         public Guid ProductID
  10:         { get; set; }
  11:         [DataMember]
  12:         public string Category
  13:         { get; set; }
  14:         [DataMember]
  15:         public string ProductName
  16:         { get; set; }
  17:         [DataMember]
  18:         public string Description
  19:         { get; set; }
  20:         [DataMember]
  21:         public decimal UnitPrice
  22:         { get; set; }
  23:         [DataMember]
  24:         public string Picture
  25:         { get; set; }
  26:         [DataMember]
  27:         public int Inventory
  28:         { get; set; }
  29:     }
  30: }

OrderDetail

   1: using System;
   2: using System.Runtime.Serialization;
   3: namespace Artech.PetShop.Orders.BusinessEntity
   4: {
   5:     [DataContract(Namespace = "http://www.artech.com/petshop/")]
   6:     public class OrderDetail
   7:     {
   8:        [DataMember]
   9:        public Guid ProductID
  10:        { get; set; }
  11:        public string ProductName
  12:        { get; set; }
  13:        [DataMember]
  14:        public decimal UnitPrice
  15:        { get; set; }
  16:        [DataMember]
  17:         public int Quantity
  18:        { get; set; }
  19:     }
  20: }

Order

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Runtime.Serialization;
   5: namespace Artech.PetShop.Orders.BusinessEntity
   6: {
   7:     [DataContract(Namespace = "http://www.artech.com/petshop/")]
   8:     [KnownType(typeof(OrderDetail))]
   9:     public class Order
  10:     {
  11:         public Order()
  12:         {
  13:             this.Details = new List();
  14:         }
  15:         [DataMember]
  16:         public Guid OrderNo
  17:         { get; set; }
  18:         [DataMember]
  19:         public DateTime OrderDate
  20:         { get; set; }
  21:  
  22:         [DataMember]
  23:         public string OrderBy
  24:         { get; set; }
  25:  
  26:         [DataMember]
  27:         public IList Details
  28:         { get; set; }
  29:  
  30:         public decimal TotalPrice
  31:         {
  32:             get
  33:             {
  34:                 return (decimal)this.Details.Sum(detail => detail.Quantity * detail.UnitPrice);
  35:             }
  36:         }
  37:     }
  38: }

本文转载:CSDN博客