YUKI Hiroshi
null+****@clear*****
Thu Feb 27 17:33:36 JST 2014
YUKI Hiroshi 2014-02-27 17:33:36 +0900 (Thu, 27 Feb 2014) New Revision: 1e5e09e527e1cab000969b429c61e479fafbf9c3 https://github.com/droonga/droonga.org/commit/1e5e09e527e1cab000969b429c61e479fafbf9c3 Message: Complete tutorial to develop handlers Modified files: tutorial/plugin-development/adapter/index.md tutorial/plugin-development/handler/index.md tutorial/plugin-development/index.md Modified: tutorial/plugin-development/adapter/index.md (+3 -1) =================================================================== --- tutorial/plugin-development/adapter/index.md 2014-02-27 17:19:58 +0900 (d106076) +++ tutorial/plugin-development/adapter/index.md 2014-02-27 17:33:36 +0900 (b566faf) @@ -63,7 +63,9 @@ require "droonga/plugin" module Droonga module Plugins module SampleLoggerPlugin - Plugin.registry.register("sample-logger", self) + extend Plugin + + registry.register("sample-logger", self) class Adapter < Droonga::Adapter # You'll put codes to modify messages here. Modified: tutorial/plugin-development/handler/index.md (+198 -27) =================================================================== --- tutorial/plugin-development/handler/index.md 2014-02-27 17:19:58 +0900 (8a7d691) +++ tutorial/plugin-development/handler/index.md 2014-02-27 17:33:36 +0900 (b02bbd3) @@ -1,10 +1,8 @@ --- -title: "Plugin: Handle requests" +title: "Plugin: Handle requests on all partitions" layout: en --- -!!! WORK IN PROGRESS !!! - * TOC {:toc} @@ -39,7 +37,11 @@ A class to define operations at the handling phase is called *handler*. Put simply, adding of a new handler means adding a new command. -## Design a read-only command + + + + +## Design a read-only command `countRecords` Here, in this tutorial, we are going to add a new custom `countRecords` command. At first, let's design it. @@ -184,13 +186,13 @@ Then, we also have to bind a collector to the step, with the configuration `step lib/droonga/plugins/count-records.rb: ~~~ruby -... +(snip) define_single_step do |step| step.name = "countRecords" step.handler = :Handler step.collector = SumCollector end -... +(snip) ~~~ The `SumCollector` is one of built-in collectors. @@ -211,7 +213,7 @@ Add `"count-records"` to `"plugins"`. (snip) ~~~ -### Run +### Run and test Let's get Droonga started. Note that you need to specify ./lib directory in RUBYLIB environment variable in order to make ruby possible to find your plugin. @@ -219,9 +221,7 @@ Note that you need to specify ./lib directory in RUBYLIB environment variable in # kill $(cat fluentd.pid) # RUBYLIB=./lib fluentd --config fluentd.conf --log fluentd.log --daemon fluentd.pid -### Test - -Send a message for the `countRecords` command to the Droonga Engine. +Then, send a message for the `countRecords` command to the Droonga Engine. ~~~ # droonga-request --tag starbucks count-records.json @@ -238,7 +238,7 @@ Elapsed time: 0.01494 ] ~~~ -Then you'll get a response message like above. +You'll get a response message like above. Look at these points: * The `type` of the response becomes `countRecords.result`. @@ -250,11 +250,10 @@ There are 3 elements in the array. Why? * Remember that we have configured the `Starbucks` dataset to use 3 partitions (and each has 2 replicas) in the `catalog.json` of [the basic tutorial][basic]. * Because it is a read-only command, an incoming message is distributed only to paritions, not to replicas. So there are only 3 results, not 6. + (TODO: I have to add a figure to indicate active nodes: [000, 001, 010, 011, 020, 021] => [000, 011, 020]) * The `SumCollector` collects them. Those 3 results are joined to just one array by the collector. -(TODO: I have to add a figure to indicate active nodes: [000, 001, 010, 011, 020, 021] => [000, 011, 020]) - As the result, just one array with 3 elements appears in the response message. ### Read-only access to the storage @@ -265,7 +264,7 @@ Let's implement codes to count up the number of records from the actual storage. lib/droonga/plugins/count-records.rb: ~~~ruby -... +(snip) class Handler < Droonga::Handler def handle(message) table_name = message["body"]["table"] @@ -274,7 +273,7 @@ lib/droonga/plugins/count-records.rb: [count] end end -... +(snip) ~~~ The instance variable `@context` is an instance of `Groonga::Context` for the storage of the partition. @@ -305,33 +304,204 @@ Elapsed time: 0.01494 Because there are totally 35 records, they are stored evenly like above. -## Read-write handler -(TBD) + + + + +## Design a read-write command `deleteStores` + +Next, let's add another new custom command `deleteStores`. + +The command deletes records of the `Store` table, from the storage. +Because it modifies something in existing storage, it is a *read-write command*. + +The request must have the condition to select records to be deleted, like: + +~~~json +{ + "dataset" : "Starbucks", + "type" : "deleteStores", + "body" : { + "keyword": "Broardway" + } +} +~~~ + +Any record including the given keyword `"Broadway"` in its `"key"` is deleted from the storage of all partitions. + +Create a JSON file `delete-stores-broadway.json` with the content above. +We'll use it for testing. + +The response must have a boolean value to indicate "success" or "fail", like: + +~~~json +{ + "inReplyTo": "(message id)", + "statusCode": 200, + "type": "deleteStores.result", + "body": true +} +~~~ + +If the request is successfully processed, the `body` becomes `true`. Otherwise `false`. +The `body` is just one boolean value, because we don't have to receive multiple results from partitions. + ### Directory Structure -(TBD) +Now let's create the `delete-stores` plugin, as the file `delete-stores.rb`. The directory tree will be: + +~~~ +lib +└── droonga + └── plugins + └── delete-stores.rb +~~~ + +Then, create a skelton of a plugin as follows: + +lib/droonga/plugins/delete-stores.rb: + +~~~ruby +require "droonga/plugin" + +module Droonga + module Plugins + module DeleteStoresPlugin + Plugin.registry.register("delete-stores", self) + end + end +end +~~~ + + +### Define a "step" for the command + +Define a "step" for the new `deleteStores` command, in your plugin. Like: + +lib/droonga/plugins/delete-stores.rb: + +~~~ruby +require "droonga/plugin" + +module Droonga + module Plugins + module DeleteStoresPlugin + Plugin.registry.register("delete-stores", self) + + define_single_step do |step| + step.name = "deleteStores" + step.write = true + end + end + end +end +~~~ + +Look at a new configuration `step.write`. +Because this command modifies the storage, we must indicate it clearly. + +### Define the handling logic + +Let's define the handler. + +lib/droonga/plugins/delete-stores.rb: + +~~~ruby +require "droonga/plugin" + +module Droonga + module Plugins + module DeleteStoresPlugin + Plugin.registry.register("delete-stores", self) + + define_single_step do |step| + step.name = "deleteStores" + step.write = true + step.handler = :Handler + step.collector = AndCollector + end + + class Handler < Droonga::Handler + def handle(message) + keyword = message["body"]["keyword"] + table = @context["Store"] + table.delete do |record| + record.key @ keyword + end + true + end + end + end + end +end +~~~ + +The handler finds and deletes existing records which have the given keyword in its "key", by the [API of Rroonga][Groonga::Table_delete]. + +And, the `AndCollector` is bound to the step by the configuration `step.collector`. +It is is also one of built-in collectors, and merges boolean values retuned from handler instances for each partition and replica, to one boolean value. -### Create a plugin -(TBD) ### Activate the plugin with `catalog.json` -(TBD) +Update catalog.json to activate this plugin. +Add `"delete-stores"` to `"plugins"`. + +~~~ +(snip) + "datasets": { + "Starbucks": { + (snip) + "plugins": ["delete-stores", "count-records", "groonga", "crud", "search"], +(snip) +~~~ + +### Run and test -### Run +Restart the Droonga Engine and send the request. -(TBD) +~~~ +# kill $(cat fluentd.pid) +# RUBYLIB=./lib fluentd --config fluentd.conf --log fluentd.log --daemon fluentd.pid +# droonga-request --tag starbucks count-records.json +Elapsed time: 0.01494 +[ + "droonga.message", + 1392621168, + { + "inReplyTo": "1392621168.0119512", + "statusCode": 200, + "type": "deleteStores.result", + "body": true + } +] +~~~ -### Test +Because results from partitions are unified to just one boolean value, the response's `body` is a `true`. +As the verification, send the request of `countRecords` command. + +~~~ +# droonga-request --tag starbucks count-records.json +Elapsed time: 0.01494 +[ + "droonga.message", + 1392621168, + { + "inReplyTo": "1392621168.0119512", + "statusCode": 200, + "type": "countRecords.result", + "body": [8, 8, 7] + } +] +~~~ -(TBD) +Note, the number of records are smaller than the previous result. +This means that 4 or some records are deleted from each partitions. -### Design input and output -(TBD) ## Conclusion @@ -342,3 +512,4 @@ We have learned how to create plugins work in handling phrase. [adapter]: ../adapter [basic]: ../basic [Groonga::Context]: http://ranguba.org/rroonga/en/Groonga/Context.html + [Groonga::Table_delete]: http://ranguba.org/rroonga/en/Groonga/Table.html#delete-instance_method Modified: tutorial/plugin-development/index.md (+3 -2) =================================================================== --- tutorial/plugin-development/index.md 2014-02-27 17:19:58 +0900 (fcc9f2f) +++ tutorial/plugin-development/index.md 2014-02-27 17:33:36 +0900 (a466dfb) @@ -58,8 +58,9 @@ Following this tutorial, you will learn how to write plugins. This will be the f For more details, let's read these sub tutorials: 1. [Modify requests and responses][adapter] - 2. [Handle requests][handler] (under construction) - 3. Distribute requests and collect responses (under construction) + 2. [Handle requests on all partitions][handler] + 3. Handle requests only on a specific partition (under construction) + 4. Distribute requests and collect responses (under construction) [basic tutorial]: ../basic/ -------------- next part -------------- HTML����������������������������... 下載