|
March 2007
March 31, 2007
Tasks, Messages, & Transactions – the Holy Trinity
The discussion is picking up around disconnected Web Service interaction scenarios. Here’s a summary of what’s going on for those just joining us, but I would suggest reading the full posts as well:
Andres: “Basically, there is no disconnected mode… if you plan to build a multi-tier application with ADO.NET Orcas in the middle tier, you will need to hack your own change tracking mechanism in the client, send the whole changeset, and apply it in the middle tier.”
Udi: “[In] the UI, … tasks often corresponded very well to the coarse-grained messages we employed in terms of SOA.”
Jesse: “I have to disagree with Andres and agree with Udi.”
(Sorry, I just couldn’t resist. Here’s the important part)
“Andres rightly points out that you don’t want 5 different methods for updating an order. Although you technically could do it that way, you probably don’t want to be ignoring the transactional context of the conversation or locking tables for significant time periods.”
And, “The backend will still have all the update methods you are used to using, so you’re not writing a bunch of extra code in the DB. However, this approach is far more powerful. One reason why is that now you have an operation defined and you can control everything about that operation. You know when the call comes in through that method that you have a customer service rep trying to update an order and can build in logic based off of that.”
Andres: “Now, we need to send those changes to the server, together, because I want them executed in a single transaction. I cannot have a service called ‘ModifyOrderCustomerInformation’, another ‘AddALineToOrder’ and ‘DeleteLineFromOrder’. I need an ‘UpdateOrder’ service. You can use a diffgram to do it, or you could build your own change-serialization mechanism.”
And, “The only way I see is to map the UI to the service interface, so the user can ‘Change Address’ or ‘Update Marital Status’ as different operations in the UI layer, but I can’t let the user ‘Modify the Customer’. It’s a lot of work, and I seriously doubt that the users will like it.”
OK, now we’re all set.
Just to get something small out of the way, every Human-Computer Interaction (HCI) professional I have worked with has been in favor of task-based UIs. Every user that I have met that has used both styles of UI, task based and “grid” based, has reported that they were more productive when using the task based UI for “interactive work”. Data entry is not interactive work, so grids might be more suitable there, although improvements in bulk-loading technology and OCR have decreased the amount of “plain” data entry that I’m encountering. It also may be that I’m working less on systems where data entry is of significant importance.
First of all let’s agree that writing business logic for something like “DeleteLineFromOrder” or “UpdateMaritalStatus” is easier than for “UpdateOrder”, given that it fit with the overall system design.
Second, let’s agree that the business logic for something generic like “UpdateOrder” is composed of the various specific cases like “DeleteLineFromOrder” and “AddLineToOrder”.
Third, let’s agree that if the UI was task based, it would be easy for the client side to activate the correct web service/method – seeing as there is a high correlation between the tasks and the service methods.
Therefore, the question becomes how do we do all the above specific work in a single transaction? One answer may be to make the service statefull and using something like WS-Atomic Transaction to tie the various calls together. The problems in this approach are already well known. Another solution uses a messaging paradigm, something I wrote about before.
If we represent each of the tasks not as “web methods”, but rather as messages we could send all these messages together in one envelope to the server, and have it process all of them in a single transaction, activating the specific business logic pieces one after the other.
I’ll be using a code-first approach to describe the solution instead of an XML-first one since it abstracts away the technology, but it is still a contract-first approach.
Each message type is just a class that implements the “IMessage” interface. So we’d have a class called “DeleteLineFromOrderMessage” which is a Data-Transfer Object (DTO) whose members/properties are the data needed for processing – in this case, the order Id and the order line number. The same would be true for the class “AddOrderLineMessage”, it would contain the order Id and some other member for the order line data quite probably a DTO itself (so that we can use it in other places as well).
The client would generate an instance of the appropriate message class as the user finishes each sub-task, saving them up. When the user would click “confirm”, the client would send all these message objects to the server in one go with this API,
void IBus.Send(params IMessage[] messages);
Like so:
myBus.Send(addOrderLineMsg, delOrderLineMsg);
The bus would take all these objects and wrap them in a single SOAP envelope, and send them to the server, probably on a transactional channel, but that’s a configuration issue. At the server side, the bus would activate the transaction (because of the transactional channel) and start dispatching each of the specific messages to its message handler, one at a time, in the same thread.
So as you can see, there is no “transactional conversation”, and therefore we’re not locking tables in between calls, because we’ve gotten rid of “in between”. It’s just like the generic update in terms of transaction time and network hops, just with specific, simple business logic.
Andres might retort to that, “Even if Udi is right, and that’s the way applications should be built, I doubt most mere mortals in Earth will want to do it.” In fact, he didJ If the frameworks supporting this style of development were supplied by Microsoft, I’m sure that most developers wouldn’t have a problem with it. When provided with such a framework, every developer I’ve worked with said that they wouldn’t want to go back to the “old way” (would that make Orcas outdated?). I’ll be putting up my frameworks soon, as well as examples on how to use them, it’s just taking longer than I expected.
Before closing, I just wanted to address the points Andres raised about concurrency:
“You could also need to know the previous values for optimistic concurrency checkings.”
I’ve written about how to do this before in regards to Realistic Concurrency, and the best example I have in terms of code is Better Domain-Driven Design Implementation.
This has gone a little longer than I planned, but I still don’t think I’ve covered everything in enough depth. I’d most appreciate investigative questions to help me shed light on the murkier parts of this, either as comments, posts on your own blog, or even via email. Let’s continue the conversation.
--
If you liked this article, drop by and visit my blog, http://udidahan.weblogs.us where you can find this post and many others that I haven't posted here. You can also find podcasts about SOA and Web Service related topics, as well as webcasts and other articles I've written. Why not play it safe and subscribe so that you don't miss any of the content I put up?
Posted by Udi Dahan at 10:13 AM Permalink
|
March 26, 2007
Errors, Exceptions, and Asynchronous Web Services
When a client initiates an action on a service by sending it a command message, it is reasonable to assume that that client will want to know about the success, or failure of that action. When working with today’s Web Service technologies, many developers perform these Web Service calls synchronously and have the service throw an exception to provide details about the specific failure. This is especially common when the Web Service call needs to return some other data, for instance the ID of the entity just added. Scalability issues around these kinds of synchronous interactions are leading the industry to more asynchronous message-based communications. The only problem is that the stack-based exception model for dealing with failure breaks in the asynchronous model.
[Originally published here.]
Let’s see some code that demonstrates simple, synchronous communications and handles some error case:
public class UserController
{
public void AddUser(string username)
{
try
{
int userId = myServer.MyService.AddUser(username);
// add user to list of users
}
catch(DuplicateUsernameException)
{
// notify user and ask to choose a different user name
}
}
}
Once we move to an asynchronous interaction model, the thread that makes the Web Service call is no longer waiting for a response – in other words, the call to “AddUser” in the above example has a “void” return type. This begs the question how would the user ID be returned, let alone information about the success, or failure of the call.
The pattern which handles this scenario is called Asynchronous Completion Token. This pattern suggests having the client pass an additional object as a part of its call to the Web Service. This object would contain both the logic and data needed to handle the result of the call. In .NET, asynchronous invocations are supported with the AsyncCallback delegate and its corresponding IAsyncResult interface for getting the data – let’s take a look at the resulting code:
public class UserController
{
public void AddUser(string username)
{
myServer.MyService.AddUser(
username,
(AsyncCallback)delegate(IAsyncResult result) {
// remember that this code runs on a background thread
int userId = Convert.ToInt32(result.AsyncState);
// add user to list of users
}
);
}
}
The code defined as the asynchronous callback will be run by a background thread at some time after the original thread that called “AddUser” had already exited the method. The next question is “what happened to the exception?”, or how do we use exceptions in the asynchronous case.
The simple answer is that we can’t use exceptions to handle these errors – simply because there is no thread that is waiting to catch such an exception even if it were thrown. This is strictly a client-side limitation, on the server-side there is no problem in throwing an exception – since it will be caught by the Web Services infrastructure and marshaled to the client. What this means is that we need an alternative mechanism for returning error information to the client. At the most basic level, this means using return codes, integer values representing the various error conditions. Going one small step up, we can use an enumeration to communicate the intent of each value like so:
public enum MyServiceErrorCodes { NoSuchUser, DuplicateUserName, ... }
Unfortunately, we find that the basic .NET types are too generic to support this scenario in that we need to support both the information returned by the Web Service and the error code. If we assume an integer based error code, we can create our own implementation of the Asynchronous Completion Token and use it like this:
public delegate void AsyncCompletionToken(object state, int errorCode);
public class UserController
{
public void AddUser(string username)
{
myServer.MyService.AddUser(
username,
(AsyncCompletionToken)delegate(object state, int errorCode) {
// remember that this code runs on a background thread
if (errorCode == (int)MyServiceErrorCodes.DuplicateUserName)
//notify user
else
{
int userId = Convert.ToInt32(state);
// add user to list of users
}
}
);
}
}
In order to make this code work, we need to change some of the infrastructure-level behaviors of the Web Services stack, something that is much easier to do with Microsoft’s Windows Communication Foundation (WCF) than with previous implementations (like the Web Service Enhancements and the basic XML Web Services of .NET). A coming article will show exactly how to do this.
The important thing to understand when moving to asynchronous communications between clients and Web Services is that the old exception-based models for communicating errors no longer work. This impacts the way client-code is written to a large degree; server code is affected as well – returning error codes rather than throwing exceptions, but this is a relatively minor change. One final note, in both the synchronous and asynchronous cases the errors returned by the Web Service are a part of its contract and need to be versioned carefully. The move to increasingly scalable Web Services comes with an increase in complexity as well. Hopefully this article has helped you better understand the details of how to make asynchronous Web Services work for you.
Posted by Udi Dahan at 05:33 PM Permalink
|
March 17, 2007
[Podcast] Autonomy & Loose-Coupling -- Chicken & Egg?
The latest "Ask Udi" podcast is online here
--
Udi answers questions around autonomy and loose-coupling, which one needs to be taken care of first, as well as how all that affects contract design and service reusability.
Posted by Udi Dahan at 08:08 AM Permalink
|
March 12, 2007
Request/Service state affinity – don’t.
I saw this question today on the one of the blogs I follow, and seeing that it’s a question that variations of it pop up all the time, I thought that I’d chip in with my 2 cents.
How do I store some state about the current request so that I can use it later during the same service operation?
One analysis I read came at it from a technological angle – how to do this with WCF. I want to take a look at two other angles here.
Originally posted here.
The first has to do with one interpretation of the question – in the course of handling that request, there are numerous objects involved. How can we make it so that all of them have access to the request data? From a technological perspective, the answer is simple – make it thread-static (in .net this is done by applying the ThreadStaticAttribute to it), or store it in some thread-local-storage. From a design perspective, though, things aren’t all that clear. Which property of which class contains the request data, so that we can mark it with such an attribute, or under what key is the data stored in the thread-local-storage?
What I usually do is use my “IBus” interface, which exposes a “MessageBeingHandled” thread-static property. Any object that needs state about the current request makes sure to get an instance of “IBus” injected. The classic example of objects that need this data include message handlers (implementing the “IMessageHandler” interface). For more information about this design, take a look at this.
The second interpretation looks at having that request data available to the service on subsequent invocations. Personally, I don’t like the idea of having this data in-memory on the object that serviced the original request. One reason I don’t like it is that it creates an affinity between the client and the specific server handling its requests. Those of you who know me already are expecting this…
What if the server restarts?
Will that client’s state be lost? Well, not if we persisted it somewhere durable instead of just in memory. Will we stop servicing requests from that client until the original server becomes available again? Well, if the data was durably persisted, then any server could pick it up. And this is exactly what BizTalk does. You don’t want to implement BizTalk again, do you?
I can tell that some of you are surprised to hear me say this. Such a small requirement, and already we need BizTalk? Did Udi really say that?
Well, there is another, simpler way. If what you have is some kind of back-and-forth between the client and the service, you could use the Message History pattern and pack up the previous request data into the messages being sent. Although we’re increasing the message size, we’ve made it so that any server can handle any request and have access to all the previous data without creating some sort of durable contention area within the service like a database. Another option is to look at long-running workflow to model these interactions.
Finally, when it comes to ultra-scalable systems, I strongly suggest keeping the network dumb and pushing the smarts out to the edges – the clients. If you don’t need to have one client pick up where another client left off, this could be the ultimate solution. It combines with the Message History pattern and ends up sending only the data necessary on subsequent requests, thus keeping message size to a minimum. Also, your service doesn’t have to handle the state any more making it capable of handling more concurrent clients.
State management is the heart of any distributed systems development effort. Unfortunately, there aren’t any easy answers to it, but it’s important not to gloss over it if you want to have any hope of scalability in the future. Patterns help, but eventually we have to make the tradeoffs ourselves. Just don’t go running to one product or another in the hopes that it will make everything magically better.
Posted by Udi Dahan at 04:58 AM Permalink
|
March 03, 2007
It’s poison, I tell you. Poison!
When developing systems that handle messages that arrive on queues or other asynchronous transport mechanisms, broader failure scenarios need can be handled than in RPC-style communications. One of these failure scenarios that’s been getting some recent play on the blogosphere is that of poison messages. Microsoft’s Nicholas Allen does a great job describing what defines a poison message and the common ways of handling it. Thomas Restrepo adds another facet to the whole issue of poison message handling that is a must-read.
[Originally posted here]
Let’s take a rather simple case, but definitely a common one when we start versioning our services – that of the receipt of a malformed message. It could be that we were unable to deserialize the data or something more complicated, but at the most basic level, there’s nothing the service can do with the message. This is often expressed in terms of some sort of exception that reaches all the way down to the messaging infrastructure. If we were using the queue in a transaction, we’d see a rollback and the message return to the queue, causing the service to loop endlessly on that message. This sort of failure case is best handled by having the messaging infrastructure just move the message to an error queue.
Other kinds of failure conditions include having our DB transactions fail because they were chosen as the victim of a deadlock. Matts does a good job of describing why these things occur and how to handle and minimize their occurrence. The appropriate system level error handling is to just retry the transaction a bit later. Once again, if we were using our queues within the context of a transaction scope that included that of the database, the message would return to the queue when that deadlock exception bubbled through. This is exactly the behavior that we want in this case, although having the message go to the end of the queue instead of the front would also be just fine.
Other kinds of failure conditions we might run into include things like unique-constraint violations, attempting to perform actions on entities that no longer exist, and other activities where trying them over again probably won’t yield any different behavior. In these cases, if an exception were to cause the perpetrating message to return to the input queue we’d get exactly the wrong behavior. At the very least we would want to maybe log the exception information, but putting the message in the error queue doesn’t seem right. I mean, there was nothing wrong with the message, it’s just that the application state had moved on or that some other logic failed. One of the reasons for putting the message in the error queue is so that a person can manually fix what’s wrong with the message and send it back to the input queue for reprocessing. In these failure conditions, that hardly seems likely.
What I usually suggest for handling failures when messaging is involved goes something like this. First of all, have an error queue/topic – they’re exceedingly useful. Second, don’t automatically roll messages back. If the service wants to retry, they should do so after handling all the other messages already in the queue – unless (!) you’ve got message ordering issues like Thomas mentioned. This means having the service request the messaging infrastructure send the message back to itself. When it comes to exceptions around the logic handling the message, just write them to a log and be done with it. The service logic will probably log anything else that’s of particular interest to it. This leaves us with the malformed message scenario. In that case, the communications layer can know that the problem occurred before any service logic (like deserialization exceptions) and therefore can (rightly) pass the message along to the error queue.
Of course, for the above guidance to be valid, we need to educate service logic developers as to when it is appropriate for them to throw exceptions and when they actually have to send the message back themselves. It is often the case that by taking on certain constraints, we can greatly simplify the handling of scenarios which are difficult in the general case.
Posted by Udi Dahan at 05:03 PM Permalink
|
|