3

I'm trying to write a REST API client for practice and I'm having trouble figuring out how lay out the project.

The approach I'm taking right now has Actions, DomainObjects, Requests, and a class that deals with authorization and headers and such (CoreClient) that exposes a generic DoPut<T>(string address,T item) and similar verbs.

Examples

Action: UpdateRecordCode (contains logic)

DomainObject: RecordCode (entity)

Request: UpdateRecordCodeRequest (entity, holds a record Id and a list of things to add to it)

Question 1: How do I determine which classes should implement interfaces - and what interfaces I should have in the first place - and which should inherit?

My best guess so far is that Actions should inherit from an ActionBase because each Action is basically a kind of ActionBase. DomainObjects should probably implement something so that they're consistent, but I'm not sure what.

The trickiest part is Requests; ideally, I'd like other parts of the application to handle Requests generally, such that I could, in the UI, have a list of Requests a user might choose. I've tried this both ways, but keep going back to a concrete class as both feel weird. Interfaces, for example, end up with having to say something like IRequest<UpdateRecordCode<UpdateRecordCodeRequest>>, which seems silly.

Question 2: Am I just doing this wrong? I feel like everybody's written a dozen API clients - I've written a number with minimal planning - but I'm trying to do this "right" this time around and it's, well, hard.

I know there are many duplicates of the "Interface or abstract class?" question, but I think this is distinct from those as it's about how they work together rather than simply "X or Y?"

1

The over-engineering architects will disagree with me, but in a language such as C#, interfaces only exist to allow multiple inheritance, and they do that poorly, having no implementation. That is, if you can find some common implementation code, it is better to use a base class instead of an interface, except when you need some form of multiple inheritance. Since C# only allows multiple inheritance in the form of interfaces, you are stuck with an interface.

In the case of your specific example, you have not explained why the design warrants interfaces or classes. What you call a "request" seems to be a combination of of an action (verbs) and parameters (nouns). This can be expressed in many ways, but the most logical for me would be a composite object containing the action and parameters. The action does not have to be specified using a class or an interface.

  • I agree that one request on its own doesn't seem like it should require an interface or inheritance. I'm trying to think of the right way to be able to have a variety of requests that could all be treated equivalently: CreateThing, ReadThing, UpdateThing, DeleteThing, UpdateSomeOtherThing, PerformRandomServiceAction, and etc. – William - Rem Mar 13 '17 at 23:02
  • 1
    "Treated equivalently" should mean there is some code in common between the request objects. To be implemented as a class, a request should have either common code or common data across all requests. – Frank Hileman Mar 16 '17 at 19:07
  • 1
    @William-Rem Forgot to mention, "common code" may be the caller; the code invoking properties or methods on the request class or interface. – Frank Hileman Mar 17 '17 at 23:32
4

As @Frank Hileman has said, interfaces in C# have no implementation, and you cannot inherit from more than one concrete/abstract class.

However, there is more to using interfaces here than inheritance.

Testing: Using interfaces in your application will allow you to 'mock' an object that is needed by another.

For instance, if you want to test a class that manipulates a person's name in different ways, you would not want to build a database with tables and data to store Persons (with every variation of test data required), then call through all of the code required to retrieve those Persons, then pass those Persons to the class you are testing one by one.

This would not only be testing the manipulation of the Person's data, but all of the code needed to connect with data (including authentication etc.)

Instead, you would want the class you are testing to accept an object that implements the interface for a Person. Now you can just create a new MyPerson: IPerson object of your own, populate it with all of the varying values you want to test, passing it in each time for testing.

Guaranteeing an object implements properties and/or functionality - regardless of what that object actually is:

Where you may want to pass an object that features certain functionality that you may want to act on, you don't want to have to type-check that object to get to the methods or properties you want (which you may have to do with concrete classes just inheriting from an abstract class).

Regardless of the underlying object, its features exposed via an interface are immediately obtainable and actionable.

Code re-use:

Using abstract classes for common functionality reduces code volume and scope for errors.

Your ActionBase may have common methods such as Create<T>, <T>Find(int), List<T>Get, Update<T>, and Delete<T>. You can implement them in a single abstract class using generics, and so remove all of that code from the Action class that would normally hold it all (Action: ActionBase<T>).

Regarding your entities, once again, an abstract class would hold the common properties in use. These may be the Identifier (if data type is common in all entities), a Status flag, DateCreated, DateModified, etc. if used in all entities.

  • I wish I could accept both yours and Frank's as the answer. "Using abstract classes for common functionality reduces code volume and scope for errors." is basically what I'd like to do and I think I was going about it the wrong way. – William - Rem Mar 28 '17 at 21:51
  • 1
    No problem, happy to help. Design is the 'art' of sofware development. There is no all encompassing correct way, but the more scenarios voiced, the easier it is to find what fits. – Steve Padmore Mar 28 '17 at 21:57

protected by gnat May 14 '18 at 6:07

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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