
Why I avoid SDKs in production - brandur
http://brandur.org/sdk
======
jasonkester
That's cool. You're not the guy we built it for.

We built that SDK so that Junior Dev Jimmy can make calls to our API and
actually finish integrating our service without giving up. That guy can't code
his way out of a paper sack, and certainly couldn't successfully send us an
HTTP request or parse the result that we sent back. We built him a client
library so that he'll stop sending us stupid emails asking how to call our
server-side API using jQuery or, not making this up as it's happened several
times from several customers, a form on an HTML page.

Anything we can do to get that guy up and running and done implementing our
API, we'll do. Because the guy who hired him is paying us money, and the last
thing we want to do is explain why he needs to fire his only developer and
find a new one.

~~~
duncans
> "... he'll stop sending us stupid emails asking how to call our server-side
> API using jQuery or, not making this up as it's happened several times from
> several customers, a form on an HTML page."

Have you considered that maybe an API _should_ be callable using
`jQuery.ajax(...)` or a simple `application/x-www-form-urlencoded` POST?

(...or was this satire?)

~~~
jasonkester
I'll assume you missed the "server-side" in the above, or that I wasn't clear
in what that meant.

I'm referring to API calls that you really really only want to ever do from
your server. Things like starting and stopping EC2 instances, where you need
to send along your API keys that are essentially the keys to your bank account
and your business's future survival.

Now imagine, as the owner of that service, that your customer is sending those
keys in plain text to the client, and wondering why he's having a hard time
starting EC2 instances using javascript from his user's browser.

~~~
nl
I wrote a thing to start & stop OpenStack servers via JavaScript. HTTPS is a
thing.

I have trouble thinking of any API that shouldn't be callable from a client.

~~~
kodablah
Any API that uses some form of authentication token that is not obtained via
some user-based interaction like OAuth may need to be at least proxied via a
server side piece to prevent exposing credentials/keys.

~~~
nl
Err.. why?

Clearly the keys need to be obtained somehow, but that was out of band for my
application. Once they are obtained I don't see any reason why passing them
from a 3rd party app over HTTPS (using CORS) is any less secure than using
session based authentication with a 1st party app.

In both cases you are vulnerable to MTM and XSS attacks. Sessions expire which
is nice, but that's the only real difference.

------
justinsb
Even if I don't use the SDK, I greatly appreciate when the same company that
writes the API also builds an SDK.

APIs which don't also maintain SDKs - in my experience - don't understand the
cost of making breaking changes, and aren't as 'nice' to consume. When the SDK
is built, these issues are discovered: I appreciate that it's not me that has
to go through those pains.

~~~
arethuza
It's also good to have a test client application that the service provider
supplied to be able to demonstrate that problems are with the service rather
than with the client code.

~~~
dclara
Agree.

------
sciolizer
Here's a suggestion: design the SDK such that calling a method returns you
everything you need to construct the REST call yourself: the verb, the url,
the query params, the form params, and perhaps some headers.

    
    
        case class HttpCall[OutputType](
            verb: String,
            url: String,
            queryParams: List[(String,String)],
            formParams: List[(String,String)],
            resultType: Class[OutputType])
    
        trait Api {
          def getMyTasks(): HttpCall[List[Task]]
          def getTask(taskId: String): HttpCall[Task]
        }
    

The SDK can also provide a driver for making the http calls:

    
    
        trait Driver {
          def call[Out](httpCall: HttpCall[Out]): Out
        }
    

So now api invocations look like this:

    
    
        val myTasks: List[Task] = driver.call(api.getMyTasks())
        val aTask: Task = driver.call(api.getTask("foo"))
    

What's nice about this design is that the consumer can write their own driver
if they don't like yours. Prefer asynchronous over synchronous? No problem!

    
    
        trait Driver {
          def call[Out](httpCall: HttpCall[Out]): Future[Out]
        }
    

Everything in the article except for grep-ability can be achieved with this
kind of design.

~~~
brandur
Great idea!

I can definitely see this as a workable solution. The only thought I have is
that once in a while behavior like retry/idempotency support might have to be
tweaked on a per-endpoint basis rather than from the general `call` method,
and at that point things get a little less pretty.

In general I'd say that the ideas expressed in the article apply more to
dynamic languages where you see much more of the convention of SDKs just
giving you back hashes instead of strongly-typed models representing foreign
resources. In something like Scala, the barrier of having to implement every
model yourself might be an extra motivator for just defaulting to the SDK.

------
angersock
You know what I don't want in production?

Dual-column layouts that still require me to scroll to the bottom of the page,
and then back up, and then back down.

~~~
brandur
Hah! Call it a design experiment, and either way it's good to gather a data
point that someone hates it.

The multi-column layout will only trigger at large screen sizes. The idea here
was to fill some of that extraneous white space you get when browsing full
screen on big monitors. I may revert it.

~~~
steamer25
Interesting. Before reverting, maybe you could try detecting page height and
then flow from left column to right column with one page height then draw a
horizontal rule under than and repeat left to right again with the next page-
height's worth of text. I.e., instead of

    
    
      1 | 4
      2 | 5
      3 | 6
    

try

    
    
      1 | 2
      -----
      3 | 4
      -----
      5 | 6
    

...kinda like reading both sides of a book spine.

~~~
brandur
Nice idea!

I naturally worry a bit that the reader's eye will have a hard time tracking
which segment it should be looking at next (say they accidentally jump from 1
--> 3 for example). But like you said, if you used the horizontal rule between
say 1/2 and 3/4, and just had whitespace between 1/2, it might be distinctive
enough for a fluid reading.

Thanks for the tip. I'll draw up a prototype.

------
zdne
Brandur, this is indeed very actual topic (and will be even more with the rise
of APIs).

As with everything, the issue is twofold. In the case of Heroku and your user-
base it is reasonable to expect your users are closer-to-wire than an iOS guy
hacking in Cocoa Touch.

From your point I can't agree more. A well designed HTTP APIs goes a long way
here. Putting a Cocoa Touch developer's hat on and I can't be bothered
learning HTTP, checking all the possible responses, handling redirects and
errors when the only thing I need is to just fire a single Mixpanel track
event...

Sort of C/C++ vs. let's say a Ruby. Assembler anyone? Stay on the metal or
develop (and crash:) faster with some trade-offs.

Anyways, good stuff. Important bringing it on the table!

~~~
brandur
Z,

> As with everything, the issue is twofold. In the case of Heroku and your
> user-base it is reasonable to expect your users are closer-to-wire than an
> iOS guy hacking in Cocoa Touch.

Certainly! Regarding the overall question of "SDK vs. custom HTTP wrapper"
there isn't a right answer; different parties have different requirements and
should use the right option for them. I'm not even saying that users of Heroku
shouldn't have an SDK here -- they should absolutely have one if it will help
them, and Heroku ships SDKs for just that reason. I was trying to make the
point that me, as a maintainer of a Heroku backend service that talks to other
services, would like the option of not having an SDK; like you said, I want to
be as close to the wire as possible.

Thanks for the feedback and reading!

------
D9u
I agree with the article. The last thing I want to do is add another layer of
complexity to an already complex application.

More layers of software equals increased chances for vulnerabilities and/or
bugs.

------
omgitstom
SDKs do have their value. I honestly think if the SDK is designed correctly
and is open source, a lot of these points are mute.

On top of that, the SDK can handle some interesting dev tasks that everyone
has to deal with regarding a REST API. For example, best practices with api
keys / secrets, error handling, caching, etc...

SDKs like any piece of software needs to be evaluated before integration, if
it is an SDK that doesn't meet your requirements for development, you build
your own against the REST API.

~~~
brandur
> SDKs do have their value.

I agree! And a well designed API can certainly help workaround many of these
problems.

I would say that fully evaluating an SDK is a non-trivial undertaking though,
especially when it comes to features like connection persistence. And then
what if it turns out that the SDK doesn't support everything you want (which
is often the case)? On the flip side - if a service has a good API, I can
build my own wrapper which uses my own conventions very quickly. And that
leads to what the main point of the article was meant to be: please design a
good API so that I can opt out of your SDK if I want to.

BTW, I think you were looking for "moot".

------
macspoofing
Including SDKs in production, as the author alluded to, is completely
situational. It comes down to answering the question: can you write a
significantly higher quality implementation to make it be worth your while?
When it comes to starting out with a web service, a ruby gem or maven library,
is invaluable. I love being able to work with an API within 5 minutes of
setting up my dev environment. As other's also mentioned, it does cut down on
support calls for the provider.

------
zimbatm
Another thing that client libraries often forget: configurable timeouts.
Without them the default is around 60-120 seconds depending on the system.

------
codeflo
> The Grep Test

I fail to see the point of this criterion. Even when you implement the client
code yourself, you'd surely not copy&paste identical HTTP calls everywhere
just so you can search for them. You'd use some form of encapsulation
(modules, classes, subroutines). And those calls are just as hard (or just as
easy) to grep for as calls to an external library.

~~~
brandur
Yeah, I included this last as I think it's by far the weakest argument.

I've found in the past, especially when looking being introduced to new
codebases that I'll see URLs around in the logs but that the logging is weak
enough that it's somewhat difficult to track down which module is initiating
them. With thin HTTP wrappers a quick Grep will expose exactly where the
offending code segment is, and I find that occasionally useful.

------
seivan
We built a clients SDK on top of a regular HTTP library (as any other do).
However if you want, you can get access to the parts of the library that
exposes headers, http methods and other thing that effectively allows you to
do work with the rest API directly if you want to.

~~~
brandur
Right. It's certainly possible, and it's awesome that many services like your
own provide good clients that empower developers this way.

My experience though is that some don't, and figuring out whether they do
support these more advanced features, and how to use them if they are
included, often makes the discovery process longer than just implementing a
simple HTTP wrapper. That of course assumes a reasonable well designed API
with pretty good docs though.

