《模式——工程化实现及扩展》(设计模式C# 版)《适配器模式 Adapter》——“自我检验"参考答案
MarvellousWorks公司最近开始向大客户提供后台接入服务,允许客户通过该服务与其多个在线业务进行B2B集成。
大体处理流程如下:
1) 客户通过消息队列将请求以报文的形式发给MarvellousWorks公司的“队列服务平台”。
2) 为了安全考虑,“消息平台”的处理逻辑主动从“队列服务平台”抓取最新提交的报文。
3) 抓取新报文后,“消息平台”对报文进行拆解,读取其中的信息并验证其有效性。
4) “消息平台”将拆解并验证有效地报文信息写入数据库
限制:
1) 报文数据均为XML格式,其 XML Schema预先由MarvellousWorks定义并发布,各家客户遵照执行
2) 由于客户使用的队列产品各异,所以“队列服务平台”计划支持微软MSMQ和IBM MQ,后续还可能支持ORACLE的Advanced Queue和BEA的MessageQ。
3) 由于MarvellousWorks的各个在线业务建立时间不同,所以数据库既有ORACLE、也有MySQL和SQL Server。
现在由你作为架构师设计“消息平台”的服务程序。
1、 队列访问接口和数据库访问接口的定义如下:
/// 队列访问接口
/// </summary>
interface IQueue
{
XmlDocument Peek();
XmlDocument Dequeue();
}
/// <summary>
/// 各在线业务数据实体的基类
/// </summary>
abstract class EntryBase { }
/// <summary>
/// 这里为了简化示例,将所有业务实体写如数据库前转换为ADO.NET自带的DataRow类型
/// </summary>
interface IEntryDataConverter
{
DataRow ToDataRow(EntryBase entry);
}
/// <summary>
/// 数据库访问接口
/// </summary>
interface IDatabase
{
IEntryDataConverter DataConverter { get; set; }
void Write(EntryBase entry);
void Write(IEnumerable<EntryBase> entries);
}
2、 请用本章介绍的适配器模式设计“消息平台”处理接口,并用建立一个简单的原型,通过单元测试验证你的思路。
3、 由于报文数据种类众多,请用本章介绍的数据适配机制配合上面的消息处理接口完成一个数据适配原型,并通过单元测试验证。
提示:
为保持“消息平台”处理逻辑的稳定,建议考虑Adapter-Adapter级联的方式,完成报文抓取到消息入库的过程
参考答案
2、所有的调用接口都采用了空数据
目标主要是通过单元测试验证结构的有效性
1、模拟用的队列产品接口及其适配器
{
string current;
/// <summary>
/// 不兼容的接口,相当于Peek()
/// </summary>
public string Head
{
get
{
Trace.WriteLine("Msmq.Head");
current = Guid.NewGuid().ToString();
return current;
}
}
/// <summary>
/// 不兼容的接口,相当于Dequeue()
/// </summary>
/// <returns></returns>
public string GetHead()
{
Trace.WriteLine("Msmq.GetHead()");
var result = current;
current = string.Empty;
return result;
}
}
class MsmqAdapter : IQueue
{
Msmq queue = new Msmq();
public XmlDocument Peek()
{
Trace.WriteLine("MsmqAdapter.Peek()");
Trace.WriteLine(queue.Head);
return null;
}
public XmlDocument Dequeue()
{
Trace.WriteLine("MsmqAdapter.Dequeue()");
Trace.WriteLine(queue.GetHead());
return null;
}
}
class IbmMq : Queue<string>
{
string lastest;
public new string Peek()
{
lastest = Guid.NewGuid().ToString();
base.Enqueue(lastest);
Trace.WriteLine("IbmMq.Peek()");
return base.Peek();
}
public new string Dequeue()
{
Trace.WriteLine("IbmMq.Dequeue()");
return base.Dequeue();
}
}
class IbmMqAdapter : IQueue
{
IbmMq queue = new IbmMq();
public XmlDocument Peek()
{
Trace.WriteLine("IbmMqAdapter.Peek()");
Trace.WriteLine("Message : " + queue.Peek());
return null;
}
public XmlDocument Dequeue()
{
Trace.WriteLine("IbmMqAdapter.Dequeue()");
Trace.WriteLine("Message : " + queue.Dequeue());
return null;
}
}
2、模拟用的数据库产品接口
{
public void WriteData(string[][] data)
{
Trace.WriteLine("OracleDb.WriteData(string[][] data)");
}
}
class SqlServer
{
public void ExecuteNonSql(DataSet data)
{
Trace.WriteLine("SqlServer.ExecuteNonSql(DataSet data)");
}
public void InsertOneRecord(DataRow data)
{
Trace.WriteLine("SqlServer.InsertOneRecord(DataRow data)");
}
}
class MySqlDatabase
{
public void ExecuteCommand(string sql)
{
Trace.WriteLine("MySqlDatabase.ExecuteCommand(string sql)");
}
}
3、模拟用的各类示例的业务实体
class DeliveryOrder : EntryBase { }
class WarehouseEntry : EntryBase { }
class InventoryList : EntryBase { }
4、模拟的数据适配接口以及面向个业务实体类型的实体数据适配类型
{
IEnumerable<EntryBase> ConvertMessageToQuerable(XmlDocument message);
}
abstract class DatabaseAdapterBase : IDatabase
{
public IEntryDataConverter DataConverter { get; set; }
public abstract void WriteData(DataRow row);
public abstract void WriteData(IEnumerable<DataRow> rows);
public virtual void Write(EntryBase entry)
{
if (DataConverter == null) throw new NullReferenceException("DataConverter");
Trace.WriteLine("DatabaseAdapterBase.Write(EntryBase entry)");
//if (entry != null)
// WriteData(DataConverter.ToDataRow(entry));
WriteData(new DataTable().NewRow());
}
public virtual void Write(IEnumerable<EntryBase> entries)
{
if (DataConverter == null) throw new NullReferenceException("DataConverter");
Trace.WriteLine("DatabaseAdapterBase.Write(IEnumerable<EntryBase> entries)");
//if (entries != null)
//{
// if (entries.Count() == 0) return;
// WriteData(entries.Select(x => DataConverter.ToDataRow(x)));
//}
WriteData(new List<DataRow>());
}
}
class OralceAdapter : DatabaseAdapterBase
{
OracleDB db = new OracleDB();
public override void WriteData(DataRow row)
{
Trace.WriteLine("OralceAdapter.WriteData(DataRow row)");
// 将datarow变成string[0][], 然后提交OracleDb.WriteData(string[][] data)
Trace.WriteLine("DataRow -> string[][]");
db.WriteData(null);
}
public override void WriteData(IEnumerable<DataRow> rows)
{
Trace.WriteLine("OralceAdapter.WriteData(IEnumerable<DataRow> rows)");
// 将IEnumerable<DataRow>变成string[][], 然后提交OracleDb.WriteData(string[][] data)
Trace.WriteLine("IEnumerable<DataRow> -> string[][]");
db.WriteData(null);
}
}
class SqlAdapter : DatabaseAdapterBase
{
SqlServer db = new SqlServer();
public override void WriteData(DataRow row)
{
Trace.WriteLine("SqlAdapter.WriteData(DataRow row)");
// 直接将DataRow入库到SqlServer.InsertOneRecord(DataRow data)
db.InsertOneRecord(null);
}
public override void WriteData(IEnumerable<DataRow> rows)
{
Trace.WriteLine("SqlAdapter.WriteData(IEnumerable<DataRow> rows)");
// 将IEnumerable<DataRow>变成DataSet, 然后提交SqlServer.ExecuteNonSql(DataSet data)
Trace.WriteLine("IEnumerable<DataRow> -> DataSet");
db.ExecuteNonSql(null);
}
}
class MySqlAdapter : DatabaseAdapterBase
{
MySqlDatabase db = new MySqlDatabase();
public override void WriteData(DataRow row)
{
Trace.WriteLine("MySqlAdapter.WriteData(DataRow row)");
// 直接将DataRow变成Insert SQL语句入库到MySqlDatabase.ExecuteCommand(string sql)
Trace.WriteLine("DataRow -> SQL");
db.ExecuteCommand(string.Empty);
}
public override void WriteData(IEnumerable<DataRow> rows)
{
Trace.WriteLine("MySqlAdapter.WriteData(IEnumerable<DataRow> rows)");
// 将IEnumerable<DataRow>变成变成Insert SQL语句, 然后提交MySqlDatabase.ExecuteCommand(string sql)
Trace.WriteLine("IEnumerable<DataRow> -> SQL");
db.ExecuteCommand(string.Empty);
}
}
/// <summary>
/// 这里为了简化示例,定义了简单的转换类型,而且将只能分散的两个接口合二为一
/// </summary>
abstract class ConverterBase : IMessageConverter, IEntryDataConverter
{
public virtual IEnumerable<EntryBase> ConvertMessageToQuerable(XmlDocument message)
{
Trace.WriteLine("IMessageConverter ConverterBase.ConvertMessageToQuerable(XmlDocument message)");
return null;
}
public virtual DataRow ToDataRow(EntryBase entry)
{
Trace.WriteLine("IEntryDataConverter ConverterBase.ToDataRow(EntryBase entry)");
return null;
}
}
class OrderConverter : ConverterBase { }
class DeliveryOrderConverter : ConverterBase { }
class WarehouseEntryConverter : ConverterBase { }
class InventoryListConverter : ConverterBase { }
5、级联Queue Adapter和Db Adapter的连接器
{
/// <summary>
/// 处理的业务实体类型
/// </summary>
string EntryName { get; }
/// <summary>
/// 连接Queue Adapter和Db Adapter实现消息提取、转换和入库的过程
/// </summary>
void Process();
}
class MessageConnector : IMessageConnector
{
string entryName;
IFactory factory;
Route route;
public MessageConnector(string entryName)
{
this.entryName = entryName;
// 从运行环境获取配置信息
factory = MessageServiceFixture.factory;
route = MessageServiceFixture.orchestration.FirstOrDefault(x => string.Equals(x.EntryName, EntryName));
if (route == null) throw new ApplicationException("route");
}
public string EntryName { get { return entryName; } }
public virtual void Process()
{
Trace.WriteLine("MessageConnectorBase.Process");
Trace.WriteLine(route);
var queueAdapter = factory.Create<IQueue>(route.QueueName);
// 为了示例简便,假设每个报文仅包括一批业务实体的情况
//if (queueAdapter.Peek() != null)
//{
var message = queueAdapter.Peek();
Trace.WriteLine("Get new message");
var entries =
factory.Create<IMessageConverter>(EntryName)
.ConvertMessageToQuerable(queueAdapter.Dequeue());
// 对业务实体进行检验和其他处理
Trace.WriteLine("Save message to db");
var dbAdpter = factory.Create<IDatabase>(route.DbName);
// 注入
dbAdpter.DataConverter = factory.Create<IEntryDataConverter>(EntryName);
dbAdpter.Write(entries);
//}
Trace.WriteLine("\n\n");
}
}
6、单元测试程序
public class MessageServiceFixture
{
const string OracleItem = "ora";
const string SqlServerItem = "sql";
const string MySqlItem = "mysql";
const string MsmqItem = "msmq";
const string MqItem = "mq";
const string OrderItem = "order";
const string DeliveryOrderItem = "DeliveryOrder";
const string WarehouseEntryItem = "WarehouseEntry";
const string InventoryListItem = "InventoryList";
/// <summary>
/// 各类业务实体流转信息
/// </summary>
public class Route
{
/// <summary>
/// 处理的业务实体名称
/// </summary>
public string EntryName { get; set; }
/// <summary>
/// 队列逻辑名称
/// </summary>
public string QueueName { get; set; }
/// <summary>
/// 数据库逻辑名称
/// </summary>
public string DbName { get; set; }
public override string ToString()
{
return string.Format("{0} :\n---------------\nQueue\t\t{1}\nDatabase\t{2}", EntryName, QueueName, DbName);
}
}
public static IFactory factory;
/// <summary>
/// 所有消息流转过程的登记清单
/// </summary>
public static IList<Route> orchestration;
/// <summary>
/// 加载各类配置信息
/// </summary>
/// <remarks>实际项目中这些配置一般登记在配置文件中</remarks>
[ClassInitialize]
public static void ClassInitialize(TestContext context)
{
factory = new Factory()
// 报文到业务实体的转换类型
.RegisterType<IMessageConverter, OrderConverter>(OrderItem)
.RegisterType<IMessageConverter, DeliveryOrderConverter>(DeliveryOrderItem)
.RegisterType<IMessageConverter, WarehouseEntryConverter>(WarehouseEntryItem)
.RegisterType<IMessageConverter, InventoryListConverter>(InventoryListItem)
// 业务实体到DataRow的转换类型
.RegisterType<IEntryDataConverter, OrderConverter>(OrderItem)
.RegisterType<IEntryDataConverter, DeliveryOrderConverter>(DeliveryOrderItem)
.RegisterType<IEntryDataConverter, WarehouseEntryConverter>(WarehouseEntryItem)
.RegisterType<IEntryDataConverter, InventoryListConverter>(InventoryListItem)
// 不同队列产品的适配器类型
.RegisterType<IQueue, MsmqAdapter>(MsmqItem)
.RegisterType<IQueue, IbmMqAdapter>(MqItem)
// 不同数据库产品的适配器类型
.RegisterType<IDatabase, OralceAdapter>(OracleItem)
.RegisterType<IDatabase, SqlAdapter>(SqlServerItem)
.RegisterType<IDatabase, MySqlAdapter>(MySqlItem);
// 有订单(Order)、订单发货单(Order Delivery)、入库单(Warehouse entry)、库存清单(Inventory List)四种报文
// 订单MSMQ接入,在线订单处理系统当前后台数据库为ORACLE
// 入库单和库存清单通过IBM MQ接入,仓库后台处理系统数据库为SQL Server
// 订单发货单通过IBM MQ接入,后台发货系统数据库为MySQL
orchestration = new List<Route>()
{
new Route()
{
EntryName = OrderItem,
QueueName = MsmqItem,
DbName = OracleItem
},
new Route()
{
EntryName = WarehouseEntryItem,
QueueName = MqItem,
DbName = SqlServerItem
},
new Route()
{
EntryName = InventoryListItem,
QueueName = MqItem,
DbName = SqlServerItem
},
new Route()
{
EntryName = DeliveryOrderItem,
QueueName = MqItem,
DbName = MySqlItem
}
};
}
[TestMethod]
public void TestDatabaseAdapter()
{
/// 启动服务端的所有处理通道
List<IMessageConnector> channels =
new List<IMessageConnector>()
{
new MessageConnector(OrderItem),
new MessageConnector(DeliveryOrderItem),
new MessageConnector(WarehouseEntryItem),
new MessageConnector(InventoryListItem)
};
channels.ForEach(x => x.Process());
}
}
Output窗口输出结果
其中,为队列适配过程、为数据库适配过程,而Connector则负责级联Adapter-Adapter
MessageConnectorBase.Process
order :
---------------
Queue msmq
Database ora
MsmqAdapter.Peek()
Msmq.Head
a2dc5283-058c-49d5-8ae0-b60a4bd3dd15
Get new message
MsmqAdapter.Dequeue()
Msmq.GetHead()
a2dc5283-058c-49d5-8ae0-b60a4bd3dd15
DatabaseAdapterBase.Write(IEnumerable
IMessageConverter ConverterBase.ConvertMessageToQuerable(XmlDocument message)
Save message to db<EntryBase> entries)
OralceAdapter.WriteData(IEnumerable<DataRow> rows)
IEnumerable<DataRow> -> string[][]
OracleDb.WriteData(string[][] data)
DeliveryOrder :
MessageConnectorBase.Process---------------
Queue mq
Database mysql
IbmMqAdapter.Peek()
IbmMq.Peek()
Message : bcf87b69-7576-4cd7-a963-a61839ecd733
Get new message
IbmMqAdapter.Dequeue()
IbmMq.Dequeue()
Message : bcf87b69-7576-4cd7-a963-a61839ecd733
IMessageConverter ConverterBase.ConvertMessageToQuerable(XmlDocument message)
Save message to db
DatabaseAdapterBase.Write(IEnumerable<EntryBase> entries)
MySqlAdapter.WriteData(IEnumerable<DataRow> rows)
IEnumerable<DataRow> -> SQL
MySqlDatabase.ExecuteCommand(string sql)
MessageConnectorBase.Process
WarehouseEntry :
---------------
Queue mq
Database sql
IbmMqAdapter.Peek()
IbmMq.Peek()
Message : 9ea890d1-baa8-4a0b-afa1-d0f5f00f03d4
Get new message
IbmMqAdapter.Dequeue()
IbmMq.Dequeue()
Message : 9ea890d1-baa8-4a0b-afa1-d0f5f00f03d4
IMessageConverter ConverterBase.ConvertMessageToQuerable(XmlDocument message)
Save message to db
DatabaseAdapterBase.Write(IEnumerable<EntryBase> entries)
SqlAdapter.WriteData(IEnumerable<DataRow> rows)
IEnumerable<DataRow> -> DataSet
SqlServer.ExecuteNonSql(DataSet data)
MessageConnectorBase.Process
InventoryList :
---------------
Queue mq
Database sql
IbmMqAdapter.Peek()
IbmMq.Peek()
Message : 4429cae7-28f2-4e42-8abb-676092faab44
Get new message
IbmMqAdapter.Dequeue()
IbmMq.Dequeue()
Message : 4429cae7-28f2-4e42-8abb-676092faab44
IMessageConverter ConverterBase.ConvertMessageToQuerable(XmlDocument message)
Save message to db
DatabaseAdapterBase.Write(IEnumerable<EntryBase> entries)
SqlAdapter.WriteData(IEnumerable<DataRow> rows)
IEnumerable<DataRow> -> DataSet
SqlServer.ExecuteNonSql(DataSet data)
1 passed, 0 failed, 0 skipped, took 0.95 seconds (MSTest 10.0).