Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Api Design

2,136 views

Published on

Too few projects demand good API design as a critical goal. A clean and
extensible API will pay for itself many times over in fostering a community of
plugins. We certainly cannot anticipate the ways in which our users will bend
our modules, but designing an extensible system alleviates the pain. There are
many lessons to be learned from Moose, HTTP::Engine and IM::Engine,
Dist::Zilla, KiokuDB, Fey, and TAEB.

The most important lesson is to decouple the core functionality from the
"fluff" such as sugar and middleware. This forces you to have a solid API that
ordinary users can extend. This also lets users write their own sugar and
middleware. In a tightly-coupled system, there is little hope for
extensibility.

In this talk, you will learn how to make very productive use of Moose's roles
to form the foundation of a pluggable system. Roles provide excellent means of
code reuse and safe composition. I will also demonstrate how to use
Sub::Exporter to construct a more useful and flexible sugar layer.

Published in: Technology, Business
  • Be the first to comment

Api Design

  1. 1. API Design Shawn M Moore Best Practical Solutions http://sartak.org
  2. 2. Google API
  3. 3. CC-BY-SA Yuval Kogman, 2006
  4. 4. CC-BY-SA Yuval Kogman, 2006
  5. 5. CC-BY-SA Hans Dieter Pearcey, 2009
  6. 6. Moose Path::Dispatcher HTTP::Engine Dist::Zilla IM::Engine API
  7. 7. CC-BY-SA-NC Will Spaetzel, 2005
  8. 8. THE SECRET
  9. 9. THE WRITE TESTS
  10. 10. WRITE TESTS WRITEWRITE TESTSTESTS WRITEWRITE TESTSTESTS
  11. 11. DO THIS WRITE WRITE RITE WRITE WRITE W WRITE WRITE WRI WRITE WRI TE WRITE TE WRITE WRIT E WR ITE
  12. 12. Test! use base 'Class::Accessor::Fast'; __PACKAGE__->mk_ro_accessors('birthday'); use Moose; has birthday => (is => 'ro'); API
  13. 13. Test!
  14. 14. Test!
  15. 15. Test!
  16. 16. Test!
  17. 17. Moose package Point; use Moose; has x => ( is => 'ro', isa => 'Num', ); Moose
  18. 18. Metaobject Protocol Class::MOP Moose Class::MOP
  19. 19. Metaobject Protocol has cache => ( is => 'ro', );
  20. 20. Metaobject Protocol has cache => ( is => 'ro', ); Moose::Meta::Attribute has Moose::Meta::Attribute
  21. 21. Metaobject Protocol has cache => ( is => 'ro', ); Moose::Meta::Method::Accessor cache
  22. 22. Metaobject Protocol class PersistentAttr extends Moose::Meta::Attribute { … } has cache => ( metaclass => 'PersistentAttr', is => 'ro', ); Moose
  23. 23. Metaobject Protocol role PersistentAttr { … } has cache => ( traits => ['PersistentAttr'], is => 'ro', );
  24. 24. Moose X MooseX MOP
  25. 25. Sugar Layer Moose
  26. 26. Sugar Layer my $class = get_class();
  27. 27. Sugar Layer my $class = get_class(); $class->has( birthday => ( is => 'ro', ) ); has
  28. 28. Sugar Layer my $class = get_class(); no strict 'refs'; *{$class.'::has'}->( birthday => ( is => 'ro', ) ); has
  29. 29. Sugar Layer my $class = get_class(); no strict 'refs'; *{$class.'::has'}->( birthday => ( is => 'ro', ) );
  30. 30. Sugar Layer Class::MOP::Class ->initialize($class) ->add_attribute( $name, %options); has add_attribute
  31. 31. Sugar Layer my $class = get_class(); $class->meta->add_attribute( birthday => ( is => 'ro', ) );
  32. 32. Sugar Layer use MooseX::Declare; class Point3D extends Point { has z => (…); after clear { $self->z(0); } }
  33. 33. Path::Dispatcher use Path::Dispatcher::Declarative -base; on ['wield', qr/^w+$/] => sub { wield_weapon($2); } under display => sub { on inventory => sub { show_inventory }; on score => sub { show_score }; }; YourDispatcher->run('display score'); Jifty::Dispatcher Prophet
  34. 34. Path::Dispatcher::Declar use Sub::Exporter -setup => { exports => [ on => &build_on, under => &build_under, …, ], }; Sub::Exporter
  35. 35. Path::Dispatcher::Declar use Sub::Exporter -setup => { exports => [ on => &build_on, under => &build_under, …, ], };
  36. 36. Path::Dispatcher::Builder Robert Krimen "grink" Robert Krimen
  37. 37. Path::Dispatcher::Builder return { on => sub { $builder->on(@_) }, under => sub { $builder->under(@_) }, …, };
  38. 38. grink++ OO
  39. 39. HTTP::Engine HTTP::Engine->new( interface => { module => 'ServerSimple', args => { … }, request_handler => sub { … }, }, )->run; HTTP::Engine
  40. 40. HTTP::Engine HTTP::Engine->new( interface => { module => 'ModPerl', args => { … }, request_handler => sub { … }, }, )->run; mod_perl
  41. 41. HTTP::Engine HTTP::Engine->new( interface => { module => 'FCGI', args => { … }, request_handler => sub { … }, }, )->run; FastCGI
  42. 42. HTTP::Engine request_handler => sub { my $request = shift; return $response; } HTTP::Engine IO
  43. 43. HTTP::Engine request_handler => sub { my $request = shift; return $response; } HTTP::Engine OK
  44. 44. HTTP::Engine request_handler => sub { my $request = shift; return $response; }
  45. 45. HTTP::Engine++ 1
  46. 46. CC-BY-SA Hans Dieter Pearcey, 2009
  47. 47. Dist::Zilla AllFiles ExtraTests InstallDirs License MakeMaker Manifest ManifestSkip MetaYAML PkgVersion PodTests PodVersion PruneCruft Readme UploadToCPAN
  48. 48. Dist::Zilla AllFiles ExtraTests InstallDirs License $_->gather_files MakeMaker for Manifest $self->plugins_with( ManifestSkip -FileGatherer MetaYAML PkgVersion ); PodTests PodVersion PruneCruft Readme UploadToCPAN Dist::Zilla
  49. 49. Dist::Zilla AllFiles ExtraTests InstallDirs License $_->gather_files MakeMaker for Manifest $self->plugins_with( ManifestSkip -FileGatherer MetaYAML PkgVersion ); PodTests PodVersion PruneCruft Readme UploadToCPAN plugins_with
  50. 50. Dist::Zilla AllFiles ExtraTests InstallDirs License $_->gather_files MakeMaker for Manifest $self->plugins_with( ManifestSkip -FileGatherer MetaYAML PkgVersion ); PodTests PodVersion PruneCruft Readme UploadToCPAN
  51. 51. Dist::Zilla AllFiles ExtraTests InstallDirs License $_->gather_files MakeMaker for Manifest $self->plugins_with( ManifestSkip -FileGatherer MetaYAML PkgVersion ); PodTests PodVersion PruneCruft Readme UploadToCPAN gather_files
  52. 52. Dist::Zilla AllFiles ExtraTests InstallDirs License $_->munge_files MakeMaker for Manifest $self->plugins_with( ManifestSkip -FileMunger MetaYAML PkgVersion ); PodTests PodVersion PruneCruft Readme UploadToCPAN Dist::Zilla
  53. 53. Request Tracker User/Prefs.html $m->callback( CallbackName => 'FormEnd', UserObj => $UserObj, …, ); RT
  54. 54. Request Tracker User/Prefs.html $m->callback( CallbackName => 'FormEnd', UserObj => $UserObj, …, );
  55. 55. Request Tracker User/Prefs.html $m->callback( CallbackName => 'FormEnd', UserObj => $UserObj, …, ); FormEnd
  56. 56. Request Tracker User/Prefs.html $m->callback( CallbackName => 'FormEnd', UserObj => $UserObj, …, );
  57. 57. Request Tracker User/Prefs.html $m->callback( CallbackName => 'FormEnd', UserObj => $UserObj, …, );
  58. 58. Request Tracker commit 4c05a6835eef112701ac58dfd1b133e220059d4f Author: Jesse Vincent <jesse@bestpractical.com> Date: Fri Dec 27 18:50:06 2002 -0500 Attempting mason callouts Ticket/Update.html <& /Elements/Callback, Name => 'BeforeTextarea', %ARGS &> 7
  59. 59. Dist::Zilla Choice ModuleBuild or MakeMaker MetaYAML or MetaJSON
  60. 60. Dist::Zilla Extensibility Dist::Zilla::Plugin::CriticTests Dist::Zilla::Plugin::Repository Dist::Zilla::Plugin::PerlTidy
  61. 61. Dist::Zilla Extensibility Dist::Zilla::Plugin::CriticTests InlineFiles Dist::Zilla::Plugin::Repository MetaProvider Dist::Zilla::Plugin::PerlTidy FileMunger
  62. 62. Dist::Zilla Extensibility Dist::Zilla::Plugin::BPS::Secret
  63. 63. CC-BY-SA Hans Dieter Pearcey, 2009
  64. 64. IM::Engine incoming_callback => sub { my $incoming = shift; my $message = $incoming->plaintext; $message =~ tr[a-zA-Z][n-za-mN-ZA-M]; return $incoming->reply($message); } IM::Engine
  65. 65. IM::Engine $self->plugin_collect( role => 'ExtendsObject::User', method => 'traits', ); plugin_collect
  66. 66. IM::Engine $self->plugin_collect( role => 'ExtendsObject::User', method => 'traits', );
  67. 67. IM::Engine $self->plugin_collect( role => 'ExtendsObject::User', method => 'traits', ); traits
  68. 68. IM::Engine my @all_traits = $self->plugin_collect( role => 'ExtendsObject::User', method => 'traits', );
  69. 69. method plugin_collect { my @items; $self->each_plugin( callback => sub { push @items, shift->$method }, ); return @items; } plugin_collect
  70. 70. IM::Engine method new_with_plugins { my %args = ( $self->plugin_collect( role => …, method => 'ctor_args', ), @_, ); $self->new(%args); }
  71. 71. IM::Engine push @{ $args{traits} }, $self->plugin_collect( role => …, method => 'traits', ); $self->new_with_traits(%args);
  72. 72. MooseX::Traits $object = Class->new_with_traits( traits => ['Counter'], ); $other = Class->new; $object->counter; # 0 $other->counter; # Can't locate... new_with_traits MooseX::Traits
  73. 73. MooseX::Traits $object = Class->new_with_traits( traits => ['Counter'], ); $other = Class->new; $object->counter; # 0 $other->counter; # Can't locate...
  74. 74. Role = Trait Moose Role Trait
  75. 75. Moose Path::Dispatcher HTTP::Engine Dist::Zilla IM::Engine
  76. 76. Moose Extensibility Separation of sugar Dist::Zilla IM::Engine Moose
  77. 77. Moose Path::Dispatcher OO sugar layer Dist::Zilla IM::Engine OO
  78. 78. Moose Path::Dispatcher HTTP::Engine Omit inconsequential details IM::Engine
  79. 79. Moose Path::Dispatcher HTTP::Engine Dist::Zilla Explicit pluggability
  80. 80. Moose Path::Dispatcher HTTP::Engine Dist::Zilla IM::Engine Extreme pluggability DRY IM::Engine DRY
  81. 81. WRITEMoose Path::Dispatcher HTTP::Engine TESTS!!! Dist::Zilla IM::Engine
  82. 82. Moose Path::Dispatcher HTTP::Engine Dist::Zilla IM::Engine
  83. 83. Thanks to my Kenichi Ishigaki 83

×