DrupalConDC Building APIs that Rock notes
[notes with ALL the personality (even Earl Mile's puppet, George) taken out]
Drupal now is a million lego pieces.
Your module is caught in a tug of war. [People wanting to add more features to it.]
99% of what we build is geared toward people clicking on things and getting it configured.
Imagine modules as a user who wants to go set stuff up, but it has no hands. Is there any way for that user, call him code, still use your module? Can someone building an installation profile have it configure your module automatically? Can they ignore your user interface and automate some part of it?
Views, for instance, explicitly built as an API and a UI.
Organic groups provides lots of functionality, but it also has an API under the surface. Your module can automatically add people to a group, can ask who belongs to a group--- other code can act as a friendly layer on top of Organic Groups.
Flag module another example.
If you have nothing but input forms, other code has to do the clunky, bad, fragile method of pretending to be a user that submits the form.
Your module stack, with features/ponies at the bottom:
Your UI | Code! (from other people)
Your API
Features! Ponies!
API can be an abstraction that hides complexity.
Messaging module example: "I'll handle knowing how to send out messages in a million ways (chosen by the user)" and in your module all you have to do is "send this user a message"
cache_set and cache_get examples in Drupal. A lot of complexity underneath (there can be many different ways of doing the cache, memcache or just database, that your module doesn't have to know about when using
APIs also provide an agreement about how things are done anywhere you go.
- Drupal is complicated
- APIs let other code use your features
- APIs hide complexity
- APIs get everyone on the same page
Token module gets everyone on the same page.
How APIs come to be
When two modules love each other very much...
The story of VotingAPI
Eaton working on one of his first larger Drupal sites for friends, a number of cool voting modules but none of them worked with each other. So I took the one with the nicest flash widget and shamelessly ripped out its guts.
This was votingapi:
votingapi_set_vote
votingapi_get_vote
Then asking:
Can you make my module a dependency for no good reason?
Find the nouns ("vote") there's your data!
Find the verbs ("set" and "get") there's your functions!
Does this sound a little like making
An object in an object-oriented language is a little API.
Step 2: Evolution!
At this point I had convinced one person to use Voting API.
Three knew verbs:
votingapi_add_vote
votingapi_change_vote
votingapi_delete_vote
Keeping the simple set_vote, but adding more complexity and control for those who want it.
Building in layers is important. Discrete helper functions
The vote object got a tag so that it could be a vote about other things.
Step 3: PLanning Ahead.
Letting other modules do the unanticipated.
Sadly, as people
supporting different kinds of votes-- vote in percentages or points, key for what the value results
result of a discussion with 3 or 4 module developers who all wanted to vote on stuff. Didn't write all this; it's the result of
Noticing of slow results for recalculating vote results, seeing your own votes, sorting by votes.
More functions -- recalculate_results and get_voting_results
Results is a new noun here. We made a separate table to store results in (calculated from votes
And verbs,
gotten
recalculated
George: there's a stray word between your verb and noun ("voting")
Um, naming conventions are important to uh... make sense. [ -- not sure what the answer was]
We always get it wrong the first time.
Second.
And third.
See where I went wrong, with the define functions for vote type?
What happens when someone wants to add another one? They define '4'.
And then someone also adds something else adds something... another '4'.
Don't use numeric
Only use enums for lists that won't change.
Use strings for stuff you can't predict.
I finally converted it from a number to a string, and personally wrote all the people using it.
And yet people always add to that anyway.
If you put an API out there someone will figure out how to abuse it in some
Remember the verbs?
What if another module wants to do
votingapi_add_vote
function mymodule_votingapi_vote
If the verbs you are exposing in your API
When your verb happens, call
module_invoke_all
(in votingapi_add_vote)
Well-placed hook invocations allow other people to do the hard work for you.
The more your module is built like that the more you can say "solving that isn't my problem; I just maintain the API" (his presentation of this, straightening his tie, was priceless)
module_invoke_all is party invitations for code
Remember the nouns?
Lots of people started saying they want the median or the mean
Rather than trying to anticipate every bizarro math
just say: I'm about to calculate the results
drupal_alter
drupal alter basically says: I have a piece of information, anyone who wants to
Give everyone a chance to change it.
drupal_alter('votingapi_results', $results, $type, $id);
function mymodule_votingapi_results_alter(&$results, ...) {
}
See that ampersand? It's important. Every single module that is handed the results object can change it. Each one in line sees the results that the other one did.
In the case of voting API, it just takes any new types of results that were calculated
drupal_alter()
It's like passing around the doobie of data
Everyone gets a chance. You don't have to.
Is your name Adrian? Perhaps Earl?
More advanced APIs like form or views tended to combine a lot of these together.
<?php
myapi_do_something() {
$stuff = module_invoke_all('myapi_stuff')
drupal_alter('myapi_stuff', $stuff);
$results = myapi_process_stuff($stuff);
module_invoke_all('myapi_stuffed', $stuff, $results);
return $results;
?>
here module_invoke_all isn't just an announcement, an invitation-- it's an announcement with RSVPs.
This is what menu API does: "I'm about to build menus, does anyone have menus I should know about?
Also hook_theme.
Then drupal_alter comes along and lets modules modify this after all the data has been gathered [my wording, it took me a minute to get this concept]
There's a hook_menu_alter
There's a theme registry function that lets this be messed after the fact.
Below-- this allows us to build something that's flexible without putting an insane number
This is referred to as the template pattern.
function myapi_process_stuff($stuff) {
foreach ($stuff as $module -> $thing) {
// [let the module that knows about the data mess with it]
}
Custom data structures:
Build it, let modules alter it, then do stuff with it
Processing an e-commerce checkout
letting other modules change what your module actually does.
If you build it they will hook.
- Identify verbs and nouns
- Layer your vebs to hide complexity
- Account for new uses
- Broadcast events via hooks
- Let modules alter data
Seven sins of API design:
Lone ranger module: reinventing the wheel, ignoring conventions, ignoring related APIs, ignoring the ways it's already being done.
Talk with other people who are doing work in this area, as early in the process as possible.
Invisible assumptions:
- assuming the global $user rather than letting people pass an object in.
- checking arg(0) which always breaks if you are not in the user interface.
- assuming that there is a user logged in.
Common when
Helping them to death:
- I'll set the breadcrumb while I'm at it!
- And I'll send an email too!
- And and these links and...
Organic groups is great but it does this a lot.
Common when grows out of specific use cases.
Check your issue queue-- people will complain about this!
Leaky Abstraction
"Just use this function..."
"...and learn everything underneath it to actually use it."
Very common in really complex, tough problem domains.
Simplify, or get out of the way (sometimes, problems are just really complicated).
Fake flexibility
You can implement the hook for blogapi, but then it dubps the data you add.
George: You put that hook in.
Learn from my mistakes, don't repeat them!
Build three test implementations of your API. Just see if a few different things will work.
Mission Creep
Even worse than feature creep.
Common with "solve-a-big-problem"
... like Views, which actually hands this pretty well.
Dependency Soup
The flip-side of mission creep
Wherever possible, make these dependencies optional -- check if function exists, and degrade gracefully if it doesn't.
Focus, focus, focus! Does my module really need to salve this.
And... pray for a package manager.
Don't try to solve that in your API module though.
Package manager is a hard problem, but Drupal needs to solve it.
- Cooperate
- Don't assume too much.
- Don't be pushy. (Make your UI optional!)
- Simplify or step aside (if you can't simplify)
- Test your extensions
- Stay focused - do one thing really well. Drupal is moving towards things that work together, the key is making them work together really well.
Comments
Post new comment