技术开发 频道

浅析Web Service适配器

意图

    将现有Web Service服务(生产者服务)接口转化为客户Web Service(消费者服务)需要的目标服务接口。

问题

    由于Web Service的设计目标最主要就是解决连通性问题,提供一个Internet环境下的分布式计算环境,在SOA架构日益广泛使用的今天,应用于应用间的直接对话更多的成为了Web Service对Web Service间的直接调用。 

    Web Service的调用是以WSDL为约束,基于SOAP的,因此与类间的Adapter适配器实现还有些不同,确切地说是有些自己的特色。

讨论 

    一般而言下面三个情况需要进行Web Service间的适配: 

    •WSDL中定义的服务方法不兼容:名称、返回值、参数列表。 

    •有时候为了接口的通用性,很多Web Service在定义的时候都采用了通用类型,例如:Xml、string,但实际上保存的是特定XSD定义下的XML数据,因此虽然参数没有变化,但数据的Schema发生变化的时候同样需要适配。 

    •原Web Service本身的接口比较初级(或比较原始),客户Web Service需要使用相对封装好的服务方法。 

    实际使用中,还很有可能是上面2种或3种情况的组合。调用方式上,既有可能是单向的“生产者+消费者”方式,也可能是互为“生产者+消费者”。


图1:Web Service间的调用模式

连续适配

    下面有个问题,与一般的独立应用不同, SOA环境中每个Web Service更多的是通过Registry的登记,属于即插即用类型的,而且为了整合业务流程常常存在同时多次适配的情况,这种情况下怎么处置呢?可以通过增加一个ServiceAdapterChain来解决。


图2:通过增加ServiceAdapterChain进行连续适配

实现结构

    先从调用模式上分析,对于单向的“生产者+消费者”方式,那么仅需要在消费者服务一方增加对生产者服务的适配器;如果是另一种情况,则需要两个适配器。


图3:单向的“生产者+消费者”


图4:双向的“生产者+消费者”

    不过上面仅分析了不同调用模式下大概的实现方式,对于怎么解决具体某个Adapter还需要分上面提的三种情况分别对待:

1、 数据Schema适配 

    Schema的适配可以采用用代码实现,如果转换过程不是特别复杂,不需要使用外部非XML机制的话,也可以采用更简洁的XSLT方式。

//处理方式 XSD(source)-> XSLT(source->target)-> XSD(target)

    考虑到这种数据Schema的适配过程可能不止一个,不同的服务方法间适配可能也会遇到相同数据Schema对之间的适配过程,因此实现上增加一个XSLT的注册机制,统一管理这个登记、翻译和转换工作。

2、 服务方法兼容性适配 

    参数列表、返回值、参数类型都会导致生产者服务与消费者服务间的不兼容的。

3、 封装性适配

    对于2、3两种情况而言,与设计模式中的Adapter没有太大差别,不过多了一个选择,“是否考虑把Adapter作为一个独立的Web Service提供出来?”如果这么做,那么最大的区别在于消息机制的变化,从二进制(或文本数据)调用变成了SOAP调用。但这样做有一些很明显的不利因素,因此不建议把Service Adapter作为独立的Web Service提供。 

    •效率问题。 

    •最后,消费者服务仍然需要通过某种开发语言调用这个Adapter Service,这和写个Service Adapter类解决问题的方式一样的麻烦。

示例 

    为了统一各适配器的统一实现,先定义了一个统一适配器接口IServiceAdapter:


UML 适配器接口的根对象

//C#
namespace VisionTask.Training.ServicePattern.ServiceAdapter
{
/// <summary>
/// 所有Web Service 适配器的根接口
/// </summary>
public interface IServiceAdapter
{
string Name { get;}
}
}

单纯数据XSD适配

1、准备


图5:生产者服务接口返回的报价信息Schema定义


图6:消费者服务接口返回的报价信息定义


图7:两个XSD之间的映射关系

//XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl=http://www.w3.org/1999/XSL/Transform
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:n1=http://www.visionlogic.com/trade
xmlns:xs=http://www.w3.org/2001/XMLSchema
xmlns=http://www.visionlogic.com/trade
exclude-result-prefixes="n1 xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/n1:Quote">
<Quote>
<xsl:attribute name="xsi:schemaLocation">
http://www.visionlogic.com/trade QuoteTarget.xsd</xsl:attribute>
<xsl:for-each select="@Id">
<xsl:attribute name="SequenceNo">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:for-each select="@Company">
<xsl:attribute name="CompanyName">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
</Quote>
</xsl:template>
</xsl:stylesheet>

2、定义抽象的Service Adapter

    为了进一步扩展的考虑,这里定义一个IXsdServiceAdapter的抽象数据适配接口,由于这个接口操作的内容一般而言都是Web Service调用过程中的XML内存文件,因此该接口仅有一个统一的Transform方法。


UML 抽象Adapter的定义

//C#
using System.Xml;
using VisionTask.Training.ServicePattern.Common.Xml;
namespace VisionTask.Training.ServicePattern.ServiceAdapter
{
/// <summary>
/// 所有单纯XSD 数据适配器的根接口
/// </summary>
public interface IXsdServiceAdapter : IServiceAdapter
{
/// <summary>
/// 该适配器需要使用的XSLT 定义文件。
/// </summary>
string Xslt { get; set;}
/// <summary>
/// 依据XSLT 对XML 数据进行转换。
/// </summary>
XmlDocument Transform(XmlDocument source);
}

/// <summary>
/// 抽象IXsdServiceAdapter 实现。
/// </summary>
public abstract class BaseXsdServiceAdapter : IXsdServiceAdapter
{
private string xslt;
public BaseXsdServiceAdapter(string xslt) { this.xslt = xslt; }

public virtual string Xslt
{
get { return xslt; }
set { xslt = value; }
}
public virtual XmlDocument Transform(XmlDocument source)
{
return XmlHelper.Transform(xslt, source);
}
public abstract string Name { get;}
}
}

3、定义作为Adaptee的生产者Web Service

//C#
using System;
using System.Text;
using System.Xml;
using System.Web.Services;
namespace VisionTask.Training.ServicePattern.UtilityService.XsdAdapter
{
[WebService(Namespace = "http://www.visionlogic.com/trade")]
public class ProducerService : System.Web.Services.WebService
{
/// <summary>
/// 这里的XmlDocument 是遵循Quote.xsd 的数据。
/// </summary>
/// <returns></returns>
[WebMethod]
public XmlDocument GetQuote(string company, string id)
{
string quoteContent = string.Format(Resource.Quote, company, id);
XmlDocument doc = new XmlDocument();
doc.LoadXml(quoteContent);
return doc;
}
}
}
//Unit Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UtilityService.UnitTest.XsdProducer;
using System.Xml;
namespace UtilityService.UnitTest.XsdAdapter
{
[TestClass()]
public class ProducerServiceTest
{
[TestMethod]
public void Test()
{
ProducerService service = new ProducerService();
string company = "VisoinTask";
string id = "2007-07-21";
XmlNode node = service.GetQuote(company, id);
Assert.IsNotNull(node);
// 这里的 attribute 名称与 Quote.xsd一致。
Assert.AreEqual<string>(id, node.Attributes["Id"].Value);
Assert.AreEqual<string>(company, node.Attributes["Company"].Value);
}
}
}

 

 

4、定义作为客户程序的消费者 Web Service


UML Consumer Web Service的定义

    同时,GetQuote方法也作为Service Adapter所必需适配的接口出现(ITarget)。

5、根据消费者Web Service的需要,定义对应的Service Adapter 

    在该Adapter内部完成基于Quote.Xslt的源XML(遵循Quote.xsd)数据到目标XML(遵循QuoteTarget.xsd)的转换。

//C#
using System.Text;
using System.Xml;
using VisionTask.Training.ServicePattern.Common.Xml;
using VisionTask.Training.ServicePattern.ServiceAdapter;
namespace VisionTask.Training.ServicePattern.UtilityService.XsdAdapter
{
/// <summary>
/// XSD 适配方式下的报价服务的Service Adapter。
/// </summary>
public class QuoteXsdServiceAdapter : BaseXsdServiceAdapter, ITarget
{
/// <summary>
/// XSLT 文件需要根据Web Service 的运行位置部署。
/// </summary>
public QuoteXsdServiceAdapter() : base ("Quote.xslt"){}
public override string Name { get { return "Quote"; } }

/// <summary>
/// 数据XSD 适配。
/// </summary>
public System.Xml.XmlDocument GetQuote(string company, string id)
{
XmlDocument source = (new ProducerService()).GetQuote(company, id);
// 由于基于XSLT 对符合不同XSD 的XML 过程很固定,因此Transform 过程,
// 方在抽象基类BaseXsdServiceAdapter 中完成。
return base.Transform(source);
}
}
}

6、借助抽象的 ITarget,实现消费者 Web Service

//C#
using System.Xml;
using System.Web.Services;
using System.Web.Services.Protocols;
namespace VisionTask.Training.ServicePattern.UtilityService.XsdAdapter
{
/// <summary>
/// 客户Web Service。
/// 这里的XmlDocument 是遵循QuoteTarget.xsd 的数据。
/// </summary>
[WebService(Namespace = "http://www.visionlogic.com/trade")]
public class ConsumerService : System.Web.Services.WebService
{
[WebMethod]
public XmlDocument GetQuote(string company, string id)
{
// 实际项目中,该目标接口可以通过其他渠道注入。
// 这里为了示例方便,直接采用new() 方式使用实体类型。
ITarget target = new QuoteXsdServiceAdapter();
return target.GetQuote(company, id);
}
}
}
//Unit Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UtilityService.UnitTest.XsdConsumer;
using System.Xml;
namespace UtilityService.UnitTest.XsdAdapter
{
[TestClass()]
public class ConsumerServiceTest
{
[TestMethod]
public void Test()
{
ConsumerService service = new ConsumerService();
string company = "VisoinTask";
string id = "2007-07-21";
XmlNode node = service.GetQuote(company, id);
Assert.IsNotNull(node);
// 这里的 attribute 名称与 QuoteTarget.xsd一致。
Assert.AreEqual<string>(id, node.Attributes["SequenceNo"].Value);
Assert.AreEqual<string>(company, node.Attributes["CompanyName"].Value);
}
}
}


服务方法兼容性适配

1、准备 

    还是上面的那个示例,如果消费者Web Service需要的不是一整票报价,而是报价单中某一票商品的内容,那么调用时他除了id、company两个参数外,还需要提供对应QuoteItem的productId,因此需要进行服务方法的适配。


图8:服务方法兼容性适配

    参考上例和上图,定义作为Adaptee的生产者Web Service和作为客户程序的消费者 Web Service。测试报价单数据如下:

//XML
<?xml version="1.0" encoding="UTF-8"?>
<Quote Company="VisionTask" Id="Quote 2007-07-18"
xsi:schemaLocation="http://www.visionlogic.com/trade quote.xsd"
xmlns="http://www.visionlogic.com/trade"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<QuoteItem ProductId="19" QuantitiveInStock="1750" Price="3.14"/>
<QuoteItem ProductId="312" QuantitiveInStock="10" Price="3000"/>
</Quote>

2、根据消费者Web Service的需要,定义对应的Service Adapter

//C# ITarget
using System.Xml;
namespace VisionTask.Training.ServicePattern.UtilityService.MethodAdapter
{
/// <summary>
/// 消费者Web Service 需要的接口。
/// </summary>
public interface ITarget
{
XmlNode GetItem(string company, string id, string productId);
}
}
//C# QuoteServiceMethodAdapter
using System.Xml;
using VisionTask.Training.ServicePattern.Common.Xml;
using VisionTask.Training.ServicePattern.ServiceAdapter;
namespace VisionTask.Training.ServicePattern.UtilityService.MethodAdapter
{
public class QuoteServiceMethodAdapter : IServiceAdapter, ITarget
{
/// <summary>
/// 实现一般性IServiceAdapter 的要求。
/// </summary>
public virtual string Name { get { return "ServiceMethod"; } }

/// <summary>
/// 实现消费者 Web Service 需要的接口。
/// </summary>
public XmlNode GetItem(string company, string id, string productId)
{
XmlDocument doc = (new ProducerServiceM()).GetQuote(company, id);
if ((doc == null) || (!doc.HasChildNodes)) return null;
string xPath = "/Quote/QuoteItem[@ProductId=" + productId + "]";
System.Diagnostics.Trace.WriteLine(xPath);
XmlNode node = doc.SelectSingleNode(xPath);
return node;
}
}
}

3、借助抽象的 Service Adapter,实现消费者 Web Service

//C#
using System;
using System.Xml;
using System.Web.Services;
using System.Web.Services.Protocols;
namespace VisionTask.Training.ServicePattern.UtilityService.MethodAdapter
{
/// <summary>
/// 客户Web Service。
/// 它继续封装对外的服务,使用时可以根据他获得某公司提供的报价单中,
/// 具体某项商品的价格。
/// </summary>
[WebService(Namespace = "http://www.visionlogic.com/trade")]
public class ConsumerServiceM : System.Web.Services.WebService
{
[WebMethod]
public double GetPrice(string company, string id, string productId)
{
ITarget target = new QuoteServiceMethodAdapter();
XmlNode node = target.GetItem(company, id, productId);
if (node == null) throw new NullReferenceException();
return Convert.ToDouble(node.Attributes["Price"].Value);
}
}
}

其他说明 

    对于第三种“封装性适配”则是上述两种方式的组合: 

    •数据的转换。 
    •方法的适配。 
    •在Service Adapter中嵌入额外的逻辑。 
    •而双向适配则是双方各自定义自己的适配器。

后续说明

    为了简化后续个模式的示例部分,增加了一个叫Common(含对应的Common.UnitTest)的项目,包括一个XmlHelper的静态类,该类负责提供一般XML操作的支持,同时为了提高执行效率他内部包括了一个自主维护的缓冲机制。该类会随着后续各模式的使用要求不断充实。

//C#
using System;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Xsl;
using System.IO;
using System.Collections.Generic;
namespace VisionTask.Training.ServicePattern.Common.Xml
{
/// 完成一般XML 管理的Helper 类型。
public static class XmlHelper
{
/// 保存所有被加载并编译的XSLT 实例缓冲
private static Dictionary<string, XslCompiledTransform> transforms =
new Dictionary<string, XslCompiledTransform>();
/// 保存所有被加载并编译的XSD 实例缓冲
private static XmlSchemaSet schemas = new XmlSchemaSet();

/// 基于XSD 验证目标XML 文件。
public static bool ValidateSchema(string xsdFile,
string targetNamespace, string xmlFile)
{
if (string.IsNullOrEmpty(xsdFile) || string.IsNullOrEmpty(targetNamespace)
|| string.IsNullOrEmpty(xmlFile)) throw new ArgumentException();
LoadSchema(xsdFile, targetNamespace);
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas = schemas;
try
{
using (XmlReader reader = XmlReader.Create(xmlFile, settings))
while (reader.Read()) ;
}
catch { return false; } //解析过程中出现错误。
return true;
}

/// 根据XSLT 的定义完成XML 文件的转换。
public static void Transform(string xsltFile,
string sourceXmlFile, string targetXmlFile)
{
if (string.IsNullOrEmpty(xsltFile) || string.IsNullOrEmpty(sourceXmlFile)
|| string.IsNullOrEmpty(targetXmlFile)) throw new ArgumentException();
GetTransform(xsltFile).Transform(sourceXmlFile, targetXmlFile);
}

public static XmlDocument Transform(string xsltFile, XmlDocument source)
{
if (string.IsNullOrEmpty(xsltFile)) throw new ArgumentException("xsltFile");
if ((source == null) || (source.DocumentElement == null)) return null;
XslCompiledTransform tranform = GetTransform(xsltFile);
MemoryStream stream = new MemoryStream();
tranform.Transform(source, null, stream);
stream.Position = 0;
XmlDocument target = new XmlDocument();
target.Load(stream);
return target;
}

// helper method
private static XslCompiledTransform GetTransform(string xsltFile)
{
// 根据缓冲情况获取XSLT 实例
XslCompiledTransform transform;
if (!transforms.TryGetValue(xsltFile, out transform))
{
transform = new XslCompiledTransform();
transform.Load(xsltFile);
transforms.Add(xsltFile, transform);
}
return transform;
}

// helper method
private static void LoadSchema(string xsdFile, string targetNamespace)
{
// 根据缓冲情况获取XSD 实例
XmlSchema schema;
if (!schemas.Contains(targetNamespace))
{
schema = new XmlSchema();
schema.SourceUri = xsdFile;
schemas.Add(targetNamespace, xsdFile);
}
}
}
}
//Unit Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Xml;
using VisionTask.Training.ServicePattern.Common.Xml;
namespace Common.UnitTest
{
[TestClass()]
public class XmlHelperTest
{
private const string targetNamespace = "http://www.visionlogic.com/trade";

[TestMethod]
public void TestValidateSchema()
{
Assert.IsTrue(XmlHelper.ValidateSchema("quote.xsd",
targetNamespace, "quote.xml"));
}

[TestMethod]
public void TestTransformWithFileName()
{
string source = "Quote.xml";
string target = "QuoteTarget.xml";
XmlHelper.Transform("Quote.xslt", source, target);
XmlDocument doc = new XmlDocument();
doc.Load(target);
Assert.AreEqual<string>("Quote", doc.DocumentElement.Name);
}

[TestMethod]
public void TransformTestWithXmlDocument()
{
XmlDocument source = new XmlDocument();
source.Load("Quote.xml");
XmlDocument target = XmlHelper.Transform("Quote.xslt", source);
Assert.IsNotNull(target);
Assert.AreEqual<string>("Quote", target.DocumentElement.Name);
}
}
}

 

0
相关文章