Creating Client-Side API's
Published on
The strength of the Net isn't just what you put on it - it's also how you interact with the world around you.
According to the great oracle Wikipedia:
An application programming interface (API) specifies how some software components should interact with each other.
[…] In practice, many times an API comes in the form of a library that includes specifications for routines, data structures, object classes, and variables. In some other cases, notably for SOAP and REST services, an API comes as just a specification of remote calls exposed to the API consumers.
http://en.wikipedia.org/wiki/Api
There are two types of API's:
- Value Getting - an API that reaches out to other web service API's and reads and/or writes data into your local system (i.e. you're the "client" getting value from the data of others).
- Value Giving - an API hosted on your system accessing local data and providing other web users read and/or read data access via JSON, XML (or other techniques). In this scenario your API is the "server".
Just to be clear - we're covering client, "Value Getting" API's here.
Use a simple type & method names
This is good basic usability - the more convoluted you make your type and method the harder it will be to use. This principle stretches from the type name you use to it's data members, methods, and their parameters.
// Convoluted, hard to quickly type this:
define my_funnylooking_type123_isayahoowidget => { ... }
// Simple, easy and obvious
define yahoo_widget => type { ... }
The example above is extreme, but I've come across some pretty obscure type names before, so it's not unheard of!
The same policy should be applied to method names, and to signatures. Be clear and simple about names of methods and their signatures. Where practical use method overloading to help your users have shortcuts or more forgiving syntax.
Store as a thread objects
Another issue I come across frequently: the type or method reaches out to the remote web service and returns data, but the data changes maybe once a day and yet it's hitting the remote web service for that data on every single invocation. It's not so much an issue, as an inefficiency. A great recipe for getting rate-limited, let alone the fact that it introduces unwarranted latency to your code.
The better approach is to create a thread object which stores the data in memory. This means the data is fetched once, and only refetched when it is instructed to refresh.
A classic example of this is the Yahoo Word of the Day. By it's very definition this doesn't change often, but we don't want to have to add a scheduled event when it can be handled internally!
In my implementation of this client side API (hosted on GitHub: https://github.com/iamjono/yahoo_wordoftheday) I used a thread object.
define yahoo_wordoftheday => thread {
data
public date::date = date,
public word::string = string,
public definition::string = string,
public link::string = string,
private daterun::date = date
/* ====================================
Populate method reaches out and gets the data
==================================== */
public populate() => {
local(data =
xml_tree(
include_url(
'http://xml.education.yahoo.com/rss/wotd/'
)->asString
)
)
// assign data to thread object data members
.word = #data->channel->item->title->contents->split(' - ')->first
.definition = #data->channel->item->description->contents
.link = 'http://education.yahoo.com/reference/dictionary/entry/'+
.word
.date = date_gmttolocal(
date(
#data->channel->item->pubDate->contents,
-format='%a, %d %b %Y %H:%M:%S GMT'
)
)
.daterun = date // set for marker of when run
}
/* ====================================
The active_tick method fires on a pre-determined
interval to refresh content
Note: requires the "every" method, see GitHub checkin.
==================================== */
public active_tick() => every(60) => {
// trigger refresh of content
date->julianday != .daterun->julianday ? .populate()
// run at least once per day (every 86400 seconds)
return 86400
}
}
In the definition of the object itself you'll notice that it says "thread" not "type". One of the significant differences between a type and a thread is that a type can be assigned to an object, whereas a thread *is* an object. This means you don't have to instantiate it, by defining it you're already creating it as a "thing" you can then address.
Just like a type, a thread can have data members, methods.
Using active_tick to refresh
Note the addition of the active_tick method.
This is a special method in a thread object: the return is an integer which is the maximum number of seconds that method will "sleep" for before it will re-execute. The sole purpose of this method here is to trigger the populate method once a day to get fresh data. The "every" method is also invoked to make sure it's not run too frequently, and a date comparator is added to make sure populate is only hit once per day. Active_tick is executed after every thread access so these additional measures are necessary.
Note, for simple storage, another option would be to leverage Memcached, but that's beyond the scope of this article.
Data members, subtypes or "map"
This subject is contentious… It is very easy to reach out to a web service and set all incoming variables to a map, and then expect the user to address the response as a map using find, iterate, query expressions, etc… and handle the data themselves. This might be easy but I would argue that it's the lazy way. Your client side API then really is just a handler for getting the data - that's all.
IMHO the much nicer way is to help users address the returned data in an OOP friendly fashion.
There are three ways to do this, and which one you use will be determined by the nature of the data you're handling:
Specify the data members explicitly
The yahoo_wordoftheday type is a great example of a simple API that stores it's data in data members for simple and direct access.
Use subtypes to nest data
The yahoo_stockquote API is an example of an API that uses a nested type.
You will see in the demo that #myquotes->AAPL->price is returning something very specific - when AAPL is added to the object it stores the result with type "yahoo_stockquote_impl" - although the end user is never going to know that… all they know is that they get it by invoking (object name)->(organization symbol)->price
Use _unknowntag to fetch data from a stored response.
Generally when a user requests a data member or method that does not exist for the type, an error is generated, but a great way of handling exceptions to this is to implement an _unknowntag method.
In general terms, instead of immediately generating an error, if supplied Lasso will invoke the _unknowntag method which should contain logic determining what course of action to take.
A practical usage would be to look up the requested method as a key for a map and return the result, or to pass an API "get" request with it as the API method parameter.
You can see in the yahoo_stockquote API the _unknowntag returns the related object from the stored .symbols data via:
return .getsymbol(method_name->asString)
The Bitly API (https://github.com/iamjono/bitly/) uses _unknowntag to trigger the .send method using the method_name as the API request method.
return .send(method_name->asString, #params)
Document your code
I take great inspiration from reading others' code if it's commented. It means I don't have to spend the time reading through the code line-by-line and disappearing down the rabbit hole of their logic to learn from it.
And face it - reading others' code is one of the best ways of learning.
So, comment your code - describe what you're doing. You never know: one day you might look back at your code and find it hard to work out what you did (and why), and you'll wish you commented it!
I'll be honest here and confess this is something I'm not the best at personally, so if you find something in my code that needs more explanation - let me know and I can explain it… and will likely make an edit to the source and add in the explanation!
Publish on GitHub
So now you have an API, and you want the world to know about it. The best way to do that is get it out there on a code repository like GitHub, Bitbucket etc.
For public repos, my personal fave is GitHub due to the ease of use for myself and other users. For my private repos (stuff that I wish to be private like the various skunkworks projects I always have going) I use Bitbucket - simply because they have free private repos. If you're a commercial or otherwise monied organization and you don't have your own internal code repo infrastructure I'm sure GitHub's private repos are great - I just can't justify it for my own personal use :)
Demos
Always have demo use cases or examples in your repo.
- Always mask your own username, password and API key though. Never, ever commit with that in!
- Make them simple and easy to use.
- Don't try to be clever, or show how smart you are. Users will see that for themselves if they read your commented code in the API itself :)
Readme
The first thing people see when they get to your repo is the readme. Yeah I know it's below the files list, but it's the first thing people look at!
- Make it simple, clear to follow.
- Make sure you spell check.
- Get your attributions right - give credit where credit is due. If your work is a derivative of someone else, say so. If people helped you, give them credit.
- Contact info of some description is a good idea, but not strictly needed because most code repo infrastructures will provide comment or issues processes.
Let people know
Last, but not least - let people know you've done it. Tweet it, shout it out loud on FaceBook. Post it's existence on email lists like LassoTalk.
Example API's
- Bitly API: https://github.com/iamjono/bitly
- Yahoo Stock Quote API: https://github.com/iamjono/yahoo_stockquote
- Yahoo Word of the Day API: https://github.com/iamjono/yahoo_wordoftheday
- Memcached API: https://github.com/LassoSoft/Memcached
- Lasso 9 Mobile Reference: (Uses a Lasso 9 server side API for fetching data) https://github.com/LassoSoft/Lasso-9-Mobile-Reference