Slide 1

Slide 1 text

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.

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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/

Slide 4

Slide 4 text

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/

Slide 5

Slide 5 text

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/

Slide 6

Slide 6 text

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.

Slide 7

Slide 7 text

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/

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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!

Slide 11

Slide 11 text

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!

Slide 12

Slide 12 text

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...

Slide 13

Slide 13 text

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.

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Test! Thursday, September 10, 2009

Slide 16

Slide 16 text

Test! Thursday, September 10, 2009

Slide 17

Slide 17 text

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.

Slide 18

Slide 18 text

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/

Slide 19

Slide 19 text

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.

Slide 20

Slide 20 text

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.

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

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!

Slide 23

Slide 23 text

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.

Slide 24

Slide 24 text

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.

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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.

Slide 28

Slide 28 text

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".

Slide 29

Slide 29 text

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.

Slide 30

Slide 30 text

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.

Slide 31

Slide 31 text

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.

Slide 32

Slide 32 text

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.

Slide 33

Slide 33 text

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.

Slide 34

Slide 34 text

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.

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

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.

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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.

Slide 40

Slide 40 text

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.

Slide 41

Slide 41 text

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.

Slide 42

Slide 42 text

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.

Slide 43

Slide 43 text

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...

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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.

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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.

Slide 48

Slide 48 text

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".

Slide 49

Slide 49 text

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...

Slide 50

Slide 50 text

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.

Slide 51

Slide 51 text

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.

Slide 52

Slide 52 text

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.

Slide 53

Slide 53 text

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.

Slide 54

Slide 54 text

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".

Slide 55

Slide 55 text

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.

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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.

Slide 58

Slide 58 text

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.

Slide 59

Slide 59 text

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.

Slide 60

Slide 60 text

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.

Slide 61

Slide 61 text

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/

Slide 62

Slide 62 text

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.

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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.

Slide 65

Slide 65 text

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.

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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.

Slide 69

Slide 69 text

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.

Slide 70

Slide 70 text

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.

Slide 71

Slide 71 text

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.

Slide 72

Slide 72 text

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.

Slide 73

Slide 73 text

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.

Slide 74

Slide 74 text

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.

Slide 75

Slide 75 text

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.

Slide 76

Slide 76 text

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.

Slide 77

Slide 77 text

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.

Slide 78

Slide 78 text

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.

Slide 79

Slide 79 text

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.

Slide 80

Slide 80 text

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.

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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