Effective C#之Item 34:Create Large-Grain Web APIs

  rel="File-List" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="themeData" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CHelios%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

Item 34: Create Large-Grain Web APIs

创建大容量的Web APIs

The cost and inconvenience of a communication protocol dictates how you should use the medium. You communicate differently using the phone, fax, letters, and email. Think back on the last time you ordered from a catalog. When you order by phone, you engage in a question-and-answer session with the sales staff:

"Can I have your first item?"

"Item number 123-456."

"How many would you like?"

"Three."

交互协议的花销和麻烦规定了你该如何使用媒介。使用电话、传真、信件或者email等不同的媒体,使得交互有所不同。想想你上次从一个目录里面进行订阅的情况。当你通过电话订阅时,你和销售人员之间就有问答的交互。

“你能把第一项填一下么?”

“号码是123-456

“你想要多少?”

“三个”

This conversation continues until the sales staff has your entire order, your billing address, your credit-card information, your shipping address, and any other information necessary to complete the transaction. It's comforting on the phone to have this back-and-forth discussion. You never give long soliloquies with no feedback. You never endure long periods of silence wondering if the salesperson is still there.

这段对话将继续到销售人员得到了你的整个订单,你的账号地址,你的信用卡信息,你的运送地址和其它任何完成该次交易必须的信息。在电话里进行这种前前后后的讨论很方便。你从不会因为得不到反馈而长久的自言自语。只要销售人员还在,你就不会经历长久的沉默。

Contrast that with ordering by fax. You fill out the entire document and fax the completed document to the company. One document, one transaction. You do not fill out one product line, fax it, add your address, fax again, add your credit number, and fax again.

与通过传真进行订阅相比。你填好整个文档,将完整的文档传真给公司。一个文档完成一次交易。你不需要一次填充一个产品线,进行传真,填写地址,再传真,填写信用卡号码,再次传真。

This illustrates the common pitfalls of a poorly defined web method interface. Whether you use a web service or .NET Remoting,you must remember that the most expensive part of the operation comes when you transfer objects between distant machines. You must stop creating remote APIs that are simply a repackaging of the same local interfaces that you use. It works, but it reeks of inefficiency. It's using the phone call metaphor to process your catalog request via fax. Your application waits for the network each time you make a round trip to pass a new piece of information through the pipe. The more granular the API is, the higher percentage of time your application spends waiting for data to return from the server.

这演示了一个定义糟糕的web方法接口的常见缺点。不管你是使用web服务还是.Net Remoting,你应该记住,操作中花销最大的部分来自于在远程机器之间传输对象。你应该停止创建这样的远程API:对你使用的同样的本地接口进行简单的重新打包。这样是可以工作的,但是效率很低。使用了打电话来隐喻通过传真处理你的订阅目录。你的应用程序,在每次将新的信息通过管道传递时,都要等待网络的处理。API的粒度越大,你的应用程序花在等待由服务器返回的数据时,花费的时间这整个处理过程中占得比重就越大。

Instead, create web-based interfaces based on serializing documents or sets of objects between client and server. Your remote communications should work like the order form you fax to the catalog company: The client machine should be capable of working for extended periods of time without contacting the server. Then, when all the information to complete the transaction is filled in, the client can send the entire document to the server. The server's responses work the same way: When information gets sent from the server to the client, the client receives all the information necessary to complete all the tasks at hand.

相反,基于web的接口在创建时,要基于序列化的文档或者客户端和服务器端之间对象的集合。你的远程交互应该像形成传真发送给目录公司一样工作:客户机应该能在不联系服务器的情况下,多工作一段事件。然后,当所有完成交易需要的信息被填满时,客户端就能够够将整个文档发送给服务器。服务器也以同样的方式进行响应:当信息从服务器发送到客户时,客户接收所有需要的信息来完成手边的工作。

Sticking with the customer order metaphor, we'll design a customer order-processing system that consists of a central server and desktop clients accessing information via web services. One class in the system is the customer class. If you ignore the transport issues, the customer class might look something like this, which allows client code to retrieve or modify the name, shipping address, and account information:

再看看客户订单的隐喻,我们将设计一个客户订单处理系统,它包含一个中心服务器和桌面客户,两者之间通过web服务访问。系统中的一个类是客户类,如果你忽略传输问题,客户类看起来可能是这样的,允许客户代码接收或者修改名字,邮寄地址,账号信息:

  1. public class Customer
  2. {
  3.   public Customer( )
  4.   {
  5.   }
  6.  
  7.   // Properties to access and modify customer fields:
  8.   public string Name
  9.   {
  10.     // get and set details elided.
  11.   }
  12.  
  13.   public Address shippingAddr
  14.   {
  15.     // get and set details elided.
  16.   }
  17.  
  18.   public Account creditCardInfo
  19.   {
  20.     // get and set details elided.
  21.   }
  22. }

The customer class does not contain the kind of API that should be called remotely. Calling a remote customer results in excessive traffic between the client and the server:

客户类不包含那类应该远程调用的API。在客户与服务器之间调用一个远程客户将导致过多的流量:

  1. // create customer on the server.
  2. Customer c = new Server.Customer( );
  3. // round trip to set the name.
  4. c.Name = dlg.Name.Text;
  5. // round trip to set the addr.
  6. c.shippingAddr = dlg.Addr;
  7. // round trip to set the cc card.
  8. c.creditCardInfo = dlg.credit;
  9.  

Instead, you would create a local Customer object and transfer the Customer to the server after all the fields have been set:

相反,你应该创建一个本地的客户对象,在所有的字段被设置完毕之后再将该对象传递给服务器:

  1. // create customer on the client.
  2. Customer c = new Customer( );
  3. // Set local copy
  4. c.Name = dlg.Name.Text;
  5. // set the local addr.
  6. c.shippingAddr = dlg.Addr;
  7. // set the local cc card.
  8. c.creditCardInfo = dlg.credit;
  9. // send the finished object to the server. (one trip)
  10. Server.AddCustomer( c );

 

The customer example illustrates an obvious and simple example: transfer entire objects back and forth between client and server. But to write efficient programs, you need to extend that simple example to include the right set of related objects. Making remote invocations to set a single property of an object is too small of a granularity. But one customer might not be the right granularity for transactions between the client and server, either.

客户的例子描述了一个显著又简单的例子:在客户和服务器之间来来回回的传递整个对象。但是为了编写高效的程序,你需要扩展该简单的例子来包含相关对象的集合。从远端来对一个对象的某个属性进行设置,粒度太小了。但是对于客户和服务器之间的传输来说,一个客户大小的粒度也许仍然不合适。

To extend this example into the real-world design issues you'll encounter in your programs, we'll make a few assumptions about the system. This software system supports a major online vendor with more than 1 million customers. Imagine that it is a major catalog ordering house and that each customer has, on average, 15 orders in the last year. Each telephone operator uses one machine during the shift and must lookup or create customer records whenever he or she answers the phone. Your design task is to determine the most efficient set of objects to transfer between client machines and the server.

为了将这个例子扩展成现实世界中你可能遇到的设计问题,需要对该系统做一些假设。该软件系统支持:一个主要的在线卖主和一百万个客户。假设那主要是一个订阅房间的目录,平均每个客户上一年有15个订单。在轮班期间,每个电话操作员使用同一个机器,不管客户是否回答电话,都需要查询或者创建一个客户记录。你的设计目标就是决定最高效的对象集合,让它们在服务器和客户机器之间进行传递。

You can begin by eliminating some obvious choices. Retrieving every customer and every order is clearly prohibitive: 1 million customers and 15 million order records are just too much data to bring to each client. You've simply traded one bottleneck for another. Instead of constantly bombarding your server with every possible data update, you send the server a request for more than 15 million objects. Sure, it's only one transaction, but it's a very inefficient transaction.

可以这样开始:清除一些明显的选择。获得每个客户和每个订单显然是被禁止的:一百万个客户和一千五百万个订单记录对于每个客户来说,太多了。这对每个客户进行一次处理都是一次瓶颈。你不应该让每次可能的数据更新都持续的冲击你的服务器:为多于一千五百万个对象向服务器发送一次请求。当然,这只是一次传输,却是非常低效的传输。

Instead, consider how you can best retrieve a set of objects that can constitute a good approximation of the set of data that an operator must use for the next several minutes. An operator will answer the phone and be interacting with one customer. During the course of the phone call, that operator might add or remove orders, change orders, or modify a customer's account information. The obvious choice is to retrieve one customer, with all orders that have been placed by that customer. The server method would be something like this:

相反,考虑如何最好的得到一系列对象的集合,用来创建一个好的数据集合代理,处理一些在后来几分钟内一定会处理的对象。一个操作员会接一个电话,和某个客户进行沟通。在电话交谈的过程中,该操作员可能添加、移除、修改订单,或者修改客户的账号信息。明显的选择就是获得一个客户以及该客户的所有订单。服务器上的方法可能是这样的:

  1. public OrderData FindOrders( string customerName )
  2. {
  3.   // Search for the customer by name.
  4.   // Find all orders by that customer.
  5. }


Or is that right? Orders that have been shipped and received by the customer are almost certainly not needed at the client machine. A better answer is to retrieve only the open orders for the requested customer. The server method would change to something like this:

这正确么?传递给客户并且也被客户接收的订单可能是客户机器并不需要的。更好的答案是,仅仅接收提出请求的客户的当前订单。服务器方法可以这样修改:

  1. public OrderData FindOpenOrders( string customerName )
  2. {
  3.   // Search for the customer by name.
  4.   // Find all orders by that customer.
  5.   // Filter out those that have already
  6.   // been received.
  7. }

You are still making the client machine create a new request for each customer phone call. Are there ways to optimize this communication channel more than including orders in the customer download? We'll add a few more assumptions on the business processes to give you some more ideas. Suppose that the call center is partitioned so that each working team receives calls from only one area code. Now you can modify your design to optimize the communication quite a bit more.

你依然在使客户机为每个客户的电话交谈创建一个新的请求。有更好的方法来优化这个交流渠道么?而不是下载客户所有的订单。我们可以在商务处理过程中添加一些假设来给你更多的想法。假设,呼叫中心是分布的,那样的话,每个工作团队仅仅接收来自一个地区的电话。现在你可以修改你的设计来对交流进行稍大的优化了。

Each operator would retrieve the updated customer and order information for that one area code at the start of the shift. After each call, the client application would push the modified data back to the server, and the server would respond with all changes since the last time this client machine asked for data. The end result is that after every phone call, the operator sends any changes made and retrieves all changes made by any other operator in the same work group. This design means that there is one transaction per phone call, and each operator should always have the right set of data available when he or she answers a call. Now the server contains two methods that would look something like this:

每个操作员在轮班开始的时候,接收那个地区的更新后的客户和订单信息。在每次电话后,客户应用程序将修改后的数据返回到服务器,服务器将对客户机上次请求数据后做出的所有更改做出反应。最终结果是,在每次电话之后,操作员发出任何修改,并且获得同一组内其它操作员做出的所有修改。该设计意味着,每次电话之后只有一次传输,当每个操作员接听电话时,总能有可用的正确的数据集合。现在,服务器包含了2个方法,看起来像这样:

  1.  
  2. public CustomerSet RetrieveCustomerData( AreaCode theAreaCode )
  3. {
  4.   // Find all customers for a given area code.
  5.   // Foreach customer in that area code:
  6.     // Find all orders by that customer.
  7.     // Filter out those that have already
  8.     // been received.
  9.   // Return the result.
  10. }
  11.  
  12. public CustomerSet UpdateCustomer( CustomerData
  13.   updates, DataTime lastUpdate, AreaCode theAreaCode )
  14. {
  15.   // First, save any updates, marking each update
  16.   // with the current time.
  17.  
  18.   // Next, get the updates:
  19.   // Find all customers for a given area code.
  20.   // Foreach customer in that area code:
  21.     // Find all orders by that customer that have been
  22.     // updated since the last time. Add those to the result.
  23.   // Return the result.
  24. }

But you might still be wasting some bandwidth. Your last design works best when every known customer calls every day. That's probably not true. If it is, your company has customer service problems that are far outside of the scope of a software program.

但是,你仍然可能浪费了一些带宽。你最后的设计,在每个已知客户每天打电话的情况下,工作的很好。但是这可能是不对的。如果是的话,你的公司将面临客户服务的问题,远远超出了软件问题的范围。

How can we further limit the size of each transaction without increasing the number of transactions or the latency of the service rep's responsiveness to a customer? You can make some assumptions about which customers in the database are going to place calls. You track some statistics and find that if customers go six months without ordering, they are very unlikely to order again. So you stop retrieving those customers and their orders at the beginning of the day. That shrinks the size of the initial transaction. You also find that any customer who calls shortly after placing an order is usually inquiring about the last order. So you modify the list of orders sent down to the client to include only the last order rather than all orders. This would not change the signatures of the server methods, but it would shrink the size of the packets sent back to the client.

在不增加传输次数或者服务器的传输延时的情况下,我们如何进一步的限制传输的大小呢?你可以对数据库里面的哪些客户要打电话做出假设。可以跟踪一些统计,会发现,如果客户6个月没有订单的话,它们就不再可能下订单了。因此在开始的时候,就停止接收这些客户以及它们的订单。这就缩小了初始传输的大小。你同样也可以发现,任何不久前打过电话的客户,在提交订单后,通常会查询最后一个订单。因此你修改发送到客户的订单的列表,仅仅包含最后的订单,比包含全部的订单要好。这不会改变服务器上方法的签名,但是会缩小发回到客户的数据包的大小。

This hypothetical discussion focused on getting you to think about the communication between remote machines: You want to minimize both the frequency and the size of the transactions sent between machines. Those two goals are at odds, and you need to make trade-offs between them. You should end up close to the center of the two extremes, but err toward the side of fewer, larger transactions.

这个假设的讨论,集中在让你考虑远程机器之间的交互:最小化机器之间传输的频率和大小。这两个目标有点冲突,你需要在它们之间做出折衷。你应该取两个极端的中点,而不是错误的选择过大,或者过小的传输。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值