patterncsharpModerate
Fluent LinkedIn REST API client interface design
Viewed 0 times
restdesignapiinterfaceclientfluentlinkedin
Problem
There is a handful of LinkedIn clients written in C# on the NuGet package library, but as far as I can tell, most only do authentication via LinkedIn. I found a couple which offer a LinkedIn REST API interface. Of those two, one looks like it has gone inactive (the nuget pack even warns that it is elementary). The other one, by Spring, has a very wide
I've already written a couple of API clients (all together in one library), but this is my first one with OAuth. There are many more plugin points for this, and so my client's API is a little more complex. I am using
Here are my goals:
Let's start with the oauth consumer key and secret. It seems the most logical place for this is in the
```
namespace LinkedN
{
// wraps th
IProfileOperations interface. I wondered what a more fluent interface would look like, so I tried my hand at rolling one over the weekend.I've already written a couple of API clients (all together in one library), but this is my first one with OAuth. There are many more plugin points for this, and so my client's API is a little more complex. I am using
DotNetOpenAuth in the background, but trying to hide that as an implementation detail (as much as possible anyway).Here are my goals:
- A default implementation that "just works" out of the box.
- Ability to customize the default implementation either through an IoC container or by overriding classes and implementing interfaces.
- The above should be accomplished using a reasonable level of granularity for swapping out different aspects of the implementation. Meaning, allow customization with the least number of interfaces.
- A "fluent" API to consume LinkedIn data.
- Only 1 or 2 classes / interfaces to work with in client code. By that I mean the starting interface, where you new something up and use IntelliSense to discover your options.
Let's start with the oauth consumer key and secret. It seems the most logical place for this is in the
*.config file, but someone might want to get it from a database to make it easier to set. If you have different app names under a single config, you could also implement this as a factory that retrieves the appropriate consumer key & secret for the app name you want LinkedIn to display to the user. I based the property names on the actual fields from the LinkedIn developer form:```
namespace LinkedN
{
// wraps th
Solution
As @Jeff pointed out, this code is ...beautiful. Well done!
I like the fluent interface a lot, but the code itself is at first glance... wow.
-
Naming
-
Comments
-
If there's an opportunity in your code, it has be about comments. Instead of this:
You could have a `
Into that:
Your code looks as SOLID as can be. Being a fan of Dependency Injection, I like that you've implemented a class for use with an IoC container. You've done a ...solid job. Seriously. I want to be maintaining code written like that!
I like the fluent interface a lot, but the code itself is at first glance... wow.
-
Naming
- All identifiers follow conventional casing (
camelCasefor locals and parameters,PascalCasefor types and their members).
- I like that you're using an
_underscoreprefix for field names, but that's just my opinion - you're perfectly consistent about it, and that is an objective fact.
- Every single name is meaningful.
- Method names start with a verb, the presented interfaces are highly cohesive, focused.
-
Comments
-
If there's an opportunity in your code, it has be about comments. Instead of this:
// wraps the oauth consumer key and secret
public interface IAuthenticateLinkedInAppYou could have a `
XML comment and IntelliSense would pick it up, and people writing the client code would certainly appreciate - it's the difference between this:
And that:
If you write your XML comments the same way you crafted your code, having XML documentation on all public members would be the icing on the cake. And if you really flesh it up, you can have the build process generate an XML file for you, with all the documentation in your project - then you can use 3rd-party tools to generate a whole website around that documentation, MSDN-style if you like.
-
Exceptions
-
I like that the ThrowExceptionWhenSettingIdentificationTwice extension is throwing an InvalidOperationException, it's a perfectly appropriate exception to be thrown in these circumstances. Again, XML comments would be a nice addition:
///
/// Does something foo.
///
/// Any foo.
///
/// Thrown when "bar" is specified for foo.
///
public void DoSomethingFoo(string foo)
{
if (foo == "bar")
{
throw new InvalidOperationException("Invalid foo.");
}
}
-
Throwing your own custom OptionAlreadySetException` would allow you to turn this:var option = endpoint.GetOption(PersonProfileRequestOption.Identification);
if (!string.IsNullOrWhiteSpace(option))
throw new InvalidOperationException(string.Format(
"The person profile endpoint has already been configured to " +
"identify resources for '{0}'.", option));Into that:
var option = endpoint.GetOption(PersonProfileRequestOption.Identification);
if (!string.IsNullOrWhiteSpace(option))
throw new OptionAlreadySetException(option);Your code looks as SOLID as can be. Being a fan of Dependency Injection, I like that you've implemented a class for use with an IoC container. You've done a ...solid job. Seriously. I want to be maintaining code written like that!
Code Snippets
// wraps the oauth consumer key and secret
public interface IAuthenticateLinkedInApp/// <summary>
/// Does something foo.
/// </summary>
/// <param name="foo">Any foo.</param>
/// <exception cref="InvalidOperationException">
/// Thrown when "bar" is specified for <c>foo</c>.
/// </exception>
public void DoSomethingFoo(string foo)
{
if (foo == "bar")
{
throw new InvalidOperationException("Invalid foo.");
}
}var option = endpoint.GetOption(PersonProfileRequestOption.Identification);
if (!string.IsNullOrWhiteSpace(option))
throw new InvalidOperationException(string.Format(
"The person profile endpoint has already been configured to " +
"identify resources for '{0}'.", option));var option = endpoint.GetOption(PersonProfileRequestOption.Identification);
if (!string.IsNullOrWhiteSpace(option))
throw new OptionAlreadySetException(option);Context
StackExchange Code Review Q#13458, answer score: 15
Revisions (0)
No revisions yet.