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

SpreeConf DC 2013: Introducing the Spree Integrator

SpreeConf DC 2013: Introducing the Spree Integrator

A detailed look at the first Spree Integrator release.

Brian Quinn

May 21, 2013
Tweet

More Decks by Brian Quinn

Other Decks in Technology

Transcript

  1. x

  2. x x

  3. My Spree Store ✔ Mandrill for transactional emails ✔ Shipwire

    for drop shipping ✔ Netsuite for accounting ✔ MixPanel for analytics
  4. Spree Integrator orders updated since (timestamp) updated orders Mandrill Endpoint

    send order confirmation email detect new orders Overview confirmation
  5. Spree Integrator orders updated since (timestamp) updated orders send shipment

    to fulfillment detect ready shipments confirmation Overview Shipwire Endpoint
  6. Spree Integrator poll for shipments (bookmark) new shipments Shipwire Endpoint

    update shipment state + tracking confirmation Overview
  7. Overview Spree Integrator poll for shipments (bookmark) new shipments Shipwire

    Endpoint update shipment state + tracking confirmation
  8. Overview Integrator poll for shipments (bookmark) new shipments Shipwire Endpoint

    + tracking Mandrill Endpoint send shipment notification email
  9. Overview Integrator poll for shipments (bookmark) new shipments Shipwire Endpoint

    + tracking Mandrill Endpoint send shipment notification email confirmation
  10. order:new send order confirmation email capture payment (if stock available)

    send shipment to fulfillment update analytics with order
  11. order:new send order confirmation email capture payment (if stock available)

    send shipment to fulfillment update analytics with order update accounts with order
  12. order:new send order confirmation email capture payment (if stock available)

    send shipment to fulfillment update analytics with order update accounts with order subscribe customer to mailing list
  13. order:new send order confirmation email capture payment (if stock available)

    send shipment to fulfillment update analytics with order update accounts with order subscribe customer to mailing list watch for fulfillment notifications
  14. order:new send order confirmation email capture payment (if stock available)

    send shipment to fulfillment update analytics with order update accounts with order subscribe customer to mailing list watch for fulfillment notifications decrease stock levels
  15. order:new send order confirmation email capture payment (if stock available)

    send shipment to fulfillment update analytics with order update accounts with order subscribe customer to mailing list watch for fulfillment notifications send shipment notification email decrease stock levels
  16. order:new 1 x order:new order:new - Mandrill - NetSuite -

    MixPanel Message Flow MSG Incoming Queue Router Consumer Registry Accepted Queue
  17. order:new 1 x order:new order:new - Mandrill - NetSuite -

    MixPanel 3 x order:new - Mandrill - NetSuite - MixPanel Message Flow MSG Incoming Queue Router Consumer Registry Accepted Queue
  18. Accepted Queue Dispatcher Mandrill order:new 1 MixPanel order:new 3 NetSuite

    order:new 2 3 x order:new - Mandrill - NetSuite - MixPanel Message Flow
  19. Accepted Queue Dispatcher Mandrill order:new 1 MixPanel order:new 3 NetSuite

    order:new 2 200: success 200: success 3 x order:new - Mandrill - NetSuite - MixPanel Message Flow
  20. Accepted Queue Dispatcher Mandrill order:new 1 MixPanel order:new 3 NetSuite

    order:new 2 200: success 200: success 3 x order:new - Mandrill - NetSuite - MixPanel Message Flow
  21. Accepted Queue Dispatcher Mandrill order:new 1 MixPanel order:new 3 NetSuite

    order:new 2 200: success 200: success 500: fail! 3 x order:new - Mandrill - NetSuite - MixPanel Message Flow
  22. Accepted Queue Dispatcher NetSuite order:new 2 500: fail! 1 x

    order:new - NetSuite order:new 2 500: fail! order:new 2 500: fail! order:new 2 200: success Message Flow
  23. Accepted Queue Dispatcher NetSuite order:new 2 500: fail! 1 x

    order:new - NetSuite order:new 2 500: fail! order:new 2 500: fail! order:new 2 200: success Message Flow
  24. order Have we seen this order before? yes Generate diff

    no: push message order:new Order has really changed?
  25. order Have we seen this order before? yes Generate diff

    yes: push message order:updated no: push message order:new Order has really changed?
  26. Why pull not push? •Less dependencies on the client side.

    •We can poll new collections easier.
  27. Why pull not push? •Less dependencies on the client side.

    •We can poll new collections easier. •We control the rate of flow.
  28. Why pull not push? •Less dependencies on the client side.

    •We can poll new collections easier. •We control the rate of flow. •Nobody has needed push yet.
  29. Order V1 parent: null Order V2 parent: V1 update new

    send order confirmation email Versioning
  30. Order V1 parent: null Order V2 parent: V1 update new

    send order confirmation email capture payment Versioning
  31. Order V1 parent: null Order V2 parent: V1 update new

    Order V3 parent: V2 update send order confirmation email capture payment Versioning
  32. Order V1 parent: null Order V2 parent: V1 update new

    Order V3 parent: V2 update send order confirmation email capture payment send shipment to fulfillment Versioning
  33. Order V1 parent: null Order V2 parent: V1 update new

    Order V3 parent: V2 update update Order V4 parent: V3 send order confirmation email capture payment send shipment to fulfillment Versioning
  34. Order V1 parent: null Order V2 parent: V1 update new

    Order V3 parent: V2 update update Order V4 parent: V3 send order confirmation email capture payment send shipment to fulfillment send shipment confirmation Versioning
  35. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { 5 "id": 15913, 6 "amount": "129.57", 7 "state": "pending", 8 "payment_method": { 9 "id": 931422127, 10 "name": "Credit Card", 11 "environment": "production" 12 } 13 } 14 } 15 } Message: Overview
  36. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { 5 "id": 15913, 6 "amount": "129.57", 7 "state": "pending", 8 "payment_method": { 9 "id": 931422127, 10 "name": "Credit Card", 11 "environment": "production" 12 } 13 } 14 } 15 } Message: Overview
  37. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { 5 "id": 15913, 6 "amount": "129.57", 7 "state": "pending", 8 "payment_method": { 9 "id": 931422127, 10 "name": "Credit Card", 11 "environment": "production" 12 } 13 } 14 } 15 } Message: Overview
  38. Order New order:new Order Updated order:updated Order Cancelled order:cancelled Shipment

    Confirmation shipment:confirmation Shipment Ready shipment:ready Shipment Rejected shipment:rejected
  39. Order New order:new Order Updated order:updated Order Cancelled order:cancelled Payment

    Ready payment:ready Shipment Confirmation shipment:confirmation Shipment Ready shipment:ready Payment Captured payment:captured Shipment Rejected shipment:rejected Payment Declined payment:declined
  40. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { ... }, 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 ... 9 }, 10 "current": { 11 "number": "R123123123", 12 ... 13 }, 14 } 15 } 16 } Message: Payload
  41. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { ... }, 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 ... 9 }, 10 "current": { 11 "number": "R123123123", 12 ... 13 }, 14 } 15 } 16 } Message: Payload
  42. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { ... }, 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 ... 9 }, 10 "current": { 11 "number": "R123123123", 12 ... 13 }, 14 } 15 } 16 } Message: Payload
  43. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { ... }, 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 ... 9 }, 10 "current": { 11 "number": "R123123123", 12 ... 13 }, 14 } 15 } 16 } Message: Payload
  44. 1 { 2 "message": "payment:ready", 3 "payload": { 4 "payment":

    { ... }, 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 ... 9 }, 10 "current": { 11 "number": "R123123123", 12 ... 13 }, 14 } 15 } 16 } Message: Payload
  45. Message: message_id 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "payment:ready",

    4 "payload": { 5 "payment": { ... }, 6 "order": { 7 "actual": { 8 "number": "R123123123", 9 ... 10 }, 11 "current": { 12 "number": "R123123123", 13 ... 14 }, 15 } 16 } ...
  46. Message: message_id 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "payment:ready",

    4 "payload": { 5 "payment": { ... }, 6 "order": { 7 "actual": { 8 "number": "R123123123", 9 ... 10 }, 11 "current": { 12 "number": "R123123123", 13 ... 14 }, 15 } 16 } ...
  47. Endpoint: Sinatra 1 require 'sinatra' 2 require 'sinatra/json' 3 4

    class SimpleEndpoint < Sinatra::Base 5 helpers Sinatra::JSON 6 7 post '/' do 8 message = JSON.parse(request.body.read) 9 10 json 'message_id' => message['message_id'] 11 end 12 end
  48. Endpoint: Sinatra 1 require 'sinatra' 2 require 'sinatra/json' 3 4

    class SimpleEndpoint < Sinatra::Base 5 helpers Sinatra::JSON 6 7 post '/' do 8 message = JSON.parse(request.body.read) 9 10 json 'message_id' => message['message_id'] 11 end 12 end
  49. Endpoint: Sinatra 1 require 'sinatra' 2 require 'sinatra/json' 3 4

    class SimpleEndpoint < Sinatra::Base 5 helpers Sinatra::JSON 6 7 post '/' do 8 message = JSON.parse(request.body.read) 9 10 json 'message_id' => message['message_id'] 11 end 12 end <- DO INTERESTING THINGS HERE
  50. Endpoint: Node.js 1 var express = require('express'); 2 var app

    = express(); 3 4 app.use(express.bodyParser()); 5 6 app.post('/', function(req, res){ 7 var id = req.body['message_id']; 8 res.setHeader('Content-Type', 'application/json'); 9 res.end(JSON.stringify({ "mesage_id": id})); 10 }); 11 app.listen(3000);
  51. Endpoint: Node.js 1 var express = require('express'); 2 var app

    = express(); 3 4 app.use(express.bodyParser()); 5 6 app.post('/', function(req, res){ 7 var id = req.body['message_id']; 8 res.setHeader('Content-Type', 'application/json'); 9 res.end(JSON.stringify({ "mesage_id": id})); 10 }); 11 app.listen(3000);
  52. 1 <?php 2 $message = json_decode(file_get_contents('php://input')); 3 4 $response =

    array( 5 'message_id' => $message->message_id 6 ); 7 8 header('Content-type: application/json'); 9 echo json_encode($response); 10 ?> Endpoint: PHP
  53. Example: Endpoint Base 1 class SimpleEndpoint < EndpointBase 2 post

    '/' do 3 process_result 200, {'message_id' => @message['message_id'] } 4 end 5 end
  54. Endpoint: Sinatra 1 post '/follow' do 2 Twitter.configure do |c|

    3 c.consumer_key = 'iQ55...' 4 c.consumer_secret = 'YQoQ...' 5 c.oauth_token = '8213...' 6 c.oauth_token_secret = '6Rwn...' 7 end 8 9 Twitter.follow(@msg['payload']['order']['actual']['twitter']) 10 11 process_result 200, { 'message_id' => @msg[:message_id], 12 'following' => Twitter.following.map &:name } 13 end
  55. Endpoint: Sinatra 1 post '/follow' do 2 Twitter.configure do |c|

    3 c.consumer_key = 'iQ55...' 4 c.consumer_secret = 'YQoQ...' 5 c.oauth_token = '8213...' 6 c.oauth_token_secret = '6Rwn...' 7 end 8 9 Twitter.follow(@msg['payload']['order']['actual']['twitter']) 10 11 process_result 200, { 'message_id' => @msg[:message_id], 12 'following' => Twitter.following.map &:name } 13 end
  56. Endpoint: Sinatra 1 post '/follow' do 2 Twitter.configure do |c|

    3 c.consumer_key = 'iQ55...' 4 c.consumer_secret = 'YQoQ...' 5 c.oauth_token = '8213...' 6 c.oauth_token_secret = '6Rwn...' 7 end 8 9 Twitter.follow(@msg['payload']['order']['actual']['twitter']) 10 11 process_result 200, { 'message_id' => @msg[:message_id], 12 'following' => Twitter.following.map &:name } 13 end
  57. Endpoint: Sinatra 1 post '/follow' do 2 Twitter.configure do |c|

    3 c.consumer_key = 'iQ55...' 4 c.consumer_secret = 'YQoQ...' 5 c.oauth_token = '8213...' 6 c.oauth_token_secret = '6Rwn...' 7 end 8 9 Twitter.follow(@msg['payload']['order']['actual']['twitter']) 10 11 process_result 200, { 'message_id' => @msg[:message_id], 12 'following' => Twitter.following.map &:name } 13 end
  58. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "order:new", 4 "payload":

    { ... }, 5 "parameters": [ 6 { 7 "name": "twitter.consumer_key", 8 "value": "iQ55k..." 9 }, 10 { 11 "name": "twitter.consumer_secret", 12 "value": "YQoQS..." 13 } 14 ] 15 } Message: Parameters
  59. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "order:new", 4 "payload":

    { ... }, 5 "parameters": [ 6 { 7 "name": "twitter.consumer_key", 8 "value": "iQ55k..." 9 }, 10 { 11 "name": "twitter.consumer_secret", 12 "value": "YQoQS..." 13 } 14 ] 15 } Message: Parameters
  60. Endpoint: Sinatra 1 post '/follow' do 2 Twitter.configure do |c|

    3 c.consumer_key = @config['twitter.consumer_key'] 4 c.consumer_secret = @config['twitter.consumer_secret'] 5 c.oauth_token = @config['twitter.oauth_token'] 6 c.oauth_token_secret = @config['twitter.oauth_token_secret'] 7 end 8 9 Twitter.follow(@msg['payload']['order']['actual']['twitter']) 10 11 process_result 200, { 'message_id' => @msg[:message_id], 12 'following' => Twitter.following.map &:name } 13 end
  61. Endpoint: Sinatra 1 post '/follow' do 2 Twitter.configure do |c|

    3 c.consumer_key = @config['twitter.consumer_key'] 4 c.consumer_secret = @config['twitter.consumer_secret'] 5 c.oauth_token = @config['twitter.oauth_token'] 6 c.oauth_token_secret = @config['twitter.oauth_token_secret'] 7 end 8 9 Twitter.follow(@msg['payload']['order']['actual']['twitter']) 10 11 process_result 200, { 'message_id' => @msg[:message_id], 12 'following' => Twitter.following.map &:name } 13 end
  62. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "parameters": [ 4 {

    5 "name": "last_follower", 6 "value": "@briandq" 7 } 8 ] 9 } Message: Parameters Response
  63. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "events": [ 4 {

    5 "code": 200 6 } 12 ] 13 } Response: Events
  64. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "events": [ 4 {

    5 "code": 200 6 } 12 ] 13 } Response: Events
  65. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "events": [ 4 {

    5 "code": 200, 6 "username": "@briandq" 7 } 8 ] 9 } Response: Events
  66. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "events": [ { "code":

    200 } ], 4 "messages": [ 5 { 6 "message": "customer:followed", 7 "payload": { 8 “username": "@briandq", 9 } 10 } 11 ] 12 } Response: Messages
  67. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "events": [ { "code":

    200 } ], 4 "messages": [ 5 { 6 "message": "customer:followed", 7 "payload": { 8 “username": "@briandq", 9 } 10 } 11 ] 12 } Response: Messages
  68. Dispatcher Endpoint { reason: X } HTTP: 500 POST JSON

    { ... } order:new order:new ⨯ attempts: 1 attempt_at: 14:02
  69. Dispatcher Endpoint { reason: Y } HTTP: 500 POST JSON

    { ... } order:new order:new ⨯ attempts: 2 attempt_at: 14:08
  70. 1 post '/follow' do 2 Twitter.configure do |config| 3 config.consumer_key

    = 'iQ55...' 4 config.consumer_secret = 'YQoQ...' 5 config.oauth_token = '8213...' 6 config.oauth_token_secret = '6Rwn...' 7 end 8 9 begin 10 Twitter.follow(@msg['payload']['order']['twitter']) 11 12 process_result 200, { 'message_id' => @msg[:message_id], 13 'following' => Twitter.following.map &:name } 14 rescue 15 process_result 500, 16 { 'username' => @msg['payload']['order']['twitter'] } 17 end 18 19 end Endpoint: Sinatra
  71. 1 post '/follow' do 2 Twitter.configure do |config| 3 config.consumer_key

    = 'iQ55...' 4 config.consumer_secret = 'YQoQ...' 5 config.oauth_token = '8213...' 6 config.oauth_token_secret = '6Rwn...' 7 end 8 9 begin 10 Twitter.follow(@msg['payload']['order']['twitter']) 11 12 process_result 200, { 'message_id' => @msg[:message_id], 13 'following' => Twitter.following.map &:name } 14 rescue 15 process_result 500, 16 { 'username' => @msg['payload']['order']['twitter'] } 17 end 18 19 end Endpoint: Sinatra
  72. 1 { 2 "message_id": "518783ac7575e46507000001", 3 "delay ": 600, 4

    "update_url": "http://example.com/some/path?value=x" 5 } Response: Delayed
  73. Dispatcher Endpoint { delay: n } HTTP: 200 POST JSON

    { ... } order:new --------------- ✓
  74. Dispatcher Endpoint { delay: n } HTTP: 200 POST JSON

    { ... } order:new --------------- ✓ push remote:poll
  75. Filter: Example 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "order:new",

    4 "payload": { 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 "total": "199.99", 9 "twitter": "@briandq", 10 "line_items": [ ... ] 11 } 12 } 13 }
  76. Filter: Example 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "order:new",

    4 "payload": { 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 "total": "199.99", 9 "twitter": "@briandq", 10 "line_items": [ ... ] 11 } 12 } 13 }
  77. Filter: Example 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "order:new",

    4 "payload": { 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 "total": "199.99", 9 "twitter": "@briandq", 10 "line_items": [ ... ] 11 } 12 } 13 } Path: payload.order.actual.twitter
  78. Filter: Example 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "order:new",

    4 "payload": { 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 "total": "199.99", 9 "twitter": "@briandq", 10 "line_items": [ ... ] 11 } 12 } 13 } Path: payload.order.actual.twitter Operator: is_not
  79. Filter: Example 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message": "order:new",

    4 "payload": { 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 "total": "199.99", 9 "twitter": "@briandq", 10 "line_items": [ ... ] 11 } 12 } 13 } Path: payload.order.actual.twitter Operator: is_not Value: nil
  80. Event: Key Example 1 { 2 "message_id": "518783ac7575e46507000001", 3 "message":

    "order:new", 4 "payload": { 5 "order": { 6 "actual": { 7 "number": "R123123123", 8 "total": "199.99", 9 "email": "[email protected]", 10 "line_items": [ ... ] 11 } 12 } 13 } Name: order_number Path: payload.order.actual.number