现在的位置: 首页 > 综合 > 正文

浅析Web Service数据转换器对象

2013年10月29日 ⁄ 综合 ⁄ 共 10632字 ⁄ 字号 评论关闭

意图: 减少消费者服务(Service)为了准备一次操作的消息(Message)频繁调用生产者服务的负载,

简而言之就是把多次调用“打包”。

 

   问题: Fowler, Martin的《Patterns of Enterprise Application Architecture》中提过这样一个应用情景:“When you're working with a remote interface, such as Remote Facade, each call to it is expensive. As a result you need to reduce the number of calls, and that means that you need to transfer more data with each call. One way to do this is to use lots of parameters. However, this is often awkward to program deed, it's often impossible with languages such as Java that return only a single value.”

 

    当我们调用一个远程接口的时候,一般而言代价相对比较大,需要通过一次调用中传递更多数据的办法减少调用次数。虽然一次返回多个参数是一个解决办法,但对于程序而言比较不可取,对于如Java之类的语言返回多个参数又不可能。

 

    SOA环境下,这个调用“打包”的需求更明显,主要原因如下:

 

    SOA的分布式环境,尤其当调用涉及到Internet调用时,时延更为明显。

 

    •为了服务接口的通用性,很多企业应用都会采用通用的数据参数类型(例如:XmlDocumentstring等),但对于最终的调用者而言很多数据对他没有必要,很可能消费者服务需要的就是其中某一个Node,甚至是某个Node的某些Attribute,为了减少不必要的数据传递,需要考虑采用某种措施对数据进行前置处理。

 

•消费者服务为了执行某个功能可能需要同时调用多个生产者服务。

 

    •同时这个过程可能是双向的,既存在消费者服务多次从生产者服务获取消息,也存在同时向多个生产者服务发送消息的情况,尤其在异步Web Service情况下,需要进行一个类似组播方式的1N的消息通知。即总体上看,同时存在writeread操作。

 

    为了提高消费者服务的使用效率,这类问题可以通过引入一个服务端数据传输对象解决(DTOData Transfer Object),由他将多次调用的工作“打包”成一次调用,他的主要作用是write / read数据,DTO将这些数据作为消费者服务所需要的消息参数,所有的参数都是临时的。从职能上看,DTO同时负责writeread,因此虽然上面3个图有多读、多写、多读多写三种方式,但对于DTO而言,设计出一组R/WPropertyIndexer即可。(对于Java等没有PropertyIndexer的语言,可以通过一组set()/get()实现,两种实现见附件部分。)

 

由于DTO的主要目的是完成分布式环境下的一次性调用要求,因此如果部署上每个生产者服务本身位于不同的物理服务器的时候,DTO本身并不会对性能体带来明显的提升,整个DTO会退化为一个近似多次调用的Remote Facade

 

Data Transfer Object与消费者服务的对应关系

    由于消费者服务可能会同时与很多领域的业务对象进行交互,因此如果为每个业务领域的调用设计一个独立的DTO,这样会引发消费者服务与过多的DTO对象间产生耦合,这时候可以考虑增加一个类似DTO容器的集合类型来管理,由于DTO中的数据都是临时性的,因此这个DTO集合包括的内容可以做的大一些(,毕竟数据也都是中转而已),然后这个集合同时服务于多个消费者服务,每个消费者仅仅从这个集合中获取自己需要的一组DTO对象。演化关系如下。

111组合

    这种方式适于目标服务相对数量很少,而且调用中需要用到“打包”情况不多的情况,采用一事一议的办法,为每类需要“打包”的业务领域对象包装一个DTO,客户程序调用这个DTO。(如果业务领域对象非常有限的时候,也可以采用一个DTO通过几组get()/set()分别的办法。)

1NN组合

    当一个客户应用的需要同时使用多个Web服务的内容时(尤其是根据某些条件,有选择的使用时),可以考虑建立一系列的DTO对象,分别对应不同的业务领域对象。

11NN组合

    当消费者服务需要同多个DTO交互的时候,为了简化这种耦合关系,提取一个集中的DTO集合类型,由他集中管理某个消费者服务需要的一组DTO对象,这样把消费者服务与DTO1M的关系,变成11M。不过这由于DTO集合相对更为固定,因此消费者服务自身不需要根据DTO频繁修改。

M1NN组合

    对于一个企业而言,DTO集合可以不仅仅面向一个消费者服务使用,它本身的作用就是一个DTO字典,因此可以把DTO集合适当扩大,令其可以同时服务与多个不同的消费者服务。

部署设计考虑

   
DTO不同,DTO Collection可以位于生产者服务端,也可以位于消费者服务端,因为它仅仅负责管理一组DTO对象的引用,考虑到组件实际执行能力的不同,消费者服务方可以仅仅保存抽象的DTO接口,而在具体业务领域对象一端保存实体DTO实现对象。

实现结构

    首先,考虑111方式的最精简实现方式:

    几个角色的说明:

IDataTransferObjectDTO对象的抽象接口,用于描述所有DTO应该具有的抽象行为,他会同时部署在DTO实体类和消费者服务方。

BusinessDomainObject是消费者服务实际需要使用的目标内容,但为了完成一个操作可能需要多次调用这个业务领域对象,因此需要在中间通过DTO进行调用“打包”。

DataTransferObjectImpl是面向具体某个业务领域对象的DTO实现,他同时实现了IDataTransferObject所定义的基本基本操作。

示例

示例情景

为了演示,设计了两个业务领域服务——“报价服务”和“联系人服务”,并为他们各自包装了一个独立的DTO对象,同时外层通过一个DTO集合进行集中管理。为了抽象的方便,根据应用情形定义如下抽象类型:

•将所有需要的业务数据对象定一个抽象的类型——BaseBusinessDomainObject
•将所有DTO对象定一一个接口——IDataTransferObject

•定义DTO集合,他的管理工作是基于抽象的IDataTransferObject进行的。

//C# BaseBusinessDomainObject 
namespace VisionTask.Training.ServicePattern.UtilityService
{
public abstract class BaseBusinessDomainObject {}
}
//C# IDataTransferObject
using System.Xml;
namespace VisionTask.Training.ServicePattern.UtilityService
{
/// <summary>
///
抽象的DTO 对象定义,包括“读/写”两个基本方法。
/// </summary>
public interface IDataTransferObject
{
/// <summary>
///
用于DTO 集合中检索每个DTO 对象的名称。

/// </summary>
string Name { get;}

/// <summary>
///
通过DTO 执行批量"读取/ 写入”单一消息内容结果。

/// </summary>
BaseBusinessDomainObject GetData();
void SetData(BaseBusinessDomainObject data);
}
}
//C# DataTransferObjectCollection
using System.Collections.Generic;
namespace VisionTask.Training.ServicePattern.UtilityService
{
/// <summary>
/// DTO
对象集合。

/// </summary>
public class DataTransferObjectCollection
{
private IDictionary<string, IDataTransferObject> dictionary =
new Dictionary<string, IDataTransferObject>();

public IDataTransferObject this[string name]
{
get
{
IDataTransferObject result;
if (!dictionary.TryGetValue(name, out result))
return null;
else
return result;
}
}

public void Add(IDataTransferObject dto) { dictionary.Add(dto.Name, dto); }
public void Remove(string name) { dictionary.Remove(name); }
}
}
 

实现简单的生产者服务

//C# QuoteService 
using System;
using System.Xml;
using System.Web.Services;
using VisionTask.Training.ServicePattern.DataTransferObject;
namespace VisionTask.Training.ServicePattern.UtilityService
{
/// <summary>
///
业务领域对象。
/// </summary>
public class Quote : BaseBusinessDomainObject
{
public struct QuoteItem
{
public string ProductId;
public double UnitPrice;
}

public string Id;
public string Company;
public QuoteItem[] Items;
}

/// <summary>
///
可以提供业务领域对象的服务,但为了获得完整的对象信息,消费者

///
服务需要多次调用服务方法,为此需要提供一个DTO 对象打包多次调用。

/// </summary>
[WebService]
public class QuoteService : WebService
{
/// <summary>
///
演示用的临时数据。

///
实际使用中该数据一般会从数据库或其他存储介质上获得。

/// </summary>
private static Quote quote = new Quote();
static QuoteService()
{
quote.Id = (new Random()).Next().ToString();
quote.Company = "vision task";
}

/// <summary>
///
一组细颗粒度的服务方法。

/// </summary>
[WebMethod]
public string GetId() { return quote.Id; }
[WebMethod]
public string GetCompany() { return quote.Company; }
[WebMethod]
public void SetId(string id) { quote.Id = id; }
[WebMethod]
public void SetCompany(string company) { quote.Company = company; }
}
}
//C# ContactService
using System;
using System.Xml;
using System.Web.Services;
using VisionTask.Training.ServicePattern.DataTransferObject;
namespace VisionTask.Training.ServicePattern.UtilityService
{
/// <summary>
///
业务领域对象。

/// </summary>
public class Contact : BaseBusinessDomainObject
{
public string Title;
public string Name;
public int Age;
}

/// <summary>
///
可以提供业务领域对象的服务,但为了获得完整的对象信息,消费者

///
服务需要多次调用服务方法,为此需要提供一个DTO 对象打包多次调用。

/// </summary>
[WebService]
public class ContactService : WebService
{
/// <summary>
///
演示用的临时数据。

///
实际使用中该数据一般会从数据库或其他存储介质上获得。

/// </summary>
private static Contact contact = new Contact();
static ContactService()
{
contact.Title = "S. Manager";
contact.Name = "joe";
contact.Age = 20;
}

/// <summary>
///
一组细颗粒度的服务方法。

/// </summary>
[WebMethod]
public string GetTitle() { return contact.Title; }
[WebMethod]
public string GetName() { return contact.Name; }
[WebMethod]
public int GetAge() { return contact.Age; }
}
}
 

为生产者服务包装DTO对象

//C# QuoteDtoService 
using System;
using System.Web.Services;
using System.Xml.Serialization;
using VisionTask.Training.ServicePattern.DataTransferObject;
using VisionTask.Training.ServicePattern.UtilityService.QuoteProducerService;
namespace VisionTask.Training.ServicePattern.UtilityService
{
/// <summary>
///
面向报价服务的专用DTO对象。
///
没有该对象时,消费者服务调用QuoteService获得一个完整的报价信息

///
需要两次往返,通过该DTO 对象的打包,仅需要一次。

/// </summary>
[WebService]
public class QuoteDtoService : WebService, IDataTransferObject
{
public string Name { get { return "Quote"; } }

[WebMethod]
[XmlInclude(typeof(Quote))]
public BaseBusinessDomainObject GetData()
{
Quote quote = new Quote();
QuoteService service = new QuoteService();
quote.Id = service.GetId();
quote.Company = service.GetCompany();
return quote;
}

[WebMethod]
[XmlInclude(typeof(Quote))]
public void SetData(BaseBusinessDomainObject data)
{
if (data == null) throw new NullReferenceException();
Quote quote = (Quote)data;
QuoteService service = new QuoteService();
service.SetId(quote.Id);
service.SetCompany(quote.Company);
}
}
}
//Unit Test

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionTask.Training.ServicePattern.UtilityService;
namespace VisionTask.Training.ServicePattern.UtilityService.UnitTest
{
[TestClass()]
public class QuoteDtoServiceTest
{
[TestMethod]
public void Test()
{
// DTO
IDataTransferObject dto = new QuoteDtoService();
Quote quote = (Quote)dto.GetData();
Assert.AreEqual<string>("vision task", quote.Company);
}
}
}
//C# ContactDtoService
using System;
using System.Web.Services;
using System.Xml.Serialization;
using VisionTask.Training.ServicePattern.DataTransferObject;
using VisionTask.Training.ServicePattern.UtilityService.ContactProducerService;
namespace VisionTask.Training.ServicePattern.UtilityService
{
/// <summary>
///
面向联系人服务的专用DTO对象。

///
没有该对象时,消费者服务调用ContactService 获得一个完整的联系人信息.

///
需要三次往返,通过该DTO 对象的打包,仅需要一次。

/// </summary>
[WebService]
public class ContactDtoService : WebService, IDataTransferObject
{
public string Name { get { return "Contact"; } }

[WebMethod]
[XmlInclude(typeof(Contact))]
public BaseBusinessDomainObject GetData()
{
Contact contact = new Contact();
ContactService service = new ContactService();
contact.Title = service.GetTitle();
contact.Name = service.GetName();
contact.Age = service.GetAge();
return contact;
}

[WebMethod]
public void SetData(BaseBusinessDomainObject data)
{
///
暂时不支持回写服务。

throw new NotSupportedException();
}
}
}
//Unit Test

using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionTask.Training.ServicePattern.UtilityService.UnitTest.ContactService;
using VisionTask.Training.ServicePattern.UtilityService;
namespace VisionTask.Training.ServicePattern.UtilityService.UnitTest
{
[TestClass()]
public class ContactDtoServiceTest
{
[TestMethod]
public void Test()
{
// DTO
IDataTransferObject dto = new ContactDtoService();
Contact contact = (Contact)dto.GetData();
Assert.AreEqual<string>("S. Manager", contact.Title);
Assert.AreEqual<string>("joe", contact.Name);
Assert.AreEqual<int>(20, contact.Age);
}
}
}
 

使用DTO集合的测试

//Unit Test 
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionTask.Training.ServicePattern.UtilityService.UnitTest.ContactService;
using VisionTask.Training.ServicePattern.UtilityService;
namespace VisionTask.Training.ServicePattern.UtilityService.UnitTest
{
[TestClass()]
public class DataTransferObjectCollectionTest
{
[TestMethod]
public void Test()
{
//
模拟初始时 DTO 集合的构造过程。

DataTransferObjectCollection dtos = new DataTransferObjectCollection();
dtos.Add(new QuoteDtoService());
dtos.Add(new ContactDtoService());

//
消费者服务通过 DTO 集合获取需要的 DTO 对象。

IDataTransferObject dto = dtos["Contact"];
Contact contact = (Contact)dto.GetData();
Assert.AreEqual<string>("S. Manager", contact.Title);
Assert.AreEqual<string>("joe", contact.Name);
Assert.AreEqual<int>(20, contact.Age);
}
}
}
 

小结

    实际项目中,精简模式的DTO模式往往是最容易遭到开发人员反对的一个模式,因为它简单到就是简单的read / write,是否使用DTO模式最主要的因素在于权衡效率与工作量后的结果

附件----实现DTO数据装载的2种方式

//C#
namespace VisionTask.Training.ServicePattern.DataTransferObject.Raw
{
/// <summary>
///
需要通过多次调用生产者服务“拼凑”的消息。
/// </summary>
struct Quote
{
public struct QuoteItem
{
public string ProductId;
public double UnitPrice;
}

public string Id;
public string Company;
public QuoteItem[] Items;
}

/// <summary>
/// 通过Property + Indexer 方式实现的DTO 操作方式。
/// </summary>
class DataTransferObjectA
{
private Quote quote = new Quote();

public string Id
{
get { return quote.Id; }
set { quote.Id = value; }
}

public string Company
{
get { return quote.Company; }
set { quote.Company = value; }
}

public Quote.QuoteItem this[int index]
{
get { return quote.Items[index]; }
set { quote.Items[index] = value; }
}
}

/// <summary>
/// 通过Get() + Set() 方式实现的DTO 操作方式。
/// </summary>
class DataTransferObjectB
{
private Quote quote = new Quote();

public string GetId()
{
return quote.Id;
}

public string GetCompany()
{
return quote.Company;
}

public void SetId(string id)
{
quote.Id = id;
}

public void SetCompany(string company)
{
quote.Company = company;
}

public double GetUnitPrice(int index)
{
return quote.Items[index].UnitPrice;
}

public void SetUnitPrice(int index, double unitPrice)
{
quote.Items[index].UnitPrice = unitPrice;
}

public string GetProductId(int index)
{
return quote.Items[index].ProductId;
}

public void SetProductId(int index, string productId)
{
quote.Items[index].ProductId = productId;
}
}
}

 

1

 
   
资料来源:http://tech.it168.com/m/2007-08-15/200708150951837_4.shtml
   
非常感谢 IT168  王翔软件工程师

抱歉!评论已关闭.