共用方式為


Two Web Service "Don'ts"

There are at least 2 things I'm still seeing in some web service implementations we should not be seeing. In my first post, Refactoring XSLT , I was shooting for original ... interesting ... maybe even useful ... but not a best practice (at least not until more testing is done). In this post I'm writing about 2 best practices - independent of the platform (.NET, J2EE, etc) being used. One of them you've [hopefully] heard 1000 times and this will be your 1001st. You might not have heard the other one before ... or at least heard the rationale behind it.

Don't code on the edge

Web Services are essentially the buttons and textboxes other applications use to access some functionality. The endpoint (an ASMX file in the case of .NET) is just that ... an endpoint ... and that's all it should be. You have no business putting application logic in an ASMX page (or its codebehind) than you do in a windows form. I would even go as far as to say there should only be one line of code (2 at the max) in each of your exposed operations. How 'bout an example?

 [System.Web.Services.WebMethod()]
public TempResponse GetTemp( TempRequest tempreq )
{
    return TempCalculator.GetTemp( tempreq );
}

As you can see, I'm calling a static GetTemp() method on a different class and returning its result. There might be another line if you have to call an instance method or maybe even another one if TempCalculator is on a different physical tier like Michele illustrates here, but the important thing to notice is the lack of any "temperature" code in the method's body. Let your business logic do that and put it in a different library assembly. "What's the big deal Don? Why not just put it in the codebehind?" If proper analysis and design have been accomplished on the business logic, it actually won't fit on the edge very easily. So, if you have business logic on the edge (in the endpoint operations or in the user interface) there's a very good chance the business logic has a poor design. Forcing the business logic in a separate library encourages solid software design.

One parameter and one return to rule them all

I'm actually illustrating this in my little WebMethod above. I'll admit my tent is pitched in the very middle of the messaging camp (opposed to the RPC camp), but that's not why I recommend only one input parameter and a single return type on all Web Service operations. The reasons really revolve around versioning and interoperability. I've been writing an article on versioning Web Service for months now to eventually be published on MSDN. I thought I lost it on a bad hard drive, but thanks to my close friend Yex, who still had a draft I sent him, I didn't have to start over from scratch. You'll have to take my word for it for now, but in the article I show how it is much easier to version datatypes (parameters and return types) than it is the interface of the operation. In other words, it's much easier to add a ZipCode member to our TempRequest type than it is to add a ZipCode parameter to our GetTemp() method while retaining compatibility with older and newer versions. We've also found the serialization behaviors between .NET and other platforms is very comparable with regard to custom types (opposed to primitive types). Lastly, it's a nice consistent pattern ... no surprises.

I think one of the reasons we're still seeing these bad practices is because developers aren't hearing and seeing them put in place often enough. DonXML talks about this problem in more detail here. I too have been guilty of using bad coding practices in demonstrations and sample code. Of course the main reason we do it is because we're trying to illustrate a certain concept and don't want to spend the time to work up a solid design. We also don't want to confuse the recipient with details outside of the thing we're trying to illustrate. So what's the answer? I think if we just point out the bad practices to the recipient - either using code comments or verbally - it would go a long way to curb this problem. You also know as well as I do that many real-world applications are just demos and sample code that has evolved. Let us all keep reminding everyone about the dangers of ignoring a solid design over productivity - becuase doing so will cause productivity to suffer eventually. Happy coding!

Comments

  • Anonymous
    November 27, 2004
    While I agree that you shouldn't put business logic in your web methods, I don't agree that you should have a single parameter type and return value. Versioning is really not a problem (I'm interested in seeing your article to see the points you bring up). .NET's XML serialization handles missing parameters just fine (well, value types get a default value, which may not be what you're after, but reference types work as you'd expect -- you get a null). Even if you're not using a .NET client to talk to your web service, missing XML nodes are handled just the same (because the incoming SOAP packet is still going to run through the XmlSerializer -- if your caller isn't giving you XML you can deserialize, a single uber-object versus multiple parameters isn't going to save you). If GetTemp needs another parameter, you don't need to create GetTemp2, and you don't need to add another member to TempRequest. Add the parameter to GetTemp, and handle default/missing cases in your code. Sometimes it may make sense to put the new parameter on the TempRequest object, but not always. Following your example, if TempRequest really is Address, it makes sense to put a ZipCode field on the object rather than as another parameter to GetTemp. However, if ZipCode doesn't logically fit into TempRequest (ie, TempRequest is not an Address), then don't put it there. Whether it's passed as a parameter or as a member of the uber object, you're still going to have to do the work to handle default values or nulls in your code.

    My group investigated this very issue a few months ago while designing a new set of web services, and decided that using the XmlSerializer to fill in defaults or nulls for missing parameters was sufficient for our purposes. We mainly looked at versioning, which Just Worked (tm), no hoops to jump through and no unsightly Foo2 or FooEx methods necessary. We're strictly .NET (for now, we have full control over our callers, and know that they will use .NET clients), so we didn't bother to look into interoperability issues. From what you say about serialization on other platforms, I don't anticipate that we will have problems there, either.
  • Anonymous
    November 28, 2004
    My web service methods were never quite that simple. Close, but still having a little more, specifically something like the following

    [System.Web.Services.WebMethod()]
    public TempResponse GetTemp( TempRequest tempreq )
    {
    try
    {
    return TempCalculator.GetTemp( tempreq );
    }
    catch (Exception e)
    {
    log(e);
    throw;
    }
    }

    Our reason for this pattern is that the unhandled exception handler is not invoked if an unhandled exception occurs in a web service method. Instead something catches the exception further upstream, converting it into a Soap Fault block that gets returned to the client. So we wrap the business logic in a try-catch block so that the information is logged appropriately.
  • Anonymous
    November 28, 2004
    Hi Bruce,

    Have you looked at the Exception Manager Application Block from MS?

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/emab-rm.asp

    It allows the logging of exceptions at the web service layer by setting up various entries in the web.config. It essential extracts error-handling our of the code, making it a lot cleaner. Very handy! :)
  • Anonymous
    November 29, 2004
    Hi Kieran,

    I sure have. But unless you publish the exception to the ExceptionManager, none of the goodness of EMAB is invoked. And, since I don't like having global try-catch blocks in my business logic, I usually rely on unhandled exception handlers as my final point of logging the really unusual (as opposed to the just exception :) stuff. Doesn't work in web services, which means that EMAB would only alter my code sample in that the log(e) statement would become more like ExceptionManager.Publish(e).

    Unless I'm missing something pretty basic, that is ;)
  • Anonymous
    November 29, 2004
    Another common approach to the unhandled exception issue is to use a SoapExtension (http://www.codeproject.com/aspnet/ASPNETExceptionHandling.asp).

    If you think about it, a SoapExtension is a logical place for this type of code. It doesn't have anything to do with defining the endpoint contract so it shouldn't go in the codebehind and it's not business logic.