多くのSOAPベースのXMLサービスと相互作用するかなり大きなコードベースがあります。
これらのサービスはそれぞれ、1対nのサービス呼び出しを行います。一般的な低レベルのWebサービス呼び出しは、次のようになります(簡略化)。
_public XElement ExecuteWebService(string xmlRequest)
_
WCFを使用してSOAPメッセージを送信し、応答を返します。GetReaderAtBodyContents()
を使用して本文を取得し、次のようにその文字列をXMLElementに変換します。
_XElement.Parse(response)
_
次に、アプリケーションの残りのレイヤー全体でそのXElementを使用します。 XmlSerialization属性でマークされている強く型付けされたデータコントラクトまたはクラスはありません。
XElementは任意の有効なXML構造である可能性があるため、このタイプの構造ではテストの作成が非常に困難になります。また、XElementが渡されるときに、XElementの読み取り、解析、更新を行うコード行が多数あります。
このタイプの構造には正当な理由がありますか?それはパフォーマンス上の理由で行われたと言われ、より柔軟です。
XElementの解析と読み取りは、1回のシリアル化(データコントラクト、XMLシリアル化)よりも本当に高速ですか?これは、厳密に型指定されたオブジェクトモデルを使用する場合よりも本当に柔軟ですか?
他のシステムでは、理解と保守が簡単なため、常にシリアル化と強く型付けされたオブジェクトを使用しました。このXElementアプローチが有効かどうかはわかりません。
私はいくつかの研究とテストを行いました、そしてここに私の結果があります:
分析
.NetプラットフォームでWebサービスのメッセージを読み書きする場合、いくつかの可能性があります。
XmlSerializerは、要素と属性の両方をシリアル化できます。これは、レガシーまたは既存のメッセージ構造を処理するときのデフォルトの選択です。データモデルクラスを作成する場合、パブリックプロパティまたはメンバーを適切に属性付けして、発信または着信XMLを生成または使用できます。
IXmlSerializableの実装は、XmlSerializerの使用と非常に似ていますが、GetSchema(), ReadXml(), and WriteXml().
を実装してXmlの読み取りと書き込みを管理するコードを記述します。このメソッドは、XmlReaderとXmlWritersを使用して、Xmlメッセージの読み取りと書き込みを行います。
データコントラクトシリアライザーも、実装と使用が非常に簡単です。属性をサポートしていないため、Xmlを扱う際のグリーンフィールド開発に適しています。データコントラクトシリアライザーは、メッセージ構造が重要ではないコードファーストシナリオに最適です。
既存のメッセージ構造が存在する場合、データコントラクトシリアライザーを使用することは困難または実行不可能である可能性があります。これらの場合、デフォルトでXmlSerializerに戻す必要があります。XmlSerializerを使用すると、着信および発信メッセージ構造のフォーマットをより細かく制御できます。
データコントラクトシリアライザーはWCFの既定のシリアライザーであり、通常、Xmlをシリアル化するときに、Xmlシリアライザーと同等またはそれ以上のパフォーマンス向上を提供します。データコントラクトシリアライザーは、JSONにシリアル化することもでき、メッセージを処理するときのより近代的なアプローチです。
データコントラクトシリアライザーは、維持するコードの量が最も少なく、優れたパフォーマンスを提供するため、常にグリーンフィールド開発の最初の選択肢です。
XElement/XDocumentの読み込みと解析により、Xmlメッセージの読み取りと書き込みの際の柔軟性が大幅に向上します。
覚えておくべきことのいくつか:
このメソッドは、大規模なXml構造からデータを抽出するのに適しています。たとえば、1つの要素に100要素のXmlメッセージがあり、必要な要素が数個または数個しかない場合は、このメソッドが適しています。メッセージ処理を集中化し、解析されるデータ要素を、コードベース全体で使用できる厳密に型指定されたオブジェクトに配置することをお勧めします。 XElementsやXDocumentをコード内で渡さないでください。テストが困難になります。また、メッセージ処理が集中化されていない場合、異なる方法が使用されているため、コードベース全体でXElement解析が重複する可能性があります。
通常、このメソッドは、少量の解析が行われる場合、シリアル化よりも優れたパフォーマンスを提供します。手動での処理と解析の量が増えると、保守とテストを行うコードが少なくなるためパフォーマンスが少し悪い場合でも、シリアライゼーションがより良いオプションになります。
パフォーマンスが最大の関心事である場合、カスタムアプローチの実装は適切ですが、コードとメンテナンスの観点からはコストがかかります。これは、パフォーマンステストに使用されているいくつかのXmlを生成する単純な実装です。 Xml文字列を受け取り、オブジェクトを作成するコンストラクターがあります。 Xml文字列を作成するためにToString()
もオーバーライドしました。
public class FoobarHandRolled
{
public FoobarHandRolled(string name, int age, bool isContent, DateTime birthDay)
{
Name = name;
Age = age;
IsContent = isContent;
BirthDay = birthDay;
}
public FoobarHandRolled(string xml)
{
if (string.IsNullOrWhiteSpace(xml))
{
return;
}
SetName(xml);
SetAge(xml);
SetIsContent(xml);
SetBirthday(xml);
}
public string Name { get; set; }
public int Age { get; set; }
public bool IsContent { get; set; }
public DateTime BirthDay { get; set; }
/// <summary>
/// Takes this object and creates an XML representation.
/// </summary>
/// <returns>An XML string that represents this object.</returns>
public override string ToString()
{
var builder = new StringBuilder();
builder.Append("<FoobarHandRolled>");
if (!string.IsNullOrWhiteSpace(Name))
{
builder.Append("<Name>" + Name + "</Name>");
}
builder.Append("<Age>" + Age + "</Age>");
builder.Append("<IsContent>" + IsContent + "</IsContent>");
builder.Append("<BirthDay>" + BirthDay.ToString("yyyy-MM-dd") + "</BirthDay>");
builder.Append("</FoobarHandRolled>");
return builder.ToString();
}
private void SetName(string xml)
{
Name = GetSubString(xml, "<Name>", "</Name>");
}
private void SetAge(string xml)
{
var ageString = GetSubString(xml, "<Age>", "</Age>");
int result;
var success = int.TryParse(ageString, out result);
if (success)
{
Age = result;
}
}
private void SetIsContent(string xml)
{
var isContentString = GetSubString(xml, "<IsContent>", "</IsContent>");
bool result;
var success = bool.TryParse(isContentString, out result);
if (success)
{
IsContent = result;
}
}
private void SetBirthday(string xml)
{
var dateString = GetSubString(xml, "<BirthDay>", "</BirthDay>");
DateTime result;
var success = DateTime.TryParseExact(dateString, "yyyy-MM-dd", null, DateTimeStyles.None, out result);
if (success)
{
BirthDay = result;
}
}
private string GetSubString(string xml, string startTag, string endTag)
{
var startIndex = xml.IndexOf(startTag, StringComparison.Ordinal);
if (startIndex < 0)
{
return null;
}
startIndex = startIndex + startTag.Length;
var endIndex = xml.IndexOf(endTag, StringComparison.Ordinal);
if (endIndex < 0)
{
return null;
}
return xml.Substring(startIndex, endIndex - startIndex);
}
}
ここでは、文字列解析手法とハードコードされた値を使用して、Xmlメッセージの読み取りと書き込みを行っています。この方法は、追加のコード、メンテナンス、カスタム実装を犠牲にして、最高のパフォーマンスを提供します。この方法は、最高のパフォーマンスを得る必要がない限り、お勧めできません。
パフォーマンスの概要
グラフには、初期パフォーマンス(1回目)の時間と平均1000回の読み取りがリストされます。使用したハードウェアは、Visual Studio 2013と.NET 4.5.2を使用したW530ラップトップでした。プロセッサーは2.80Ghzのi7-3840QMでした。すべてのシリアライザは、ナノ秒の読み取り時間を提供します。ウォームアップ時間は、起動時に適切な初期化を実行することで軽減できます。 XMLシリアライザは、事前にSGENを使用することで軽減できます。
Serializer First Time Average 1000 Reads
XmlSerializer 2448965 245
Implementing XmlSerializable 2051813 208
Custom 161105 29
Using XElement/XDocument 247024 113
Data Contact (Json) 1979593 303
すべての時間はティックです。ティックはハードウェアに依存しますが、ガイドラインとして、1秒間におよそ10,000,000ティックがあります。
最終的な推奨事項
サービスがデータを交換するメッセージを設計するときは、次のガイドラインに従う必要があります。
これらのガイドラインに従うことにより、これらのメッセージを使用するクライアントをより簡単かつ高速にすることができます。