RWSD: Service “good-practices”

Posted by Andy on June 30, 2009
RWSD, Software Development / No Comments

I thought I’d jot down a list of what I would consider to be good practices when developing software services.  I’m mainly a C#/.NET developer, so I’ll focus on exposing services via WCF, but most of these concepts are language “agnostic.”  A lot of these are pretty obvious and well-known; however, I’ve seen each of these violated enough times in the span of the last two-to-three months to make me think that maybe they are not so well-known…

  1. Don’t use a raw “object” type for any parameter, property, return value, or really anything else exposed as a service. Using a raw object makes it exceedingly difficult to achieve automatic serialization of messages coming in or out of the service method.  If you expose an “object” in your service, the caller has no idea what to pass in, and you have no way to deserialize whatever XML you end up receiving, unless you import XSDs from the client, which is a pain.  Importing XSDs from another company in your service creates an undesirable dependency between your service and the client.  Sometimes this is necessary, but dealing with WSDLs and XSDs is almost always more painful than it should be.
  2. Use low-level (primitive) datatypes as much as possible for the the properties of your input and output types, and avoid any objects that have properties that go more than two levels deep in object complexity. Web services are supposed to make it easy to achieve interop between any two languages, but I’ve never seen any web-service integration that was easy.  Internal services (where you can control all the datatypes) are usually less painful, but when dealing with external partners, trying to integrate with a foreign WSDL with a complex object model almost always requires some hacking.  The simpler the objects are, the easier it is to integrate.
  3. Don’t expose any object in your service that has anything to do with your backend data-access-layer. This creates a very undesirable dependency between your clients and your data-access layer.  If you expose a data object over the wire, and an external party integrates with your service, it now becomes very difficult to make any breaking changes to your data access layer.  Always create a simple data-transfer object to expose in your service, and hide everything having to do with your domain data.  Another reason to use simple DTOs is it forces any lazily-loaded data objects to be hydrated as you are copying the data to the DTO.
  4. If you need to leave room for extension in a service method, try to define concrete parameters for as many of the fields as possible, and add a string-string dictionary as the last parameter to your service method so the caller can attach additional data.  Another option is to expose an argument that can be subclassed to add functionality. I personally prefer to use the string-string dictionary, because dealing with subtypes adds an extra level of complexity for your integration.  The string-string dictionary is slightly painful, because it’s not really type-safe, requires clunky casting and validation, and is not easily documentable or intuitive to use.  This string-string dictionary is also a very useful technique for receiving and returning client-specific data in a service call.  A lot of times, the client will have their own identifiers for messages, and if you end up needing to perform asynchronous processing on the message, with a callback to the client, it becomes simple to store the data on your side, then return it back to the client with the callback, so they know what the response corresponds to on their side.
  5. With #4 said, avoid “untyped” data as much as possible. By “untyped,” I mean any data that has to be converted/cast at the receiving end before it can be used.  I’ve seen several services where the only argument exposed is a string-string dictionary.  With this, the client can pass whatever arbitrary keys they want, and you have to validate and cast the corresponding values before doing anything.  Try to use concrete, serializable types as much as possible.
  6. Don’t abbreviate anything! One of my coding pet-peeves is when people abbreviate arbitrary words in a method or variable name.  It takes a bit longer to type out the full word, but if you type it all out, you’ll never have to remember what you abbreviated, and the code becomes much more obvious to read and work with.  It is acceptable to abbreviate common acronyms, such as “Http,” or “Sql” however, but try to avoid abbreviating any domain-specific terms.
  7. Don’t define an argument or return type, then share it across multiple methods. There are a few cases where this might be useful, like if your methods all need to return the exact same data, but I’ve seen cases where the shared object ends up needing some custom parameters for certain methods.  When this happens, you end up adding some additional properties, which are coincidentally “given” to all the other methods.  Now you’re stuck with an overblown object that has too many properties for any one method, and requires additional validation code and error handling.
  8. Avoid throwing exceptions out of service methods. If possible, try to catch all exceptions within your service method, and return back some sort of status object indicating what went wrong.  Dealing with SOAP faults is yet another thing that adds an extra level of difficulty to any third-party integration.  I’m not a huge fan of dealing with error codes, but I think it’s actually cleaner and easier to deal with on the client end.
  9. Keep the service interface as compact and intuitive as possible. Avoid any generic methods that can be used to accomplish different types of business use cases.  Avoid “super” interfaces that provide all of the methods you need for an application.  Creating an interface with 20+ utility methods might seem like an easier way to deploy and integrate the service, but it creates a ton of undesirable dependencies between your client and your service.  Follow the “I” principle in the SOLID principles: “Interface Segregation” – segregate the parts of your interface into the smallest possible units that a client needs.  If a client only needs a few methods in your interface, don’t make them integrate with the entire thing.  By doing this you also give yourself the option to move the different services to different locations in your backend, without moving the entire thing, and gives you the ability to scale out parts of your system that might get hit harder.
  10. For heavy service loads, use a “HTTP-Queue” bridge to process messages in an asynchronous fashion. If you have any part of your system that has to process varying loads of messages, you can use an HTTP bridge to receive and enqueue incoming messages.  The HTTP layer exposes a web service to receive the messages in an interop-friendly format (SOAP, etc.), but simply passes the message directly onto a queue for processing by a backend service.  WCF makes this dead simple, you can setup a WCF web service application (hosted in IIS) to listen for messages, then setup a NET MSMQ binding between the web app and your backend Windows Service app.  Using queues makes it slightly more difficult to handle the response, because it requires a web-service callback to the client, but it’s a really solid pattern.
  11. If possible, don’t do anything overly fancy with the serialization of objects in your service. In many cases, your client will be using a different language than you to integrate with your service.  If you make the serialization of types complicated or non-standard, the client will most likely have problems integrating with your service using the default tools of their language.
  12. Keep everything as simple as possible! Try to avoid using any of the more advanced features that might be provided by your language.  I guess WCF is sort of an exception to this rule, since it abstracts away almost the entire service protocol layer, and does a ton of work behind the scenes; however, try to keep your service interface as clean, simple, and standard as possible.  Even the simplest of services can give your clients headaches to integrate with, so just try your best to make it easy on them.

Tags: , ,