1

I'm having trouble deciding how to design this service API.

public class GetCurrentValuesRequest
{
    public int ReferenceID { get; set; }
    public int[] FilterIDs { get; set; }
}

public class GetDefaultValuesRequest
{
    public int[] FilterIDs { get; set; }
}

public class GetValuesAsOfDateRequest
{
    public int ReferenceID { get; set; }
    public int[] FilterIDs { get; set; }
    public DateTime AsOf { get; set; }
}

public class GetValuesAsOfChangeSetRequest
{
    public int ReferenceID { get; set; }
    public int[] FilterIDs { get; set; }
    public long ChangeSetIDs { get; set; }
}

public class GetProposedValuesRequest
{
    public int ReferenceID { get; set; }
    public int[] FilterIDs { get; set; }
    public long ApprovalKey { get; set; }
}

public class GetValuesIfModifiedRequest
{
    public int ReferenceID { get; set; }
    public int[] FilterIDs { get; set; }
    public DateTime Since { get; set; }
}

public class GetValuesResponse
{
    public string[] Results { get; set; }
}

public class GetValuesIfModifiedResponse
{
    public string[] Results { get; set; }
    public bool IsModified { get; set; }
}

public interface IService
{
    GetValuesResponse GetValues(GetCurrentValuesRequest request);

    GetValuesResponse GetValues(GetDefaultValuesRequest request);

    GetValuesResponse GetValues(GetValuesAsOfDateRequest request);

    GetValuesResponse GetValues(GetValuesAsOfChangeSetRequest request);

    GetValuesResponse GetValues(GetProposedValuesRequest request);

    GetValuesIfModifiedResponse GetValuesIfModified(GetValuesIfModifiedRequest request);
}

I've thought about changing it to have make the IfModified request / response subclasses of the simple GetValues request response and only including one GetValues call. The server would return a different response depending on the input request, but that requires user to call IService this:

var response = (GetValuesIfModifiedResponse)serviceClient.GetValues(new GetValuesIfModifiedRequest() { ... });

I've also thought about placing IsModified in the simple GetValuesResponse, and only populating it if a GetValuesIfModifiedRequest is passed into it. But that seems a bit strange to include it in a result from a method which does not actually do anything with it. Also, it might throw of a user if they see it and expect to be able to use it in their code. bool? IsModified is better, but I'm not entirely sold on it just yet.

Any suggestions for how to best design this API?

7
  • What's wrong with simply setting IsModified to false if the response is not modified? I don't see a good reason to provide two different responses. Commented Mar 19, 2013 at 21:01
  • Because IsModified == false may not be accurate. In fact the use case for this property is if (response.IsModified) { refreshCache(); } so if the service is not called correctly, the cache would never get refreshed.
    – p.s.w.g
    Commented Mar 19, 2013 at 21:04
  • What does "called correctly" mean? Does the service need to return a tri-state value instead of a boolean one? Commented Mar 19, 2013 at 21:10
  • If "AsOf" is requested, the server goes through a few extra checks before returning, and if IsModified == false then the Results array is empty. So IsModified provides extra information about the state of the system. I'd rather have IsModified == null indicate that no such checks were made.
    – p.s.w.g
    Commented Mar 19, 2013 at 21:15
  • OK. So the three states are Modified, Unmodified, NotChecked? Commented Mar 19, 2013 at 21:17

2 Answers 2

1

In my opinion, your sole request object should look something like this:

public class GetValuesRequest
{
    public int[] FilterIDs { get; set; }
    public DateTime SearchDate { get; set; }
    public SearchType SearchType { get; set; }
}

public enum SearchType
{
    AsOf,
    Since
}

You can tweak this to your taste, but the point is that there is only one search date submitted, and the enum provides a switch mechanism between past and future.

You can then return an object thusly:

public class GetValuesResponse
{
    public string[] Results { get; set; }
    public CacheState CacheResult { get; set; }
}

public enum CacheState
{
    Modified,
    Unmodified,
    NotChecked
}
5
  • Thanks for taking the time to reply, but I don't really like relying on users to set flags (AsOf) in order to use the request correctly. It also means I have to account for every combination of parameters a user might throw into the request (my actual class has many more methods of querying data, many of which are not compatible with each other).
    – p.s.w.g
    Commented Mar 19, 2013 at 21:11
  • The only thing I would change about your updated example is that the service should return only one type with a cache status. Returning two types complicates things for the caller. Commented Mar 19, 2013 at 22:23
  • Agreed. Which is why I like your CacheState enum (+1). But when I think of trying to express all the logic of every variety of request in a single request class, it makes me cringe. I think it's just asking for malformed or incomplete requests.
    – p.s.w.g
    Commented Mar 19, 2013 at 22:28
  • So don't. I don't mind that each request type is declared with it's own class. But return values are not polymorphic like parameters are, so return a single type instead of the two types that you're proposing, unless there's some compelling reason why the user must accept a different return type for "If Modified" requests. Commented Mar 19, 2013 at 22:32
  • That's what I'm going with. Thanks for helping me reason my way through it.
    – p.s.w.g
    Commented Mar 19, 2013 at 22:44
1

This looks like you're trying to create a dynamic query over http. Have you looked at OData? Since you're using WCF, there is built-in support as of 4.0 I believe for implementing OData called WCF Data Services, all you have to do is return IQueryable from your service methods. For convenience, you can create helper queries on your client library that wraps around the OData queries. So your service interface would now look like

public interface IService
{
    IQueryable<Values> GetValues();
}

And your client library would contain helper functions that call the service.

public class ServiceClient
{
   //Injected or initialized see docs under previous link on accessing a service
   private IService _service
   public IEnumerable<Values>GetFilteredValues(int[] filterIds)
   {
      //get the values matching passed in Ids.
      return from _service.GetValues().Where(v => filterIds.Contains(v.Id));
   }

   public IEnumerable<Values>GetFilteredValuesAsOf(int[] filterIds, DateTime asOf)
   {
      //get the values matching passed in Ids and created since the asOf date
      return from _service.GetValues().
             Where(v => filterIds.Contains(v.Id) && v.CreatedOn > asOf);
   }

}

The beauty of this is that the query happens server side. If it is backed by a Database, the database handles it.

3
  • The op mentioned there was some other logic that occurs, if it can be expressed using linq syntax then this is the right approach. Commented Mar 19, 2013 at 21:39
  • I've considered that, but at the moment that's too big of a change for my service to support.
    – p.s.w.g
    Commented Mar 19, 2013 at 22:03
  • Also the IQueryable cannot be evaluated by the database because it would involve features not supported by my ORM (EF 4, but I might be able to do it when I upgrade to EF 5)
    – p.s.w.g
    Commented Mar 19, 2013 at 22:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.