Upgrade to Pro — share decks privately, control downloads, hide ads and more …

API Design

API Design

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.

Finally, I will reveal the secret to designing excellent APIs.

Shawn Moore

October 23, 2011
Tweet

More Decks by Shawn Moore

Other Decks in Programming

Transcript

  1. API Design
    Shawn M Moore
    Best Practical Solutions
    http://sartak.org
    Thursday, September 10, 2009
    Presented YAPC::Asia, 2009-09-10.
    Tokyo Institute of Technology, Tokyo, Japan.

    View Slide

  2. GoogleͷςοΫτʔΫͰ΋APIઃܭͷ࿩͕ग़ͯ·ͨ͠
    Thursday, September 10, 2009
    There is a good Google Tech Talk on "how to design an API and why it matters". There isn't a whole
    lot of overlap between this talk and that one. Watch that one.
    http://www.youtube.com/watch?v=aAb7hSCtvGw

    View Slide

  3. CC-BY-SA Yuval Kogman, 2006
    Thursday, September 10, 2009
    At YAPC::NA 2009, this guy, Hans Dieter Pearcey aka confound aka hdp, presented a talk about
    Dist::Zilla.
    http://www.flickr.com/photos/nuffin/179250512/

    View Slide

  4. CC-BY-SA Yuval Kogman, 2006
    Thursday, September 10, 2009
    Dist::Zilla was written by this other guy, Ricardo Signes, aka rjbs.
    http://www.flickr.com/photos/nuffin/179250812/

    View Slide

  5. CC-BY-SA Hans Dieter Pearcey, 2009
    Thursday, September 10, 2009
    Dieter presented this slide about Dist::Zilla's pluggable design. I loved it and I wanted to devote an
    entire talk to its glory.
    http://weftsoar.net/~hdp/dzil/

    View Slide

  6. Moose
    Path::Dispatcher
    HTTP::Engine
    Dist::Zilla
    IM::Engine
    ࠓ೔͸͜ΕΒͷϓϩδΣΫτ͕࣋ͭΫʔϧͳ
    APIͷ࿩Λ͠·͢
    Thursday, September 10, 2009
    I'm here to highlight really cool API designs that these projects have. In particular, they design for
    extensibility and pluggability. Extensibility is really important to the current and future success of
    these projects.

    View Slide

  7. CC-BY-SA-NC Will Spaetzel, 2005
    Thursday, September 10, 2009
    If you haven't noticed yet, this talk is going to be very Moose-heavy. All those modules have the
    Moose nature.
    http://www.flickr.com/photos/redune/6562798/

    View Slide

  8. THE SECRET
    ൿ݃ɺͱ͍ͬͯ΋Έͳ͞Μ͝ଘͩ͡ͱࢥ͍·͕͢
    Thursday, September 10, 2009
    There is a poorly kept secret for designing great APIs. I hope that all of you already do this, but you
    probably do not do it enough.

    View Slide

  9. THE SECRET
    WRITE
    TESTS
    େࣄͳͷ͸ςετΛॻ͘ɺͱ͍͏͜ͱ
    Thursday, September 10, 2009
    Write tests.

    View Slide

  10. WRITE TESTS
    WRITE
    TESTS
    WRITE
    TESTS
    WRITE
    TESTS
    WRITE
    TESTS
    ݂؅੾ΕΔ·ͰςετΛॻ͍͍ͯͩ͘͞
    Thursday, September 10, 2009
    Write so many tests your ears bleed. I am not joking!

    View Slide

  11. DO THIS
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    WRITE
    ͳʹ͸ͳ͘ͱ΋ɺͱʹ͔͘ςετ
    Thursday, September 10, 2009
    If you remember nothing else, remember to write tests!

    View Slide

  12. Test!
    use base 'Class::Accessor::Fast';
    __PACKAGE__->mk_ro_accessors('birthday');
    use Moose;
    has birthday => (is => 'ro');
    ςετΛॻ͚͹࢖͍΍͍͢API͔Ͳ͏͔͕Θ͔Γ·͢
    Thursday, September 10, 2009
    Write tests so you can tell if your API is painful to use. Which of these would you rather be stuck
    with?
    Make it painless for your users. Some of them might be using your module a lot. If it's tedious to
    use your module...

    View Slide

  13. Test!
    ࢖͍ͮΒ͍Ϟδϡʔϧ͸࢖ͬͯ΋Β͑·ͤΜ͔Β
    Thursday, September 10, 2009
    ... then you'll piss your users off. They'll leave and use some other module, or worse, find out where
    you live.

    View Slide

  14. Test!
    Thursday, September 10, 2009
    This is Jesse Vincent, the nicest guy in the world :)

    View Slide

  15. Test!
    Thursday, September 10, 2009

    View Slide

  16. Test!
    Thursday, September 10, 2009

    View Slide

  17. Moose
    package Point;
    use Moose;
    has x => (
    is => 'ro',
    isa => 'Num',
    );
    ͜ͷઌ͸Mooseϕʔεͷ࿩ͳͷͰগ͠ղઆ
    Thursday, September 10, 2009
    Moose serves as the foundation for the rest of the talk, so I want to explain what it "got right" in
    terms of its API. These next few slides are difficult but it will get clearer and less heady, so wake up
    soon if you space out.

    View Slide

  18. Metaobject Protocol
    Class::MOP
    Moose͸Class::MOPͷϥούͰ͢
    Thursday, September 10, 2009
    Moose is built on top of a metaobject protocol. This is Class::MOP.
    See my "Extending Moose for Applications" talk for a proper introduction to the metaobject protocol
    http://sartak.org/talks/yapc-na-2009/extending-moose/

    View Slide

  19. Metaobject Protocol
    has cache => (
    is => 'ro',
    );
    ཁ͢ΔʹΫϥεͷ֤ύʔπ͸͢΂ͯΦϒδΣΫτͰ͢
    Thursday, September 10, 2009
    The MOP is vital to Moose's operation. Basically, it means that every part of your class is
    represented by an object.

    View Slide

  20. Metaobject Protocol
    has cache => (
    is => 'ro',
    );
    Moose::Meta::Attribute
    has͸Moose::Meta::AttributeͷΠϯελϯεΛ࡞Γ·͢
    Thursday, September 10, 2009
    When you say "has" it creates an instance of the Moose::Meta::Attribute class, which holds
    information like the attribute's name, its type constraint, default value, etc.

    View Slide

  21. Metaobject Protocol
    has cache => (
    is => 'ro',
    );
    Moose::Meta::Method::Accessor
    ͜ΕͰcacheͱ͍͏ΞΫηαϝιου͕࡞ΒΕ·͢
    Thursday, September 10, 2009
    The is => 'ro' option creates a "cache" method in your class. It also creates an object of class
    Moose::Meta::Method::Accessor to represent that "cache" method.

    View Slide

  22. Metaobject Protocol
    class PersistentAttr
    extends Moose::Meta::Attribute {

    }
    has cache => (
    metaclass => 'PersistentAttr',
    is => 'ro',
    );
    MooseͷΫϥεΛ֦ு͢Ε͹ಠࣗػೳ΋௥ՃͰ͖·͢
    Thursday, September 10, 2009
    This is important because we can subclass Moose's class to add our own special logic, such as
    making the cache persist across processes. Subclassing and adding logic is ordinary object-
    oriented programming!

    View Slide

  23. Metaobject Protocol
    role PersistentAttr {

    }
    has cache => (
    traits => ['PersistentAttr'],
    is => 'ro',
    );
    ΞτϦϏϡʔτΦϒδΣΫτʹϩʔϧΛ૊ΈࠐΉ͜ͱ
    ΋Ͱ͖·͢
    Thursday, September 10, 2009
    We can also specify roles to apply to cache's attribute object. This is slightly better because it
    means a single attribute can have many extensions. Just like how it's better to design with roles
    than subclasses in ordinary programming.

    View Slide

  24. MooseX
    ΄ͱΜͲͷMooseX͸MOPΛར༻͍ͯ͠·͢
    Thursday, September 10, 2009
    The metaobject protocol powers most of the MooseX modules. In my opinion, the metaobject
    protocol is responsible for a very large part of Moose's popularity. The other reason for Moose's
    popularity is it enables concise class code.

    View Slide

  25. Sugar Layer
    Moose͸γϡΨʔ૚͕͖Ε͍ʹ෼཭͍ͯ͠Δͷ΋ಛ௃
    Ͱ͢
    Thursday, September 10, 2009
    Moose also makes a very clean separation between its sugar layer and the rest of the system.

    View Slide

  26. Sugar Layer
    my $class = get_class();
    ͋ΔΫϥεΛར༻͍ͨ͠ͱ͠·͢
    Thursday, September 10, 2009
    Say you wanted to get ahold of some class...

    View Slide

  27. Sugar Layer
    my $class = get_class();
    $class->has(
    birthday => (
    is => 'ro',
    )
    );
    has͸ϝιουͰ͸ͳ͍ͷͰ͜ΕͰ͸ͩΊͰ͢
    Thursday, September 10, 2009
    Then add an attribute to it. This doesn't work because "has" is not a method. Its first parameter is
    supposed to be the attribute name, not the class you're adding the attribute to.

    View Slide

  28. Sugar Layer
    my $class = get_class();
    no strict 'refs';
    *{$class.'::has'}->(
    birthday => (
    is => 'ro',
    )
    );
    ͔ͩΒͱ͍ͬͯhasΛ
    ؔ਺ͱͯ͠ݺͿͷ͸͹͔͍͛ͯ·͢
    Thursday, September 10, 2009
    So we have to call $class's "has" as a function. This kind of thing is ridiculous. Maybe the other class
    has used "no Moose" so that "has" is deleted. Or perhaps it renamed "has".

    View Slide

  29. Sugar Layer
    my $class = get_class();
    no strict 'refs';
    *{$class.'::has'}->(
    birthday => (
    is => 'ro',
    )
    );
    ͦΕʹݟͮΒ͍Ͱ͢ΑͶ
    Thursday, September 10, 2009
    Not to mention how ugly this mess is.

    View Slide

  30. Sugar Layer
    Class::MOP::Class
    ->initialize($class)
    ->add_attribute(
    $name, %options);
    has͸جຊతʹ͸add_attributeͷϥούͰ͢
    Thursday, September 10, 2009
    If we look at the source code of Moose, we can see "has" is basically a wrapper around the
    "add_attribute" method of the Class::MOP::Class instance.

    View Slide

  31. Sugar Layer
    my $class = get_class();
    $class->meta->add_attribute(
    birthday => (
    is => 'ro',
    )
    );
    ͜ΕͰ͍ͣͿΜϚγʹͳΓ·ͨ͠
    Thursday, September 10, 2009
    Much better. There's no messy syntax. This can be used outside of $class's namespace just fine.
    This also works if class has cleaned up after Moose with "no Moose" or namespace::clean.

    View Slide

  32. Sugar Layer
    use MooseX::Declare;
    class Point3D extends Point {
    has z => (…);
    after clear {
    $self->z(0);
    }
    }
    γϡΨʔ૚͕෼཭͍ͯ͠ΔͷͰमਖ਼΋ָͰ͢
    Thursday, September 10, 2009
    Having a clean sugar layer means that other people can write better sugar. I like the idea of
    providing a separate Devel::Declare-powered sugar layer in a separate distribution. It forces you to
    cleanly separate the pieces.

    View Slide

  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Ͱ
    ࢖͏ͨΊʹॻ͖·ͨ͠
    Thursday, September 10, 2009
    Path::Dispatcher is a standalone-URI dispatcher. I wrote it because I wanted Jifty::Dispatcher for
    Prophet's command-line interface.
    This is its sugar layer. Like Moose, it has a clean, extensible API if you want the freedom to do
    unusual things.

    View Slide

  34. use Sub::Exporter -setup => {
    exports => [
    on => \&build_on,
    under => \&build_under,
    …,
    ],
    };
    Path::Dispatcher::Declarative
    ΋ͱ΋ͱ͸Sub::ExporterΛ࢖͍ͬͯ·ͨ͠
    Thursday, September 10, 2009
    It used to be that Path::Dispatcher::Declarative was implemented as an ordinary Sub::Exporter-using
    module.

    View Slide

  35. use Sub::Exporter -setup => {
    exports => [
    on => \&build_on,
    under => \&build_under,
    …,
    ],
    };
    Path::Dispatcher::Declarative
    Ͱ΋͜ΕͰ͸·֦ͬͨ͘ுੑ͕͋Γ·ͤΜ
    Thursday, September 10, 2009
    This is not at all extensible. You can't change the meaning of "on" or "under" because these are
    hardcoded. Reusing this sugar would be painful as well.

    View Slide

  36. Path::Dispatcher::Builder
    Robert Krimen
    "grink"
    Robert Krimen͕֦ு͕ͨͬͨ͠ͷͰ
    Thursday, September 10, 2009
    This was fine for a few weeks, but then Robert Krimen started using Path::Dispatcher. And he
    wanted to extend it for a module he was writing called Getopt::Chain.

    View Slide

  37. Path::Dispatcher::Builder
    return {
    on => sub { $builder->on(@_) },
    under => sub { $builder->under(@_) },
    …,
    };
    αϒΫϥεԽͯ͠ϩδοΫมߋͰ͖ΔΑ͏ʹ͠·ͨ͠
    Thursday, September 10, 2009
    Path::Dispatcher::Builder makes the sugar layer creation use OOP. This let Robert subclass
    Path::Dispatcher::Builder and use it for his own modules. He can reuse the regular dispatcher logic,
    tweak it by overriding methods, and add his own behavior.

    View Slide

  38. grink++
    OOͷγϡΨʔ͸ຊ౰ʹ͍͍Ͱ͢Α
    Thursday, September 10, 2009
    OO sugar is a really neat idea that I haven't seen anywhere else.

    View Slide

  39. HTTP::Engine
    HTTP::Engine->new(
    interface => {
    module => 'ServerSimple',
    args => { … },
    request_handler => sub {

    },
    },
    )->run;
    HTTP::EngineΛ࢖͏ͱ޷͖ͳαʔόΛબ΂·͢
    Thursday, September 10, 2009
    HTTP::Engine abstracts away the various HTTP server interfaces that Perl has accumulated since
    HTTP was invented. The benefit is in letting the user pick which server interface best fits their
    particular needs.

    View Slide

  40. HTTP::Engine
    HTTP::Engine->new(
    interface => {
    module => 'ModPerl',
    args => { … },
    request_handler => sub {

    },
    },
    )->run;
    ϚκͳਓͳΒmod_perlΛ࢖͑͹͍͍Ͱ͢͠
    Thursday, September 10, 2009
    For example, you can use mod_perl if you enjoy pain.

    View Slide

  41. HTTP::Engine
    HTTP::Engine->new(
    interface => {
    module => 'FCGI',
    args => { … },
    request_handler => sub {

    },
    },
    )->run;
    Ϋʔϧͳਓ͸FastCGIΛ࢖͍·͢
    Thursday, September 10, 2009
    Or FastCGI if you're a cool dude.

    View Slide

  42. HTTP::Engine
    request_handler => sub {
    my $request = shift;
    return $response;
    }
    HTTP::EngineΛ࢖͑͹IOͷϦμΠϨΫτͳͲ͸ෆཁͰ͢
    Thursday, September 10, 2009
    HTTP::Engine works well because the code you write doesn't have to worry about redirecting I/O
    streams, making sense of %ENV, or any of the other crap you do when writing against a particular
    server module.

    View Slide

  43. HTTP::Engine
    request_handler => sub {
    my $request = shift;
    return $response;
    }
    HTTP::EngineͳΒ࠷௿ݶඞཁͳ͜ͱΛॻ͚͹OK
    Thursday, September 10, 2009
    HTTP::Engine boils the web server cycle to the least common denominator. You take a request...

    View Slide

  44. HTTP::Engine
    request_handler => sub {
    my $request = shift;
    return $response;
    }
    ϦΫΤετΛड͚औͬͯϨεϙϯεΛฦ͢
    Thursday, September 10, 2009
    … and return a response.

    View Slide

  45. HTTP::Engine++
    ৽͍͠αʔό͕௥Ճ͞Εͯ΋1ߦม͑Ε͹ରԠՄೳ
    Thursday, September 10, 2009
    Can we please standardize on this? New server modules can implement an HTTP::Engine::Interface,
    then immediately every existing HTTP::Engine-based application can switch to it by changing only a
    single line of code.

    View Slide

  46. CC-BY-SA Hans Dieter Pearcey, 2009
    Thursday, September 10, 2009
    Now I want to explain why this is so awesome.

    View Slide

  47. Dist::Zilla
    AllFiles
    ExtraTests
    InstallDirs
    License
    MakeMaker
    Manifest
    ManifestSkip
    MetaYAML
    PkgVersion
    PodTests
    PodVersion
    PruneCruft
    Readme
    UploadToCPAN
    ͜Ε͸Α͘࢖ΘΕΔϓϥάΠϯͷϦετͰ͢
    Thursday, September 10, 2009
    Here's a list of plugins used by a typical Dist::Zilla-based distribution.

    View Slide

  48. Dist::Zilla
    AllFiles
    ExtraTests
    InstallDirs
    License
    MakeMaker
    Manifest
    ManifestSkip
    MetaYAML
    PkgVersion
    PodTests
    PodVersion
    PruneCruft
    Readme
    UploadToCPAN
    $_->gather_files
    for
    $self->plugins_with(
    -FileGatherer
    );
    Dist::ZillaͰ͸Α͘͜Μͳϝιουݺͼग़͠Λ͠·͢
    Thursday, September 10, 2009
    Dist::Zilla itself occasionally calls methods like this. The key bit is "plugins_with".

    View Slide

  49. Dist::Zilla
    AllFiles
    ExtraTests
    InstallDirs
    License
    MakeMaker
    Manifest
    ManifestSkip
    MetaYAML
    PkgVersion
    PodTests
    PodVersion
    PruneCruft
    Readme
    UploadToCPAN
    $_->gather_files
    for
    $self->plugins_with(
    -FileGatherer
    );
    plugins_withʹ໾ׂ໊Λ౉͢ͱ
    Thursday, September 10, 2009
    plugins_with takes a role name...

    View Slide

  50. Dist::Zilla
    ExtraTests
    InstallDirs
    MakeMaker
    Manifest
    ManifestSkip
    PkgVersion
    PodVersion
    PruneCruft
    UploadToCPAN
    AllFiles
    License
    MetaYAML
    PodTests
    Readme
    $_->gather_files
    for
    $self->plugins_with(
    -FileGatherer
    );
    ͦͷ໾ׂΛ֤࣋ͭछϓϥάΠϯ͕બ͹Ε
    Thursday, September 10, 2009
    ...and selects the plugins that "do" the role. These plugins all do the "FileGatherer" role, which
    means the plugin adds files to a distribution.

    View Slide

  51. Dist::Zilla
    ExtraTests
    InstallDirs
    MakeMaker
    Manifest
    ManifestSkip
    PkgVersion
    PodVersion
    PruneCruft
    UploadToCPAN
    AllFiles
    License
    MetaYAML
    PodTests
    Readme
    $_->gather_files
    for
    $self->plugins_with(
    -FileGatherer
    );
    gather_filesͰ֤छϓϥάΠϯ͕
    ௥Ճͨ͠ϑΝΠϧΛ·ͱΊ·͢
    Thursday, September 10, 2009
    Then, dzil calls gather_files on each of these plugins so it can actually add files to the distribution.
    "License", "Readme", and "MetaYAML" add the respective files, "AllFiles" adds every file the author
    wrote. "PodTests" adds pod testing files to the distribution.

    View Slide

  52. Dist::Zilla
    AllFiles
    License
    MakeMaker
    Manifest
    ManifestSkip
    MetaYAML
    PodTests
    PruneCruft
    Readme
    UploadToCPAN
    $_->munge_files
    for
    $self->plugins_with(
    -FileMunger
    );
    ExtraTests
    InstallDirs
    PkgVersion
    PodVersion
    Dist::ZillaͰ͸͜ͷख๏Λ͋ͪͪ͜Ͱར༻͍ͯ͠·͢
    Thursday, September 10, 2009
    Dist::Zilla uses this architecture for all of the interesting parts of building a CPAN distribution. This
    is "munging files", which lets plugins edit files to increase the version number, or move tests
    around.

    View Slide

  53. Request Tracker
    $m->callback(
    CallbackName => 'FormEnd',
    UserObj => $UserObj,
    …,
    );
    User/Prefs.html
    RTʹ΋ಉ͡Α͏ͳϝΧχζϜ͕͋Γ·͢
    Thursday, September 10, 2009
    It turns out that RT has a very similar extension mechanism.

    View Slide

  54. Request Tracker
    $m->callback(
    CallbackName => 'FormEnd',
    UserObj => $UserObj,
    …,
    );
    User/Prefs.html
    ίʔϧόοΫͰؔ܎͢ΔϓϥάΠϯΛ୳ͯ͠
    Thursday, September 10, 2009
    This code exists in User/Prefs.html. The callback method selects all plugins that do the "User/
    Prefs.html" "role".

    View Slide

  55. Request Tracker
    $m->callback(
    CallbackName => 'FormEnd',
    UserObj => $UserObj,
    …,
    );
    User/Prefs.html
    ͦΕͧΕͷϓϥάΠϯͰFormEndͱ͍͏ϝιουΛ࣮ߦ
    Thursday, September 10, 2009
    Then it calls the FormEnd "method" (template) on these selected plugins.

    View Slide

  56. Request Tracker
    $m->callback(
    CallbackName => 'FormEnd',
    UserObj => $UserObj,
    …,
    );
    User/Prefs.html
    ϝιουʹ͸೚ҙͷύϥϝʔλΛ౉ͤ·͢
    Thursday, September 10, 2009
    And you can pass arbitrary parameters to each method.

    View Slide

  57. Request Tracker
    $m->callback(
    CallbackName => 'FormEnd',
    UserObj => $UserObj,
    …,
    );
    User/Prefs.html
    ಠ֦ࣗு͸΄ͱΜͲ͜ͷίʔϧόοΫͰ
    ߏங͍ͯ͠·͢
    Thursday, September 10, 2009
    This works extremely well for us! We try to build most customer extensions with callbacks. It's
    basically the same design as Dist::Zilla's.

    View Slide

  58. Request Tracker
    commit 4c05a6835eef112701ac58dfd1b133e220059d4f
    Author: Jesse Vincent
    Date: Fri Dec 27 18:50:06
    2002 -0500
    Attempting mason callouts
    Ticket/Update.html
    <& /Elements/Callback,
    Name => 'BeforeTextarea',
    %ARGS
    &>
    ͜ͷ࢓૊Έ͸΋͏7೥ۙ͘࢖͍ͬͯ·͢
    Thursday, September 10, 2009
    RT has had callbacks since 2002, first released in 3.0.0. This pattern has been the best mechanism
    for any kind of RT extension for almost seven years now.

    View Slide

  59. Dist::Zilla
    Choice
    ModuleBuild MakeMaker
    MetaYAML MetaJSON
    or
    or
    ͜͏͓ͯ͘͠ͱϢʔβ͕ࣗ༝ʹػೳΛબ୒Ͱ͖·͢
    Thursday, September 10, 2009
    This design gives the user choice over which behavior she wants. And in my experience, users really
    really want choice.

    View Slide

  60. Dist::Zilla
    Extensibility
    Dist::Zilla::Plugin::CriticTests
    Dist::Zilla::Plugin::Repository
    Dist::Zilla::Plugin::PerlTidy
    ֦ு΋ࣗ༝Ͱ͢
    Thursday, September 10, 2009
    This design is also extensible for free. These are some of the modules that have been written to
    extend Dist::Zilla.

    View Slide

  61. Dist::Zilla
    Extensibility
    Dist::Zilla::Plugin::CriticTests
    Dist::Zilla::Plugin::Repository
    Dist::Zilla::Plugin::PerlTidy
    InlineFiles
    MetaProvider
    FileMunger
    ʮϩʔϧʯʹඞཁͳ৚݅͑͞ຬͨͤ͹͍͍ͷͰ͔͢Β
    Thursday, September 10, 2009
    All they need to do is fulfill the requirements of the roles they "do". I'm going to talk about that
    more in my (Parameterized) Roles talk.
    http://sartak.org/talks/yapc-asia-2009/(parameterized)-roles/

    View Slide

  62. Dist::Zilla
    Extensibility
    Dist::Zilla::Plugin::BPS::Secret
    ֦ுੑ͸େࣄͰ͢Αɻެ։Ͱ͖ͳ͍
    ίʔυ΋͋Γ·͔͢Β
    Thursday, September 10, 2009
    Extensibility is also important for code you can't share. We can't ask Ricardo to include company
    secrets for Dist::Zilla, and maintaining a fork really sucks.

    View Slide

  63. CC-BY-SA Hans Dieter Pearcey, 2009
    Thursday, September 10, 2009
    So now you know!

    View Slide

  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ͱ͍͏
    ϓϩδΣΫτ
    Thursday, September 10, 2009
    IM::Engine is a project I'm working on. It's basically HTTP::Engine for IM. You can write a bot, once,
    that will run on any service IM::Engine can talk to, including IRC. IM::Engine smooths over the
    differences in the protocols.

    View Slide

  65. IM::Engine
    $self->plugin_collect(
    role => 'ExtendsObject::User',
    method => 'traits',
    );
    ͱΓΘ͚ؾʹೖ͍ͬͯΔͷ͸plugin_collectͰ͢
    Thursday, September 10, 2009
    I've extended Ricardo's design with a number of helper methods. plugin_collect is the one I like
    best.

    View Slide

  66. IM::Engine
    $self->plugin_collect(
    role => 'ExtendsObject::User',
    method => 'traits',
    );
    ͜ͷ໾ׂΛ࣋ͭϓϥάΠϯͷͦΕͧΕʹ͍ͭͯ
    Thursday, September 10, 2009
    For each plugin that does the ExtendsObject::User role...

    View Slide

  67. IM::Engine
    $self->plugin_collect(
    role => 'ExtendsObject::User',
    method => 'traits',
    );
    traitsϝιουΛݺͼग़͠·͢
    Thursday, September 10, 2009
    ...call its "traits" method.

    View Slide

  68. IM::Engine
    my @all_traits =
    $self->plugin_collect(
    role => 'ExtendsObject::User',
    method => 'traits',
    );
    ฦΓ஋͸ͦΕͧΕͷϝιουͷ݁ՌΛ
    ·ͱΊͨ΋ͷʹͳΓ·͢
    Thursday, September 10, 2009
    The return value of this call is the list of all return values of the "traits" methods.

    View Slide

  69. method plugin_collect {
    my @items;
    $self->each_plugin(
    callback => sub {
    push @items,
    shift->$method
    },
    );
    return @items;
    }
    ͜Ε͕plugin_collectͷ؊ͷ෦෼Ͱ͢
    Thursday, September 10, 2009
    This is the important part of plugin_collect's implementation. There's not much there. I like very
    layered APIs because they're easier to understand and reuse, especially by your users, than huge
    monolithic methods. Each layer does only a little bit of work.

    View Slide

  70. IM::Engine
    method new_with_plugins {
    my %args = (
    $self->plugin_collect(
    role => …,
    method => 'ctor_args',
    ),
    @_,
    );
    $self->new(%args);
    }
    ͜Ε΋͓ؾʹೖΓͷσβΠϯͰ͢
    Thursday, September 10, 2009
    Here's a piece of design I like a lot. This lets plugins participate in object construction. Each plugin
    can provide constructor arguments.

    View Slide

  71. IM::Engine
    push @{ $args{traits} },
    $self->plugin_collect(
    role => …,
    method => 'traits',
    );
    $self->new_with_traits(%args);
    ͜͏ͯ͠ϓϥάΠϯʹϩʔϧΛ
    ఏڙͤ͞Δ͜ͱ΋ՄೳͰ͢
    Thursday, September 10, 2009
    This lets plugins participate even more in object construction. Now plugins can provide roles for
    the object you're constructing. This lets plugins add attributes and methods to the object. I use this
    in a plugin to give state management methods to User objects.

    View Slide

  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Λར༻͍ͯ͠·͢
    Thursday, September 10, 2009
    new_with_traits comes from MooseX::Traits. It's a really nice module for designing pluggable and
    extensible systems. You just pass a list of roles to new_with_traits and it will arrange it so that the
    object does those roles.

    View Slide

  73. MooseX::Traits
    $object = Class->new_with_traits(
    traits => ['Counter'],
    );
    $other = Class->new;
    $object->counter; # 0
    $other->counter; # Can't locate...
    ͜͏͢Δͱଞͷ֦ுʹѱӨڹΛ
    ༩͑Δ͜ͱ͸͋Γ·ͤΜ
    Thursday, September 10, 2009
    Other objects of that class are not affected by new_with_traits. The way it works internally is by
    creating a new subclass of Class. This is vital because it maintains modularity. I don't want my
    extensions to screw up your extensions.

    View Slide

  74. Role = Trait
    MooseͷੈքͰ͸RoleͱTrait͸΄΅ಉٛޠͰ͢
    Thursday, September 10, 2009
    In Moose land, roles and traits are basically synonymous. Some people will tell you there are subtle
    differences, but there's no clear consensus. I just say "roles" except when I have to say "traits" for a
    module.

    View Slide

  75. Moose
    Path::Dispatcher
    HTTP::Engine
    Dist::Zilla
    IM::Engine
    ·ͩ·ͩ঺հ͍ͨ͠ྫ͸
    ͋Γ·͕͢ɺͦΖͦΖ࣌ؒͰ͢
    Thursday, September 10, 2009
    So that is all I have time to cover. There are plenty more nice examples in modules like KiokuDB,
    Fey, and the now-moosified Catalyst.

    View Slide

  76. Moose
    Dist::Zilla
    IM::Engine
    Extensibility
    Separation of sugar
    Moose͸֦ுੑ΍γϡΨʔͷ෼཭ͷେ੾͞Λ
    ڭ͑ͯ͘Ε·ͨ͠
    Thursday, September 10, 2009
    Moose teaches us that extensibility can lead to a great corpus of extensions. Separation of sugar
    keeps you and your users flexible.

    View Slide

  77. Moose
    Path::Dispatcher
    Dist::Zilla
    IM::Engine
    OO sugar layer
    OOγϡΨʔ૚ͱ͍͏ߟ͑ํ͸΋ͬͱ޿·ͬͯ΄͍͠ͳ
    Thursday, September 10, 2009
    The OO sugar layer is a new idea that I hope catches on. I'll have to dedicate more time to it.

    View Slide

  78. Moose
    Path::Dispatcher
    HTTP::Engine
    IM::Engine
    Omit inconsequential details
    ຊ࣭͡Όͳ͍෦෼Λͦ͗མͱͤ͹ΞϓϦ͸
    ॊೈͰ؆ܿʹͳΓ·͢
    Thursday, September 10, 2009
    If you omit inconsequential details, then your application remains flexible and concise.

    View Slide

  79. Moose
    Path::Dispatcher
    HTTP::Engine
    Dist::Zilla
    Explicit pluggability
    ௥Ճ͍ͨ͠ػೳΛ໌ࣔతʹࢦఆͰ͖͍͍ͨͬͯ
    Thursday, September 10, 2009
    Pluggability does not have to be implicit, as in subclassing. Explicitly controlling pluggability lets
    you do more interesting things.

    View Slide

  80. Moose
    Path::Dispatcher
    HTTP::Engine
    Dist::Zilla
    IM::Engine
    Extreme pluggability
    DRY
    IM::EngineͷΑ͏ͳ΍ΓํΛ͢Ε͹DRYʹͰ͖Δ
    Thursday, September 10, 2009
    … such as the things IM::Engine does, by letting plugins manipulate system objects. It also provides
    methods for common plugin operations so you don't have to repeat them everywhere.

    View Slide

  81. Moose
    Path::Dispatcher
    HTTP::Engine
    Dist::Zilla
    IM::Engine
    WRITE
    TESTS!!!
    Thursday, September 10, 2009
    I almost forgot...

    View Slide

  82. Moose
    Path::Dispatcher
    HTTP::Engine
    Dist::Zilla
    IM::Engine
    ͓ͬͱ๨ΕΔͱ͜Ζ
    ͩͬͨɻςετॻ͚Αʂ
    Thursday, September 10, 2009
    I almost forgot...

    View Slide

  83. Thanks to my translator
    83
    Kenichi Ishigaki
    Thursday, September 10, 2009
    Thank you to Ishigaki-san for translating my slides!

    View Slide