Successfully reported this slideshow.
Your SlideShare is downloading. ×

Fighting Fat Models (Богдан Гусев)

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
Ali CV
Ali CV
Loading in …3
×

Check these out next

1 of 63 Ad

Fighting Fat Models (Богдан Гусев)

Download to read offline

Fat Models - проблема, которая ожидает 90% хорошо развивающихся проектов. Ее причина очевидна: среди Fat Models Fat Views and Fat Controllers - модели являются наилучшим вариантом. Но что делать если вы уже дошли до предела?

Fat Models - проблема, которая ожидает 90% хорошо развивающихся проектов. Ее причина очевидна: среди Fat Models Fat Views and Fat Controllers - модели являются наилучшим вариантом. Но что делать если вы уже дошли до предела?

Advertisement
Advertisement

More Related Content

Viewers also liked (20)

Similar to Fighting Fat Models (Богдан Гусев) (20)

Advertisement

More from Fwdays (20)

Recently uploaded (20)

Advertisement

Fighting Fat Models (Богдан Гусев)

  1. 1. Fighting with fat models Bogdan Gusiev
  2. 2. Bogdan G. is 9 years in IT 6 years with Ruby and Rails Long Run Rails Contributor
  3. 3. Some of my gems http://github.com/bogdan Datagrid js-routes accepts_values_for furi
  4. 4. My Blog http://gusiev.com
  5. 5. http://talkable.com A small startup is a great place to move from middle to senior and above
  6. 6. Fat Models Why the problem appears? All business logic code goes to model by default.
  7. 7. In the MVC: Why it should not be in controller or view? Because they are hard to: test maintain reuse
  8. 8. A definition of being fat 1000 Lines of code But it depends on: Docs Whitespace Comments
  9. 9. $ wc -l app/models/* | sort -n | tail 532 app/models/incentive.rb 540 app/models/person.rb 544 app/models/visitor_offer.rb 550 app/models/reward.rb 571 app/models/web_hook.rb 786 app/models/site.rb 790 app/models/referral.rb 943 app/models/campaign.rb 998 app/models/offer.rb 14924 total Existing techniques
  10. 10. Existing techniques Services Separated utility class Concerns Modules that get included to models Presenters/Wrappers Classes that wrap existing model to plug new methods What do we expect?
  11. 11. Standard: Reusable code Easy to test Good API Advanced: Effective data model MORE features per second Data Safety Good API
  12. 12. Good API Is a user connected to facebook? user.connected_to_facebook? # OR FacebookService.connected_to_facebook?(user) # OR FacebookWrapper.new(user) .connected_to_facebook? The need of Services
  13. 13. When amount of utils that support Model goes higher extract them to service is good idea. Move class methods between files is cheap
  14. 14. # move (1) User.create_from_facebook # to (2) UserService.create_from_facebook # or (3) FacebookService.create_user Organise services by process rather than object they operate on Otherwise at some moment UserService would not be enough
  15. 15. Otherwise at some moment UserService would not be enough The problem of services Service is separated utility class. module CommentService
  16. 16. module CommentService def self.create(attributes) comment = Comment.create!(attributes) deliver_notification(comment) end end "Я знаю откуда что берется" Services don't provide default behavior
  17. 17. provide default behavior The Need of Default Behavior Object should encapsulate behavior: Data Rules Set of rules that a model should fit at the programming
  18. 18. Set of rules that a model should fit at the programming level Ex: A comment should have an author Business Rules Set of rules that a model should fit to exist in the real world Ex: A comment should deliver an email notification What is a model? The model is an imitation of real object that reflects some it's behaviors
  19. 19. that we are focused on. Wikipedia Model is a best place for default behaviour MVC authors meant that
  20. 20. Implementation Using built-in Rails features: ActiveRecord::Callbacks
  21. 21. Hooks in models We create default behavior and our data is safe. Example: Comment can not be created without notification. class Comment < AR::Base after_create :send_notification
  22. 22. end API comparison Comment.create # or CommentService.create
  23. 23. Successful Projects tend to do one thing in many different ways rather than a lot of things
  24. 24. Comment on a web site Comment in native mobile iOS app Comment in native mobile Android app Comment by replying to an email letter Automatically generate comments
  25. 25. Team Growth Problem How would you deliver a knowledge that comment should be made like this to 10 people? CommentService.create(...)
  26. 26. Reimplement other person's API has more wisdom than invent new one. Comment.create(...)
  27. 27. Edge cases In all cases data created in regular way In one edge cases special rules applied
  28. 28. Service with options module CommentService def self.create( attrs, skip_notification = false) end
  29. 29. Default behavior and edge cases Hey model, create my comment. Ok Hey model, why did you send the notification? Because you didn't say you don't need it
  30. 30. Because you didn't say you don't need it Hey model, create model without notification Ok Support parameter in model class Comment < AR::Base attr_accessor :skip_comment_notification after_create do unless self.skip_comment_notification send_notification end end end
  31. 31. end #skip_comment_notification is used only in edge cases. Default Behaviour is hard to make But it solves communication problems that will only increase over time
  32. 32. What is the difference? FacebookService.register_user(...) Comment.after_create :send_notification Business rules: User could be registered from facebook Comment should send an email notification
  33. 33. Model stands for should Service stands for could Please do not confuse should with must
  34. 34. Where are presenters? UserPresenter.new(user) # OR class User include UserPresenter end Trade an API for less methods in object
  35. 35. More effective presenters?
  36. 36. Example of Service implementation with wrapper More example at ActiveRecord source code class StiTools def self.run(from_model, to_model) new(from_model, to_model).perform end private def initialize(from_model, to_model) def perform shift_id_info
  37. 37. Datagrid Gem Example of collection wrapper https://github.com/bogdan/datagrid UsersGrid.new( last_request: Date.today, created_at: 1.month.ago..Time.now) class UsersGrid scope { User } filter(:created_at, :date, range: true) filter(:last_request_at, :datetime, range: true
  38. 38. Wrapping Data https://github.com/bogdan/furi u = Furi.parse( "http://bogdan.github.com/index.html") u.subdomain # => 'bogdan' u.extension # => 'html' u.ssl? # => false module Furi def self.parse(string)
  39. 39. Service usage is inconvinient because of validation Customer.has_many :purchases Purchase.has_many :ordered_items OrderItem.belongs_to :product ManualOrder.ancestors.include?( ActiveRecord::Base) # => false order = ManualOrder.new(attributes) if order.valid? order.save_all_those_records_at_once!
  40. 40. Wrappers/Presenters Very specific use Wrapper around collection Parsing serialised object Under-the-hood class inside a service Service usage is inconvinient
  41. 41. The model is still fat. What to do? Use Concerns
  42. 42. Use Concerns class Comment < AR::Base include CommentNotification include FeedActivityGeneration include Archivable end Rails default: app/models/concerns/* Attention!
  43. 43. Attention! People with high pressure or propensity to suicide Next slide can be considered offensive to your religion Single Responsibility Principle
  44. 44. SUCKS The proof follows There is no a single thing in the universe that follows the SRP
  45. 45. in the universe that follows the SRP class Proton include Gravitation include ElectroMagnetism include StrongNuclearForce include WeekNuclearForce end Why man made things should?
  46. 46. Why man made things should? The world is unreasonably complext to follow SRP How a model that suppose to simulate those things can have a single responsibility?
  47. 47. It can't! Model Concerns are unavoidable if you want to have a good model
  48. 48. if you want to have a good model Concerns are Vertical slicing Unlike MVC which is horizontal slicing.
  49. 49. Split model into Concerns class User < AR::Base
  50. 50. class User < AR::Base include FacebookProfile end # Hybrid Concern that provides # instance and class methods module FacebookProfile has_one :facebook_profile # simplified def connected_to_facebook? def self.register_from_facebook(attributes) Ex.1 User + Facebook has_one :facebook_profile => Model #register_user_from_facebook => Service
  51. 51. #register_user_from_facebook => Service connect_facebook_profile => Service connected_to_facebook? => Model Every user should know if it is connected to facebook or not Ex.2 Deliver comment notification Comment #send_notification => Model Default Behaviour Even if exceptions exist
  52. 52. Even if exceptions exist Basic application architecture View Controller Model
  53. 53. Model Services Presenters Concern Concern Concern Concerns Base Attributes Associations has_one
  54. 54. has_one has_many has_and_belongs_to_many But rarely Libraries using Concerns ActiveRecord ActiveModel Devise Datagrid
  55. 55. Datagrid Summary
  56. 56. Inject Service between Model and Controller if you need them
  57. 57. Could? => Service Should? => Model
  58. 58. SRP is a misleading principle It should not inhibit you from having a Better Application Model
  59. 59. Fat models => Thin Concerns
  60. 60. Reimplement other person's API has more wisdom than invent new one.
  61. 61. Presenters are pretty specific Use them in Wrapping the collection "private" class Service usage is inconvenient
  62. 62. The End Thanks for your time http://gusiev.com https://github.com/bogdan

×