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

DCI with Ruby

Sho Kusano
December 25, 2012

DCI with Ruby

DCI の使い方を考える

Sho Kusano

December 25, 2012
Tweet

More Decks by Sho Kusano

Other Decks in Programming

Transcript

  1. DCI with
    Ruby
    わかった気になる DCI

    View Slide

  2. こんにちは!
    ロージーです!

    View Slide

  3. Dec
    21 Dec
    22 Dec
    23
    今日はこの4日間で僕が学んだ DCI の話をします
    (クリスマスなんて知ったこっちゃねぇ)
    Dec
    24

    View Slide

  4. 1. What is DCI

    View Slide

  5. Data
    Context
    Interaction
    データ
    文脈
    挙動

    View Slide

  6. View Slide

  7. 全然わかんない!
    教えて!マスター=センセイ!

    View Slide

  8. Ruby on Rails: The Bad Parts
    https://vimeo.com/50568777

    View Slide

  9. DCI and the application builds
    our mental models
    https://vimeo.com/51957143

    View Slide

  10. The Right Way to Code DCI in Ruby
    http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby

    View Slide

  11. and Github Repositories
    Alias DCI
    https://github.com/rebo/alias_dci
    DCI-Sample
    https://github.com/vsavkin/DCI-Sample
    DCI
    https://github.com/rubyworks/dci

    View Slide

  12. Data
    ex: User
    Behavior
    ex: Customer
    Story
    ex: Buy the Item
    Data Context Interaction

    View Slide

  13. 2. Where is Logics

    View Slide

  14. 『ユーザーが商品を買う』
    というロジックはどこにあるべきか論

    View Slide

  15. 例をご用意しました
    『ユーザーがアイテムを買う』
    User
    has_many :purchases
    Purchase
    belongs_to :user
    belongs_to :item
    Item
    has_many :purchases

    View Slide

  16. Case 1: Model
    class User
    def buy_item(item)
    self.purchases.create(item: item)
    end
    end
    https://gist.github.com/285ec33e6cfceb0ba0cf#file-case_1_model-rb

    View Slide

  17. Case 2: Controller
    class PurchasesController
    def create
    item = Item.find(params[:item_id])
    current_user.purchases.create(item: item)
    end
    end
    https://gist.github.com/285ec33e6cfceb0ba0cf#file-case_2_controller-rb

    View Slide

  18. Case 3: DCI
    class BuyItemContext
    def initialize(user, item)
    @user = user
    @item = item
    @user.extend(Customer)
    end
    def process
    @user.buy(item)
    end
    end
    module Customer
    def buy(item)
    self.purchases.create(item: item)
    end
    end
    https://gist.github.com/285ec33e6cfceb0ba0cf#file-case_3_dci-rb

    View Slide

  19. このサイズだと判断が難しいと思うけれど
    とりあえずコントローラにあるのはヤバそうな気配がする

    View Slide

  20. User#buy_item
    は、正しいのか

    View Slide

  21. Wikipedia 曰く
    Model View Controllerページ
    Model
    ͦͷΞϓϦέʔγϣϯ͕ѻ͏ྖҬͷσʔλͱखଓ͖ʢϏδωεϩδοΫ -
    γϣοϐϯάͷ߹ܭֹ΍ૹྉΛܭࢉ͢ΔͳͲʣΛදݱ͢ΔཁૉͰ͋Δɻ·
    ͨɺσʔλͷมߋΛviewʹ௨஌͢Δͷ΋modelͷ੹೚Ͱ͋Δʢmodelͷมߋ
    Λ௨஌͢ΔͷʹObserver ύλʔϯ͕༻͍ΒΕΔ͜ͱ΋͋Δʣɻ
    ଟ͘ͷΞϓϦέʔγϣϯͰ͸σʔλͷ֨ೲʹӬଓతͳهԱͷ࢓૊Έʢσʔ
    λϕʔεͳͲʣ͕࢖ΘΕ͍ͯΔɻMVCͷ֓೦Ͱ͸ɺσʔλͷʢUIҎ֎ͷʣ
    ೖग़ྗ͸औΓѻΘͳ͍ͷͰɺσʔλΞΫηε΋ຊདྷMVCͷ֓೦ͷൣᙝΛ௒
    ͑Δ΋ͷͰ͸͋Δ͕ɺ׶͍͑ͯ͑͹modelͷதʹӅṭ͞ΕΔͱߟ͑ΒΕΔɻ

    View Slide

  22. 正しいみたい!

    View Slide

  23. View Slide

  24. お疲れ様でした!
    まだ続きます

    View Slide

  25. 3. be Fat

    View Slide

  26. # Redmine - project management software
    # Copyright (C) 2006-2012 Jean-Philippe Lang
    #
    # This program is free software; you can redistribute it and/or
    # modify it under the terms of the GNU General Public License
    # as published by the Free Software Foundation; either version 2
    # of the License, or (at your option) any later version.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program; if not, write to the Free Software
    # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    require "digest/sha1"
    class User < Principal
    include Redmine::SafeAttributes
    # Different ways of displaying/sorting users
    USER_FORMATS = {
    :firstname_lastname => {
    :string => '#{firstname} #{lastname}',
    :order => %w(firstname lastname id),
    :setting_order => 1
    },
    :firstname_lastinitial => {
    :string => '#{firstname} #{lastname.to_s.chars.first}.',
    :order => %w(firstname lastname id),
    :setting_order => 2
    },
    :firstname => {
    :string => '#{firstname}',
    :order => %w(firstname id),
    :setting_order => 3
    },
    :lastname_firstname => {
    :string => '#{lastname} #{firstname}',
    :order => %w(lastname firstname id),
    :setting_order => 4
    },
    :lastname_coma_firstname => {
    :string => '#{lastname}, #{firstname}',
    :order => %w(lastname firstname id),
    Redmine
    app/models/user.rb

    View Slide

  27. Token.delete_all ['user_id = ?', id]
    Watcher.delete_all ['user_id = ?', id]
    WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
    WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
    end
    # Return password digest
    def self.hash_password(clear_password)
    Digest::SHA1.hexdigest(clear_password || "")
    end
    # Returns a 128bits random salt as a hex string (32 chars long)
    def self.generate_salt
    Redmine::Utils.random_hex(16)
    end
    end
    class AnonymousUser < User
    validate :validate_anonymous_uniqueness, :on => :create
    def validate_anonymous_uniqueness
    # There should be only one AnonymousUser in the database
    errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
    end
    def available_custom_fields
    []
    end
    # Overrides a few properties
    def logged?; false end
    def admin; false end
    def name(*args); I18n.t(:label_user_anonymous) end
    def mail; nil end
    def time_zone; nil end
    def rss_key; nil end
    def pref
    UserPreference.new(:user => self)
    end
    # Anonymous user can not be destroyed
    def destroy
    false
    end
    end
    Redmine
    app/models/user.rb
    706 lines

    View Slide

  28. Token.delete_all ['user_id = ?', id]
    Watcher.delete_all ['user_id = ?', id]
    WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
    WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
    end
    # Return password digest
    def self.hash_password(clear_password)
    Digest::SHA1.hexdigest(clear_password || "")
    end
    # Returns a 128bits random salt as a hex string (32 chars long)
    def self.generate_salt
    Redmine::Utils.random_hex(16)
    end
    end
    class AnonymousUser < User
    validate :validate_anonymous_uniqueness, :on => :create
    def validate_anonymous_uniqueness
    # There should be only one AnonymousUser in the database
    errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
    end
    def available_custom_fields
    []
    end
    # Overrides a few properties
    def logged?; false end
    def admin; false end
    def name(*args); I18n.t(:label_user_anonymous) end
    def mail; nil end
    def time_zone; nil end
    def rss_key; nil end
    def pref
    UserPreference.new(:user => self)
    end
    # Anonymous user can not be destroyed
    def destroy
    false
    end
    end
    Redmine
    app/models/user.rb
    706 lines

    View Slide

  29. コードがすごく汚いとかではない
    (むしろいい方だと思う)

    View Slide

  30. じゃあなんで太るのか

    View Slide

  31. たいてい参照されるケースが多すぎる
    (User がやたら太るのはその理由)

    View Slide

  32. 90%のユーザーストーリーで必要のない
    振る舞いを抱え続けている

    View Slide

  33. 常に必要のない振る舞いは
    適宜与えてあげればいい

    View Slide

  34. それって、DCIでは?

    View Slide

  35. 4. Rails with DCI

    View Slide

  36. 現状の Rails で DCI をするには
    1. dci gem を頑張って使う(微妙)
    2. DCI-Sample みたいに自分で頑張る(辛い)
    3. user.extend(Customer) しまくる(妥当)
    4. ESM に gem 化をお願いする(どうなん)

    View Slide

  37. AOP
    じゃダメなの?

    View Slide

  38. 僕の理解が間違ってなければ AOP って
    #alias_method_chain
    なのでは

    View Slide

  39. もう自作したほうが早いのでは?

    View Slide

  40. Dicer
    https://github.com/rosylilly/dicer
    作ってみた

    View Slide

  41. WIP
    まだ実用に耐えない
    僕にお酒を飲ませると実装が進みます!

    View Slide

  42. 5. the Future

    View Slide

  43. Rails 4
    ちょっと見たけど、
    こういう問題への解決策の提示はなかった

    View Slide

  44. このままだと、主要モデルが太り続けるのは
    間違いない

    View Slide

  45. こんだけ喋ったけど
    僕もまだいい方法が思いつかない

    View Slide

  46. モデルダイエット勉強会誰かやりませんか><
    (僕 DCI の勉強進めて DCI の話します)

    View Slide

  47. 応質
    答疑
    Question
    https://speakerdeck.com/rosylilly/dci-with-ruby

    View Slide