代码改变世界

[转载]AAF灵便应用框架简介系列(4):对象的持久化

2009-04-02 09:32  周国选  阅读(350)  评论(0编辑  收藏  举报

下面我们一起来看看灵便对象的持久化。

灵便对象的持久化在某些情况下确实可以简单到如前言中的例子一般,有时候我们则可能需要更多的配置和设定。下面我们从一个更实际的例子出发,来看看在各种不同的现实考量下,我们需要做哪些工作。

这个例子是要建立一个订单管理系统的业务对象模型及其实现(我们不考虑界面,在业务对象实现完毕之后,界面会是很快的一件事情)。在考虑具体的对象模型之前,我们作这样几个假设:
0)我们先不考虑订单的处理,而只考虑下单和订单查询逻辑
1)商品目录不限定层数,可以为任意多层,只有在叶子节点下可以增加代售商品
2)订单查询中可以通过商品目录的叶子节点(即没有子目录的目录)查询
3)订单查询中无需通过商品目录的非叶子节点查询
4)物品只限注册用户购买
5)先不考虑权限和后台用户管理

自然而然的,我们需要至少这样几个业务对象:
User(注册用户)
Category(商品目录)
Product(商品)
Order(订单)
另外我们还需要几个业务管理对象(我习惯将其称为业务服务对象):
UserService
ProductService
OrderService
下面我们看看,我们建立一个应用的对象模型并实现之是多快多爽的一件事情。

为了简单一点,我们把能实现为字段的属性都实现为字段,虽然在实际应用中我们更倾向于实现为属性。
我们首先来写看看业务对象代码:

using System;

using Aaf.Agile;
using Aaf.Agile.Imp;

namespace AafSample
{
 // 用户
 public class User : AgileObject
 {
  public string Name;
  public string LoginId;
  public string Email;
  public string CellPhone;
 }

 // 商品目录
 public class Category : AgileObject
 {
  public string Name;
  public string Description;
  [Relation(RelationType = typeof(FreeAgileRelation), ChildType = typeof(Category))]
  public IAgileRelation SubCats;
 }

 // 商品
 public class Product : AgileObject
 {
  public string CatId;
  public string Name;
  public string Description;
  public string UnitName;//单位
  public decimal Price;
 }

 // 订单
 public class Order : AgileObject
 {
  [Relation(RelationType = typeof(FreeAgileRelation), ChildType = typeof(OrderItem))]
  public IAgileRelation OrderItems;// 订单明细集合
  public DateTime CreatedTime = DateTime.Now;
  
  public decimal Sum // 订单总金额
  {
   get
   {
    decimal sum = 0;
    foreach(OrderItem item in this.OrderItems)
    {
     sum += item.ItemSum;
    }
   
    return sum;
   }
  }
 }

 // 订单明细
 public class OrderItem : AgileObject
 {
  public string ProdId;//订购物品编号
  public decimal Quantity;//订购数量
  public decimal UnitPrice;//单价
  public decimal ItemSum;//订单项金额
 }

}

业务对象代码就这些,写完了,快吗。任意层次目录问题(Category、Product可以实现为Composite模式,但为了使结构看上去尽可能简单点,以便大家快速理解,我们宁愿有一点冗余代码),订单及订单项关系问题就这么几行代码就完成了。

下面我们看看几个Service的代码。在实现一部分逻辑之前,首先定义这部分逻辑对外提供的接口,是我的开发习惯。这个习惯无论是作为传统分析、设计的一个重要步骤还是作为XP中的一个“自选动作”,都能起到帮助我们迅速理清思路,找到问题最佳解的作用。但是,我们今天暂时略过这一步骤,其目的仍然是为了“使结构看上去尽可能简单点,以便大家快速理解”。

下面我们来看看业务服务的代码:
using System;

using Aaf.Agile;
using Aaf.Core;
using Aaf.Persistence;

namespace AafSample
{
 public class ProductService
 {
  private const string RootCatId = "27FDA2D79C27429895D7F2479FFA278C";

  private static ProductService s_Instance = new ProductService();
  private Category _rootCat;

  public static ProductService Instance
  {
   get
   {
    return s_Instance;
   }
  }
  
  private static ITypeService TypeService
  {
   get
   {
    return (ITypeService)ServiceHub.Instance.GetService(typeof(ITypeService));
   }
  }

  private static IPersister Persister
  {
   get
   {
    return (IPersister)ServiceHub.Instance.GetService(typeof(IPersister));
   }
  }

  private ProductService()
  {
   // 检查是否存在根目录,不存在就创建。
   _rootCat = this.GetCategory(RootCatId);
   if (_rootCat == null)
   {
    _rootCat = (Category)TypeService.CreateInstance(typeof(Category));
    _rootCat.Id = RootCatId;

    this.SaveCategory(_rootCat);
   }
  }

  // 在指定目录Id下创建一个子目录,本方法并不提交保存,外部调用者可以在自己认为恰当的时间保存
  // parentId指向的父目录,从而提交本次修改。注意,仅仅保存本方法返回的新目录实例是不够的,
  // 因为这样做,只会保存新目录实例,而不会保存父目录与新目录实例之间的关系项。
  //
  // 实际上,还有一种更高级的做法,可以使得外部只要保存新目录实例就可以保存它与父实例之间的关
  // 系,但是这一办法将在“对象的持久化(高级)”一文中论述。
  public Category CreateCategory(string parentId, string catName, string catDescription)
  {
   if (parentId == null)
   {
    return null;
   }

   Category parentCat = (Category)Persister.LoadAgileObject(typeof(Category), parentId);

   if (parentCat != null)
   {
    Category cat = (Category)TypeService.CreateInstance(typeof(Category));
    cat.Name = catName;
    cat.Description = catDescription;
    parentCat.SubCats.Add(cat);

    return cat;
   }

   return null;
  }

  // 挂起一个目录。挂起的具体意义有应用自己决定。我们可以在这里约定挂起的目录不会在界面中显示出来
  // 同样的,对象修改后的保存都由外部调用者在恰当的时机显式调用相关保存方法予以保存。原则上,服务
  // 方法中不主动保存任何对象(除了一些内置的根节点之外)。其它方法与此类似,以后不再反复说明。
  public Category SuspendCategory(string catId)
  {
   Category cat = (Category)Persister.LoadAgileObject(typeof(Category), catId);

   if (cat != null)
   {
    cat.Suspend();

    return cat;
   }

   return null;
  }

  // 获取根目录
  public Category GetRootCategory()
  {
   return _rootCat;
  }

  // 根据指定Id获取一个目录
  public Category GetCategory(string catId)
  {
   Category cat = (Category)Persister.LoadAgileObject(typeof(Category), catId);

   if (cat != null)
   {
    return cat;
   }

   return null;
  }

  // 获取一个目录的所有子目录
  public Category[] GetSubCategories(string parentId)
  {
   Category cat = GetCategory(parentId);

   if (cat != null)
   {
    return (Category[])cat.SubCats.CopyTo(typeof(Category), 0);
   }

   return new Category[0];
  }

  // 保存一个目录
  public void SaveCategory(Category cat)
  {
   Persister.SaveAgileObject(cat);
  }
 
  // 在指定目录下,创建一个商品
  public Product CreateProduct(string catId, string prodName, string prodDescription, string unitName, decimal price)
  {
   Category cat = GetCategory(catId);

   if (cat != null)
   {
    Product prod = (Product)TypeService.CreateInstance(typeof(Product));

    prod.CatId = catId;
    prod.Name = prodName;
    prod.Description = prodDescription;
    prod.UnitName = unitName;
    prod.Price = price;

    return prod;
   }

   return null;
  }

  // 获取指定目录下的所有商品。在现实应用中,我们很少一次读取一个目录下的所有商品对象。为了性能,考虑到
  // 商品列表页面大量商品同时显示的特点(“大量”和“显示”是关键词,这意味着量很大,但只是看),我们
  // 往往会采取数据库段分页前台DataReader生成页面的方式,而绕过大量对象的加载。这一点将在“对象的持久化
  // (高级)”一文中讲到。此处为了主题突出,暂且如此。
  public Array GetProducts(string catId)
  {
   return Persister.LoadAgileObject(typeof(Product), new AgileObjectFilter(TypeService.GetObjectTypeDescription(typeof(Product)), "CatId={0}", new object[]{catId}));
  }

  public Product GetProduct(string prodId)
  {
   Product prod = (Product)Persister.LoadAgileObject(typeof(Product), prodId);

   if (prod != null)
   {
    return prod;
   }

   return null;
  }

  public Product SuspendProduct(string prodId)
  {
   Product prod = (Product)Persister.LoadAgileObject(typeof(Product), prodId);

   if (prod != null)
   {
    prod.Suspend();

    return prod;
   }

   return null;
  }

  public void SaveProduct(Product prod)
  {
   Persister.SaveAgileObject(prod);
  }

 }

 public class UserService
 {
  private static UserService s_Instance = new UserService();

  public static UserService Instance
  {
   get
   {
    return s_Instance;
   }
  }

  private static ITypeService TypeService
  {
   get
   {
    return (ITypeService)ServiceHub.Instance.GetService(typeof(ITypeService));
   }
  }

  private static IPersister Persister
  {
   get
   {
    return (IPersister)ServiceHub.Instance.GetService(typeof(IPersister));
   }
  }

  private UserService()
  {
  }

  // 创建一个用户,我们暂不考虑loginId重复检测、用户密码等问题
  public User CreateUser(string name, string loginId)
  {
   User user = (User)TypeService.CreateInstance(typeof(User));
   user.Name = name;
   user.LoginId = loginId;

   return user;
  }

  // 根据Id获取一个用户
  public User GetUser(string uid)
  {
   return (User)Persister.LoadAgileObject(typeof(User), uid);
  }

  // 根据登录名获取一个用户
  public User GetUserByLoginId(string loginId)
  {
   return (User)Persister.LoadAgileObject(typeof(User), new AgileObjectFilter(TypeService.GetObjectTypeDescription(typeof(User)), "LoginId={0}", new object[]{loginId}))[0];
  }
 }


 public class OrderService
 {
  private static OrderService s_Instance = new OrderService();

  public static OrderService Instance
  {
   get
   {
    return s_Instance;
   }
  }

  private static ITypeService TypeService
  {
   get
   {
    return (ITypeService)ServiceHub.Instance.GetService(typeof(ITypeService));
   }
  }

  private static IPersister Persister
  {
   get
   {
    return (IPersister)ServiceHub.Instance.GetService(typeof(IPersister));
   }
  }

  private OrderService()
  {
  }

  // 创建一个用户,我们暂不考虑loginId重复检测、用户密码等问题
  public Order CreateOrder(string prodId)
  {
   Order order = (Order)TypeService.CreateInstance(typeof(Order));
   
   return order;
  }

  // 为订单创建一个订单项
  public OrderItem CreateOrderItem(Order order, string prodId, decimal quantity)
  {
   if (order != null)
   {
    Product prod = ProductService.Instance.GetProduct(prodId);

    if (prod != null)
    {
     OrderItem orderItem = (OrderItem)TypeService.CreateInstance(typeof(OrderItem));
     orderItem.ProdId = prodId;
     orderItem.Quantity = quantity;
     orderItem.UnitPrice = prod.Price;
     order.OrderItems.Add(orderItem);

     return orderItem;
    }
   }

   return null;
  }

  // 保存订单,订单项无需单独保存,只要保存订单,所属订单项就会自动与订单在同一个事务中被保存
  public void SaveOrder(Order order)
  {
   Persister.SaveAgileObject(order);
  }

  // 获取一段时间内的所有订单,在西安市系统中的考虑与ProductService的GetProducts方法类似
  public Array GetOrders(DateTime from, DateTime to)
  {
   return Persister.LoadAgileObject(typeof(Order), new AgileObjectFilter(TypeService.GetObjectTypeDescription(typeof(Order)), "CreatedTime>={0} and CreatedTime<{1}", new object[]{from, to}));
  }
 }
}

这些代码我是在抽空写文章的同时现写的。好了,已经编译通过了。当然,在此之前,我刚刚创建一个新的C#类库项目,在引用中增加了对如下程序集的引用:Aaf.Agile.dll, Aaf.Agile.Imp.dll, Aaf.Core.dll, Aaf.ObjectModel.dll(因为历史原因,这个类库有点名不副实),Aaf.Persistence.dll。

那么现在我们这个类库是不是就可以运行了呢,比如通过NUnit测试用例(本文就不涉及测试用例的编写了)?实际上,确实差不多可以运行了,但是还是要作如下检查:
1)所有实现程序集是不是和接口程序集一道部署在运行目录下了。虽然在引用时只需要引用各服务的接口程序集,在运行的时候,各服务的实现显然还是需要的。
3)Boot.Config是否配置妥当?因为本文中的业务服务并没有被实现为AAF服务,所以并不要改动原有的Boot.Config。
4)确保Storage.Config存在并且其内容至少包含一个Name为""的StorageContext(存储上下文),因为没有作任何显式映射,所有对象都将被持久化到这个默认存储上下文。应用开发人员应确保Storage.Config中定义的“DataSource”和“InitialCatalog”确实存在,而数据表的创建和维护由内核负责。下面是一个典型Storage.Config的内容:
  <?xml version="1.0" encoding="utf-8" ?>
    <StorageContexts>
        <StorageContext name=""><!-- SqlSample -->
            <DriverClass>Aaf.Storage.Imp.SqlServer.Driver, Aaf.Storage.Imp.SqlServer</DriverClass>
            <PersistenceDriver>Aaf.Persistence.Imp.SqlServer.PersistenceDriver, Aaf.Persistence.Imp</PersistenceDriver>
            <Provider></Provider>
            <DataSource>yourdb</DataSource>
            <InitialCatalog>aaftest</InitialCatalog>
            <UserID>sa</UserID>
            <Password>123456</Password>
            <Enabled>true</Enabled>
            <MinimunConnections>10</MinimunConnections>
            <MaximumConnections>500</MaximumConnections>
            <ConnectionTimeout>60</ConnectionTimeout>
            <ConnectionReaperDelay>600</ConnectionReaperDelay>
        </StorageContext>
    </StorageContexts>