这篇文章在去年就看了,但当时不是很理解;现在回头看,有些体会。 实现SOA的一个重要途径是使用Xml WebServices,相对一些“高山流水、曲高和寡”的理论和概念来说,对于俺们平庸之辈,它门槛低,可操作性强。 为了便于理解后边的内容,再次老话重提……SOA的四个基本原则: 1)边界明确:Boundaries Are Explicit 2)服务自治:Services Are Autonomous 3)服务共享架构和契约:Services Share Schemas & Contacts 4)基于策略来使服务兼容:Service Compatibilility Is Based Upon Policy. 也许因为翻译非常抽象,可以仔细阅读 原英文文章(建议翻译的中文页面就不要看了,越看越迷糊) WebCast教程
进入正题。文档处理器(Documoent Processor)、幂等消息(Idempotent Message)和预订(Reservation)是在进行web 服务设计时可参考的三个模式。
核心:根据现有文档和已知的业务事件来构建业务处理模型。 注解: 1)在进行web服务接口设计时,应完全站在业务角度,而非设计角度; 2)所谓文档,应指的是反映在业务流程中不同异构系统中用于交换和流通的数据资料; 3)所设计的服务接口应该很好地封装内部实现的细节; 4)以文档为参照来定义或重用 XML 架构以表示服务的请求和响应消息;并直接从这些架构生成对象,以加速开发。 5)所定义的服务必须为一个完整的过程,避免“CRUD”; 示例: 本示例设计:查询某个国家的客户。代码下载 //不好的设计------返回DataSet类型,如果使用者不是.Net很难使用 [WebMethod(Description="Find customers in the Customer table based on a specific country code。 This method returns a dataset - consider this an ANTI-Pattern for interoperable web services.")] public NWCustomerDataSet FindCustomerByCountryReturnDataSet(string Country) { try { CustomerService svc = new CustomerService(); return svc.FindCustomerByCountryReturnDataSet(Country); } … } //好的设计-返回一个结构化的文档信息,简单规范,任何使用者都很方便使用 [WebMethod(Description="Find customers in the Customer table based on a specific country code.\nThis method returns a structured \"document\" of customer information - a much cleaner approach than returning datasets.")] public FindCustomerResponse FindCustomersByCountry(string Country) { try { // Get the customer data CustomerService svc = new CustomerService(); NWCustomerDataSet nwCustomers; nwCustomers = svc.FindCustomerByCountryReturnDataSet(Country); // At the service boundary, translate the data into a format // described by the schema FindCustomerResponse Customers = new FindCustomerResponse();
// Copy the data into a data transfer object foreach (NWCustomerDataSet.CustomersRow custRow in nwCustomers.Customers) { // Generated wrapper class Customers.Add(CopyRowToDTO(custRow)); }
return Customers; } catch (Exception ex) { System.Diagnostics.Trace.WriteLine(ex.Message); return null; } }
//以下是FindCustomerResponse结构化文档: <?xml version="1.0" encoding="utf-16"?> <xs:schema xmlns="http://www./ <xs:complexType name="NorthwindCustomer"> <xs:sequence> <xs:element name="CustomerID" type="xs:string" /> <xs:element name="CompanyName" type="xs:string" /> <xs:element name="ContactName" type="xs:string" /> <xs:element name="ContactTitle" type="xs:string" /> <xs:element name="Address" type="xs:string" /> <xs:element name="City" type="xs:string" /> <xs:element name="Region" type="xs:string" /> <xs:element name="PostalCode" type="xs:string" /> <xs:element name="Country" type="xs:string" /> <xs:element name="Phone" type="xs:string" /> <xs:element name="Fax" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:complexType name="FindCustomerResponse"> <xs:sequence> <xs:element name="Customer" type="NorthwindCustomer" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> <xs:complexType name="FindCustomerByCountryRequest"> <xs:sequence> <xs:element name="Country" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:schema>
问题: 模式固然是好的,但要根据具体情况进行取舍。因为:服务使用者必须将该结构化的xml文档映射回其内部本身的数据架构,与此同时,效率必然也会大大降低。
核心:使用带有公共契约的消息请求进行通讯。 注解: 1)什么是幂等消息? a) 一来一往,可靠的消息传输:可以理解为TCP,一来一往,保证消息是可靠的;不幂等呢,可以理解为UDP,发送过去,结果到底怎么样就不知道了; b) 使用者很有可能多次发送同一请求,比如在网上购物,用户反复点击结账(不知道为什么); 2)如上图所示,服务使用方在发送请求的时候,携带一个UOW-工作单元ID;Web服务接口根据该UOW来判断请求的命令是否已经执行过; 3)关于UOW:可以把UOW设计为一个定制的SOAP header或URI,在这里面可以(如果需要)加上直接调用者及相关信息,这样做是为了解决使用者使用同一UOW进行多次请求的处理问题: URI协助服务方分析调用者的信息,以决定具体采用什么方式进行回应? c) 如包含上次没收到结果的信息,服务方就可以再次处理请求; d) 如果是写操作,且上次已经成功返回,则服务端就可以抛出异常; e) 如果是读操作,则把对应的缓存数据返回; 4)由上面几条,我觉得结论是:这种模式比较复杂,并不好操作。因为本来使用复杂的消息头或URI已经额外增加了工作量;确定合理的缓存策略更是让人头疼,制定不好肯定会严重影响服务性能。 示例: 本示例设计: 代码下载 /// <summary> /// This method updates the number of Insureds covered with a relative value. Since each /// call to update query modifies the database value to a new value, the call to update /// database should be made only if not done earlier. This is ensured by checking the RequestId /// passed as first parameter. If the RequestId passed is different from the previously /// passed RequestId value, only than update database call is made. /// Each RequestId is maintained in a list. /// </summary> /// <returns></returns> public UpdatePolicyResponse UpdateInsuredsCovered(int requestId, int policyId, int insuredsCovered) { UpdatePolicyResponse response = new UpdatePolicyResponse();
response.RequestID = requestId.ToString(); response.PolicyId = policyId.ToString(); response.InsuredsCovered = insuredsCovered.ToString();
// // The requestId should be a number greater than zero // if (requestId <= 0) { response.Result = ResultConts.INVALID_REQUEST_MSG; return response; }
// // Make the update of the price as required // UpdatePolicy updateData = new UpdatePolicy(); response.Result = updateData.UpdateInsuredsCovered(requestId, policyId, insuredsCovered); return response; }
核心:使用预订ID、状态保存和过期策略,处理需要多次请求才能完成的业务流程。
1)针对的问题:复杂的、长时间的业务流程。如充值业务,网上购物等。 2)在完成业务过程最后一个步骤之前;所有前边的请求都被认为“试探性操作”,即随时有可能取消;因此前面所有的试探性操作的结果都会被跟踪保存; 3)当开始第一步试探性请求时,服务方会创建一个预订的ID ,以后每次校验该ID,如果不符,则交易取消; 4)服务端对每次试探性操作盖上时间戳,超过时间没有下一个操作,交易也会取消; 5)每一步试探性操作都是一来一回:使用者请求,服务端处理后发送确认消息,当使用者收到确认消息以后才算该操作完成; 5)对于重复预订请求,可以结合Idempotemt模式进行处理; 示例: 本示例设计:代码下载 // For a given Flight number, check travel date and number of seats. // The service checks the availability of seats and returns the availability as ‘Y‘ or ‘N‘. // If the the seats are available the service also returns a RequestID that can be used // to confirm the reservation. The RequestID is set to 0 if the seats are not available // for the requested flight. If the request is successfully processed the value of Result // element will be "Success" In case of invalid input or exceptional conditions an error // message is returned in the Result element of the response. [WebMethod(BufferResponse=false, CacheDuration=60, Description="Check the availability of reservation in the specified flight. The Travel Date must be in mm/dd/yyyy format and Number of Seats must be greater than Zero. For testing purposes you can use Flight Numbers AI111 or AI112." )] public ReservationStatusResponse CheckSeatAvailability( string FlightNumber,string TravelDate, string NumberofSeats ) { FBReservationService svc = new FBReservationService (); return svc.CheckReservationStatus(FlightNumber,TravelDate,NumberofSeats); }
// This service checks the validity of the incoming RequstID. Valid RequestIDs will be // confirmed. Return statuses are as follows: // Confirm - for Valid RequestIDs // Duplicate - for valid but not unexpired RequestIDs // Invalid Requst - for all other RequestIDs // // If the request is successfully processed the value of Result element is "Success" // For invalid input or exceptional conditions an error message is returned in the Result // element. [WebMethod(BufferResponse=false, CacheDuration=60, Description="Confirm the reservation for the provided )] public ConfirmReservationResponse ConfirmReservationRequest( string RequestID ) { FBReservationService svc = new FBReservationService (); return svc.ConfirmReservationStatus(RequestID); } 最后,引用一个名言:pattern is “an abstraction from a concrete form which keeps recurring in specific, non-arbitrary contexts." 所谓模式,只是在特定环境中过程的最佳实践,并不是万能的。况且,模式往往是抽象成了最简单的,现实情况更复杂,所以,把它当成战略就行了。
在设计的时候,没有最完美,只有最合适。不能被这些华丽的东西蒙住双眼,Just Keep It Simple。
|
|
来自: ShangShujie > 《document》