I've been working on usability studies for Indigo for the last 12 months now and have been learning a lot. I just learned another very valuable lesson this week, thanks to Steve Swartz.
During a study I was running last week, participants worked with a service that maintained state between calls for each session but that did not share the same state across multiple sessions. However, participants expected that the state would be persisted across sessions such that one client proxy could call an operation that would change the state and another proxy would see that state change in a different operation. Participants were writing code to bind the results of the operation to Avalon UI and the fact that state was not persisted across sessions caused them to have to rewrite a lot of their data binding code.
The contract for the service was as follows:
[ServiceContract(Namespace = "http://Usability.Task")]
void AddAlbum(string title);
void SellAlbum(string title);
You'll notice that the contract says nothing about whether or not state is persisted. I had been setting the InstanceMode property on the service implementation to maintain state but of course, this is part of the implementation, not the contract, and so consumers of the service have no visibility into the setting of this property. I had thought that it might be useful to communicate to users the setting of the InstanceMode property but Steve made it clear that this was not appropriate, since this really has nothing to do with the statefulness of the service. This property is provided as a way to allow service developers to deal with perf and scalability issues in their service implementation, not necessarily to provide service developers with a mechanism by which to preserve state between calls or between sessions.
So I asked how best to communicate the stateful nature of a contract? Steve offered two suggestions (hopefully Steve doesn't mind me quoting him below). The first involved adding Open and Close methods to the contract:
"I would add an Open() and Close() method to the contract. Open() would be the only method that could initiate a connection; Close would be the method that would always close a connection and spill the instance on the service. Open() would take a key that would identify the app session being dealt with. The service would be storing data in a database (or in a file – whatever works for the particular app)."
The second involves passing an id in each of the operations to identify the 'session' that the client is participating in:
In both suggestions, the fact that state is persisted in some way is made explicit by the design of the contract. In terms of usability, being explicit is almost always a good thing. It means that there is one less assumption that users have to make about how an API works and in the case of the usability study last week, would likely have prevented participants from rewriting all their data binding code after realizing that the initial assumptions they made about how the service operated were incorrect.
I think that one of the reasons that I hadn't designed the contract this way originally was because I was still thinking in an object oriented fashion instead of a service oriented fashion. I was still thinking in terms of objects that have state instead of services that are stateless. Looking back now it seems like such an obvious mistake to make but I expect that it's one that many people might make when they transition from building objects to building services. It strikes me that being explicit about maintaining state in the design of the service contract could be a best practice that might be codified in tools such as FxCop and others. Having a rule that fires whenever it appears that a service implementation is storing state and does not expose Open or Close operations or does not appear to take some id or token in each operation might be a useful way to guide developers towards best practices and help them think in terms of services. Or even better might be some tool that helps generate a contract for you based on certain requirements that the user feeds into the tool and thus would generate the appropriate operations for you.
I'm not sure how feasible it would be to write such an FxCop rule or build such a tool but the idea of packaging up best practice guidance such as this in tools is appealing. I'd be interested in hearing about other best practices that people follow when building services and to learn about ways in which people ensure that those best practices are followed.