Aphorisms ofAPI Design – Step 1: – Define your terms



Aphorisms ofAPI Design – Step 1: – Define your terms

0 0


slides-aphorisms-api

Aphorisms of API Design

On Github Crell / slides-aphorisms-api

Aphorisms ofAPI Design

Presented by Larry Garfield

@Crell

  • Senior Architect, Palantir.net
  • Drupal 8 Web Services Lead
  • Drupal Representative, PHP-FIG
  • Advisor, Drupal Association
  • General busybody

Step 1:

Define your terms

API

Application Programming Interface

Code intended for other code

Aphorism

A concise statement containing a subjective truth or observation cleverly and pithily written.

Wikipedia

They're not rules

They're more like guidelines

Our Agenda

Clever and pithy sayings about good code.

Learn the rules like a proso you can break them like an artist.

— Pablo Picasso

#1

Asimov's Law

If there are multiple universes,then there are infinite universes

— The Gods Themselves

If there are multiple possible implementations,there are infinite possible implementations

So what?

Machine names > Constants

const STATUS_PUBLISHED = 1;
const STATUS_DRAFT = 0;

function load_articles_by_status($status) {
  // Find articles $ids for $status
  return article_load_multiple($ids);
}

Oops

const MYMODULE_STATUS_PENDING = 3;

const YOURMODULE_STATUS_IN_REVIEW = 3;
function load_articles_by_status($status = '') {
  // Find articles $ids for $status
  return article_load_multiple($ids);
}

But aren't booleans 2-state?

Boolean != either/or

Booleans don't have 2 values

Booleans are TRUE or not

Access is a boolean

Access control has N-possibilities

Garfield's Law

One is a special case of many.

http://groups.drupal.org/node/8001

node_load_multiple()

Drupal 6

// Fetch one node.
function node_load($nid) {
  $record = db_query("SELECT * FROM node WHERE nid=:nid", [':nid' => $nid])
    ->fetchObject();
  $record = load_extra_stuff($record);
  return $record;
}
// Fetch multiple nodes.
function node_load_multiple(array $nids) {
  $records = db_query("SELECT * from node WHERE nid IN (:nids)", [':nids' => $nids])
    ->fetchAll();
  foreach ($records as $record) {
    $loaded[] = load_extra_stuff($record);
  }
  return $loaded;
}

SELECT N + 1

(aka, melt your database)

Drupal 7

// Fetch one node.
function node_load($nid) {
  $nodes = node_load_multiple(array($nid));
  return reset($nodes);
}

// Fetch multiple nodes.
function node_load_multiple(array $nids) {
  $records = db_query("SELECT * from node WHERE nid IN (:nids)", [':nids' => $nids])
    ->fetchAll();
  $records = load_extra_stuff($records);
  return $records;
}

Aphorism #1

N is the only number

#2

Fail Fast, Fail Cheap, Be Lazy.

— Rasmus Lerdorf

Make the code debug for you

Don't plan for everything that could happen

Plan for how it will break

Drupal 6 theme system

function theme($hook) {
  // ...
  if (!isset($hooks[$hook])) {
    return;
  }
  // ...
  return $output;
}

Drupal 7 theme system

function theme($hook, $variables = array()) {
  // ...
  if (!isset($hooks[$hook])) {
    if (!isset($candidate)) {
      watchdog('Theme key "{$hook}" not found.');
    }
    return '';
  }
  // ...
  return $output;
}

General failure reading Drive C:

PHP Warning: Invalid argument supplied for foreach() in /includes/form.inc on line 2975

Code failure is like voting in Chicago

Fail early, fail often

Constrain inputs

Fail usefully

Good APIs are picky

class InsertQuery extends Query {
  public function fields(array $fields, array $values = []) {
    // ...
  }
  public function preExecute() {
    if (array_intersect($iFields, $defaultFields)) {
      throw new FieldsOverlapException('...');
    }
    // ...
    return TRUE;
  }
}
A good programmer is someone who always looks both ways before crossing a one-way street.

— Doug Linder

If you're not developing under E_ALL|E_STRICT,you're doing it wrong.

Aphorism #2

Fail fast, fail cheap, fail usefully

#3

You can't teach what you don't know.

You don't know what you can't teach.

You don't understand what you can't document.

I can't understand what you don't document.

abstract class FileTransferFTP extends FileTransfer {
   /**
    * Return an object which can implement the FTP protocol.
    *
    * @param string $jail
    * @param array $settings
    * @return FileTransferFTP
    *    The appropriate FileTransferFTP subclass based on available    
    *    options. If the FTP PHP extension is available, use it.
    */
   static function factory($jail, $settings) { }
}

Date.module

/**
 * Getter callback to return date values as datestamp in UTC from the field.
 */
function date_entity_metadata_field_getter($object, array $options,
                                           $name, $obj_type, &$context) { }

No, seriously, WTF?

Lack of comments indicate

  • Laziness
  • Indifference
  • Lack of comprehension
  • Embarrassment

What to document

  • Every function
  • Every method
  • Every class
  • Every object property
  • Every constant
  • Every parameter
  • Every return value

No exceptions (Well, document those, too)

Usage docs

A picture is worth 1000 words

A code sample is worth 1000 comments

You wish you were as cool as Gearman's docs

<aside>

Clearly written code with well-named methods is self-documenting.
protected function normalizeCharset($value) {
    switch (mb_detect_encoding($value)) {
    case 'ASCII':
      break;
    case 'UTF-8':
      $value = htmlentities($value, ENT_NOQUOTES);
      $value = html_entity_decode($value, ENT_NOQUOTES, 'UTF-8');
      break;
    case FALSE:
      $value = iconv('Windows-1252', 'UTF-8//TRANSLIT', $value);
      $this->log->warn("Detected Windows-1252 character encoding.");
      break;
    default:
      $this->log->warn("Unrecognized character encoding.");
  }
  return $value;
}

WTF is up with UTF-8? And why is FALSE Windows-1252?

protected function normalizeCharset($value) {
  // mb_detect_encoding() in most situations supports only two character sets,
  // ASCII and UTF-8.  However, it is sadly not unusual for incoming data
  // to be in Windows-1252, aka MS Word. We therefore guess that a
  // false/not-found character set is Windows-1251, and try to convert that
  // to UTF-8.
  // Note: The odds of this breaking on some other character encoding besides
  // those three is rather high.

  // ...
}
switch (mb_detect_encoding($value)) {
  // I have absolutely no idea why UTF-8 strings need to be converted
  // from UTF-8 to UTF-8, but if this code is removed many strings end up
  // failing with invalid multi-byte encoding.
  case 'UTF-8':
    // Convert any characters we possibly can to their HTML encoded entities.
    // If we don't specify a character encoding then this should do at least
    // a passingly decent job of detecting it, or at least doesn't care as much
    // as other APIs do.
    $value = htmlentities($value, ENT_NOQUOTES);
    // Convert those HTML entities back into real characters, but this time
    // insist on UTF-8.  This will at worst convert UTF-8 characters back
    // to UTF-8 and at best convert ISO-8859-1 characters to HTML entities and
    // from HTML entities to UTF-8 characters.
    $value = html_entity_decode($value, ENT_NOQUOTES, 'UTF-8');
    break;
  // ...

  // A False return from mb_detect_encoding() means that it couldn't
  // figure out what the encoding is.  In a standard configuration mb_* only
  // knows about ASCCI and UTF-8, so that's not especially useful. We will
  // make an assumption that anything else is Windows-1252, aka MS Word legacy
  // garbage. If correct, this will convert $value from Windows-1252 to
  // UTF-8, transliterating where necessary rather than just failing.
  case FALSE:
    $value = iconv('Windows-1252', 'UTF-8//TRANSLIT', $value);
    $this->log->warn("Detected Windows-1252 character encoding.");
    break;
}

</aside>

Aphorism #3

Docs or it didn't happen

#4

A UI is not an API

A User Interface is not an Application Programming Interface

A User is not a Program

A UI is a client of your API

What good is a website without a UI?

Who said you're building a web site?

PHPUnit

(Tests or it didn't happen)

A website is not an API

A website uses an API

An API does not need a web site

You're not done until you havethree implementations

3 implementations?

Unit test Web services call Command line Web site

Aphorism #4

A UI is not an API

#5

You know that saying about standing on the shoulders of giants? Drupal is standing on a huge pile of midgets.

— Jeff Eaton

Don't add to API bloat

Leverage existing patterns

  • Mimicry is the highest form of flattery
  • Mimicry is easier to remember
  • Mimicry takes less work

Existing patterns

  • Design patterns
  • Your platform's patterns
    • Drupal (Entities, hooks, etc.)
    • Symfony (Events, Bundles, YAML config files)

Go with the flow... it makes documentation easier

Aphorism #5

The best API is the API you didn't have to write

#6

Use uncertainty as a driver.

— Kevlin Henney, 97 Things Every Software Architect Should Know

Don't make decisions if you don't have to.

Make changing your mind cheap.

(Your client will change his mind.)

(Twice.)

You can only change what is encapsulated.

Logging

  • Database
  • Syslog
  • Display on screen
  • Pager / SMS message
  • Twitter

Don't decide for me!

Interfaces are your friend

interface LogInterface {
  public method log($message, $severity);
}

PSR-3

namespace Psr\Log;

interface LoggerInterface {
  public function log($level, $message, array $context = array());

  public function emergency($message, array $context = array());
  public function alert($message, array $context = array());
  public function critical($message, array $context = array());
  public function error($message, array $context = array());
  public function warning($message, array $context = array());
  public function notice($message, array $context = array());
  public function info($message, array $context = array());
  public function debug($message, array $context = array());
}

Caching

  • Database
  • APC
  • Memcache
  • Redis
  • Riak

Don't decide for me!

In Drupal

interface DrupalCacheInterface {
  function get($cid);
  function getMultiple(&$cids);
  function set($cid, $data, $expire = CACHE_PERMANENT);
  function clear($cid = NULL, $wildcard = FALSE);
  function isEmpty();
}

PSR-6 (Draft)

Stay tuned... ;-)

Encapsulation avoids decision making

Avoiding decision making requires loose coupling

Explicit interfaces

namespace Psr\Log;

interface LoggerInterface {
  public function log($level, $message, array $context = array());

  public function emergency($message, array $context = array());
  public function alert($message, array $context = array());
  public function critical($message, array $context = array());
  public function error($message, array $context = array());
  public function warning($message, array $context = array());
  public function notice($message, array $context = array());
  public function info($message, array $context = array());
  public function debug($message, array $context = array());
}

Dependency Injection

Not just for testing...

Also for day-before-launch-changes

Separate Logic from Data

Separation of concerns

  • Interface-driven development
  • Context-free (stateless) services
  • Single-Responsibility Principle
  • Dependency injection

Aphorism #6

Avoid making decisions

#7

Delegation adds indirection

Indirection requires abstraction

Abstraction solves hides complexity

Abstraction is not free

Performance

  • Language level
    • call_user_func_array() = 3 functions
    • __call() = 3 methods
  • Query builders in Drupal
    • Query builder is 30% slower than raw queries
    • (Drupal's query builder is really simple, too...)

What the heck is going on under the hood?

... 14 method calls later I still don't know.

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.

— C. A. R. Hoare

The unavoidable price of reliability is simplicity.

— C. A. R. Hoare

Aphorism #7

There is no problem that cannot be solved by addinganother layer of abstraction...

except abstraction

#8

DrupalYour software has idiosyncrasies, but it's not special.

No one understands Drupalisms, except Drupal developers

(And not even all of them)

No one understands Symfonyisms, except Symfony developers

(And not even all of them)

No one understands PHPisms, except PHP developers

(And not even all of them)

No one understands your system, except you

(And sometimes not even then)

No matter how cool you are,John Resig knows more Javascript than you do.

Some numbers

  • Age of 3rd gen languages: 56 years
  • Age of PHP: 18 years
  • Age of Drupal: 12 years
  • Age of your website: 6 months

Odds of you doing something original...?

Find existing wheels

Use existing platforms

Use existing standards

  • PHP-FIG Specifications
  • Learn your HTTP!
  • REST API: I can go home and rest and not design from scratch

Learn from existing wheels

  • PECL
  • jQuery
  • Wordpress(!)
  • Symfony2, Zend Framework, etc.
  • Java
  • Design patterns

Drupal 8

  • Symfony2 (HttpFoundation, HttpKernel, DependencyInjection, EventDispatcher, Routing, Serializer, Validator, Yaml)
  • Symfony CMF
  • Zend Feed
  • Doctrine Annotations
  • Guzzle
  • EasyRDF
  • Assetic
  • Twig

Feature no software needs

Ego

Durden's Law

Aphorism #8

You are not a special and unique snowflake

Grand Unified Theory of API design

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

— Martin Golding

You will always know where you live.

In six months, some one will need to do something you never thought of with your code and will not be able to edit it.

That will be you.

Plan accordingly.

Stalk me!

http://www.palantir.net/

http://www.garfieldtech.com/

@Crell

Tell me how I did

https://joind.in/9367

Thanks!

Further reading