• 领域驱动设计案例:Tiny Library:应用服务层


    Tiny Library使用应用服务层向用户界面层提供服务,具体实现是采用Microsoft WCF Services。在Tiny Library的解决方案中,是由TinyLibrary.Services项目为整个系统提供这一WCF服务的。按照传统的应用系统分层方法,TinyLibrary.Services项目位于领域模型层之上、用户界面层之下,它是UI与Domain的交互界面。TinyLibrary.Services的实现中,与DDD相关的内容主要是数据传输对象(DTO),至于如何编写与实现WCF服务,那是.NET技术上的问题,本文不会做太多的讨论。

    数据传输对象(Data Transferring Object,DTO)

    在TinyLibrary.Services中,有一种特殊类型的对象,我们称之为数据传输对象。根据Fowler在PoEAA一书中的描述,DTO用于进程间数据交换,由于DTO是一种对象,它能够包含很多数据信息,因此,DTO的采用可以有效减少进程间数据交换的来回次数,从而在一定程度上提高网络传输效率。

    由于DTO需要跨越进程边界(在我们的案例中,需要跨越网络边界),因此,DTO是可以被序列化/反序列化的,它不能包含上下文相关的信息(比如,Windows句柄)。正因为如此,DTO中的数据类型都是很简单的,它们可以是原始数据类型(Primitive Data Types)或者是其它的DTO。

    有些朋友在阅读Fowler的PoEAA一书时,对于DTO的理解还是有困难,我在此将我的理解写下来,供大家参考

    1. 领域模型对象不负责任何数据传输的功能,这是因为,如果将领域模型对象用于数据传输,则势必需要在另一个层面(或者另一个进程中)产生一个领域模型对象的副本,此时才有可能在服务器与客户机之间产生一种数据契约,而这种做法违背了层内高内聚,层间低耦合的原则
    2. DTO是简单而原始的,并且是运行时上下文无关的。这是为了满足序列化/反序列化的需求。因此,DTO所包含的数据要不就是原始数据类型,要不就是其它的DTO
    3. 客户端需求决定DTO的设计。因此,DTO与领域模型对象并非一一对应的关系,相反,它是为了满足客户端需求而存在的

    在Tiny Library案例中,我选用了WCF的Data Contracts作为DTO的实现标准,因为这样做不仅能够基于客户端需求设计合理的DTO结构,而且还可以利用WCF的DataContractSerializer实现DTO的序列化/反序列化。不仅如此,WCF为我们在服务器与客户机通讯上提供了技术支持和有力保障。

    打开TinyLibrary.Services项目,我们可以看到一个IDataObject的泛型接口,其定义如下:

    隐藏行号 复制代码 IDataObject定义
    1. public interface IDataObject<TEntity> where TEntity : IEntity
      
    2. {
      
    3.     void FromEntity(TEntity entity);
      
    4.     TEntity ToEntity();
      
    5. }
      
    6. 
      

    我们可以要求所有的DTO都实现这个接口,以便使其具有将实体转换为DTO,或者将DTO转换为实体的能力。在Tiny Library案例中,我并没有强制要求所有的DTO都实现这个接口(也就是说,Tiny Library本身不规定DTO必须实现这个接口),我引入这个接口的目的,就是想说明,其实我们是可以这样做的:在我们的系统中为DTO设计好一个合理的框架,以便让DTO获得更强大的功能。引入这个接口的另一个目的就是为了编程方便:实现基于某个实体的DTO,只需要使其实现IDataObject接口即可,Visual Studio会自动产生方法桩(Method Stubs),无需手动编写代码。

    请注意TinyLibrary.Services.DataObjects.RegistrationData这个类,它与BookData、ReaderData有很大的区别,它并不是Registration实体的映射体现,换句话说,它所包含的所有状态属性,并不是与Registration实体所包含的属性一一对应。例如,RegistrationData这个DTO中包含书名(BookTitle)以及ISBN号(BookISBN)的信息,而这些信息都是来自于Registration的关联实体:Book。RegistrationData的设计完全是为了迎合用户界面,因为在UI上,我们需要针对某个读者列出他/她的借书信息,而RegistrationData包含了这所有需要的信息。

    应用层职责

    在《Entity Framework之领域驱动设计实践》系列文章中,我曾经提到过,根据DDD,应用系统分为四层:展现层、应用层、领域层和基础结构层。在Tiny Library案例中,TinyLibrary.Services充当了应用层的职责,它不负责处理任何业务逻辑,而是从更高的层面,为业务逻辑的正确执行提供适当的运行环境,同时起到任务协调的作用(比如事务处理和基础结构层服务调用)。

    隐藏行号 复制代码 WCF Service中“还书”操作的具体实现
    1. public void Return(string readerUserName, Guid bookId)
      
    2. {
      
    3.     try
      
    4.     {
      
    5.         using (IRepositoryTransactionContext ctx = ObjectContainer
      
    6.             .Instance
      
    7.             .GetService<IRepositoryTransactionContext>())
      
    8.         {
      
    9.             IRepository<Book> bookRepository = ctx.GetRepository<Book>();
      
    10.             IRepository<Reader> readerRepository = ctx.GetRepository<Reader>();
      
    11.             Reader reader = readerRepository.Find(Specification<Reader>.Eval(r => r.UserName.Equals(readerUserName)));
      
    12.             Book book = bookRepository.GetByKey(bookId);
      
    13.             reader.Return(book);
      
    14.             ctx.Commit();
      
    15.         }
      
    16.     }
      
    17.     catch
      
    18.     {
      
    19.         throw;
      
    20.     }
      
    21. }
      
    22. 
      

    上面的代码展示了“还书”操作的具体实现方式,我们可以看到,位于应用层的WCF Services仅仅是协调仓储操作和事务处理,业务逻辑由reader.Return方法实现:

    隐藏行号 复制代码 TinyLibrary.Domain.Reader的Return方法
    1. public void Return(Book book)
      
    2. {
      
    3.     if (!book.Lent)
      
    4.         throw new InvalidOperationException("The book has not been lent.");
      
    5.     var q = from r in this.Registrations
      
    6.             where r.Book.Id.Equals(book.Id) &&
      
    7.             r.RegistrationStatus == RegistrationStatus.Normal
      
    8.             select r;
      
    9.     if (q.Count() > 0)
      
    10.     {
      
    11.         var reg = q.First();
      
    12.         if (reg.Expired)
      
    13.         {
      
    14.             // TODO: Reader should pay for the expiration.
      
    15.         }
      
    16.         reg.ReturnDate = DateTime.Now;
      
    17.         reg.RegistrationStatus = RegistrationStatus.Returned;
      
    18.         book.Lent = false;
      
    19.     }
      
    20.     else
      
    21.         throw new InvalidOperationException(string.Format("Reader {0} didn't borrow this book.",
      
    22.             this.Name));
      
    23. }
      
    24. 
      

    配置文件

    TinyLibrary.Services是整个案例的服务供应者(Service Provider),因此,整个系统服务器端的配置与初始化应该由TinyLibrary.Services启动时负责执行。因此,基于服务器的系统配置应该写在TinyLibrary.Services项目的app.config中。其中包括:Apworks的配置、Unity(或者Castle Windsor)的配置、WCF Services的配置以及Entity Framework所使用的数据库连接字符串的设置。

    从实践角度考虑,在基于CQRS体系结构模式的应用系统中,各个组件的初始化和配置逻辑应该位于WCF Services的Global.asax文件中,以便在WCF Service Application启动的时候,所有组件都能成功地初始化。我将在今后的CQRS案例中进一步描述这一点。

    至此,Tiny Library的服务端部分基本介绍完毕,回顾一下,这些内容包括:领域建模、仓储实现和应用服务层。下一讲将简单介绍一下Tiny Library的Web界面的设计与开发。由于本系列文章的重点不是讨论某个技术的具体实现,因此,在下一讲中不会涉及太多有关ASP.NET MVC的细节内容。

  • 相关阅读:
    为archlinux安装mplayer
    linux与windows的文本文件之间的转换
    有关git的换行符的处理问题
    让git忽略文件模式的改变
    linux更新系统之后,删除多余的开机启动项
    关于centos更新后virtualbox无法使用的问题
    SQL 中逻辑运算符的优先级
    archlinux安装输入法需要的包及archlinux无法使用输入法的解决
    从前有座山,山里有座庙
    批量修改照片名称的shell脚本
  • 原文地址:https://www.cnblogs.com/daxnet/p/1866797.html
Copyright © 2020-2023  润新知