Top Banner
Fighting with fat models Bogdan Gusiev
63

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

Apr 13, 2017

Download

Engineering

fwdays
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Fighting Fat Models (Богдан Гусев)

FightingwithfatmodelsBogdanGusiev

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

BogdanG.

is9yearsinIT6yearswithRubyandRails

LongRunRailsContributor

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

Someofmygemshttp://github.com/bogdan

Datagridjs-routesaccepts_values_forfuri

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

MyBlog

http://gusiev.com

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

http://talkable.com

Asmallstartupisagreatplacetomovefrommiddletoseniorandabove

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

FatModelsWhytheproblemappears?

Allbusinesslogiccodegoestomodelbydefault.

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

IntheMVC:Whyitshouldnotbeincontrollerorview?

Becausetheyarehardto:

testmaintainreuse

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

Adefinitionofbeingfat

1000LinesofcodeButitdependson:

DocsWhitespaceComments

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

$ 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

Existingtechniques

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

Existingtechniques

ServicesSeparatedutilityclass

ConcernsModulesthatgetincludedtomodels

Presenters/WrappersClassesthatwrapexistingmodeltoplugnewmethods

Whatdoweexpect?

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

Standard:ReusablecodeEasytotestGoodAPI

Advanced:EffectivedatamodelMOREfeaturespersecondDataSafety

GoodAPI

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

GoodAPIIsauserconnectedtofacebook?

user.connected_to_facebook?# ORFacebookService.connected_to_facebook?(user)# ORFacebookWrapper.new(user) .connected_to_facebook?

TheneedofServices

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

WhenamountofutilsthatsupportModelgoeshigher

extractthemtoserviceisgoodidea.

Moveclassmethodsbetweenfilesischeap

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

# move(1) User.create_from_facebook# to(2) UserService.create_from_facebook# or(3) FacebookService.create_user

Organiseservicesbyprocessratherthanobjecttheyoperateon

OtherwiseatsomemomentUserServicewouldnotbeenough

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

OtherwiseatsomemomentUserServicewouldnotbeenough

TheproblemofservicesServiceisseparatedutilityclass.

module CommentService

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

module CommentService def self.create(attributes) comment = Comment.create!(attributes) deliver_notification(comment) endend

"Язнаюоткудачтоберется"

Servicesdon't

providedefaultbehavior

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

providedefaultbehavior

TheNeedofDefaultBehaviorObjectshouldencapsulatebehavior:

DataRulesSetofrulesthatamodelshouldfitattheprogramming

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

SetofrulesthatamodelshouldfitattheprogramminglevelEx:Acommentshouldhaveanauthor

BusinessRulesSetofrulesthatamodelshouldfittoexistintherealworldEx:Acommentshoulddeliveranemailnotification

Whatisamodel?Themodelisanimitationofrealobject

thatreflectssomeit'sbehaviors

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

thatwearefocusedon.

Wikipedia

Modelisabestplacefordefaultbehaviour

MVCauthorsmeantthat

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

ImplementationUsingbuilt-inRailsfeatures:

ActiveRecord::Callbacks

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

HooksinmodelsWecreatedefaultbehaviorandourdataissafe.

Example:Commentcannotbecreatedwithoutnotification.

class Comment < AR::Base after_create :send_notification

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

end

APIcomparison

Comment.create# orCommentService.create

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

SuccessfulProjectstendtodo

onethinginmanydifferentwaysratherthanalotofthings

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

CommentonawebsiteCommentinnativemobileiOSappCommentinnativemobileAndroidappCommentbyreplyingtoanemailletterAutomaticallygeneratecomments

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

TeamGrowthProblemHowwouldyoudeliveraknowledgethatcommentshould

bemadelikethisto10people?

CommentService.create(...)

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

Reimplementotherperson'sAPIhasmorewisdomthaninventnewone.

Comment.create(...)

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

EdgecasesInallcasesdatacreatedinregularway

Inoneedgecasesspecialrulesapplied

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

Servicewithoptions

module CommentService def self.create( attrs, skip_notification = false)end

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

Defaultbehavior

andedgecasesHeymodel,createmycomment.

Ok

Heymodel,whydidyousendthenotification?Becauseyoudidn'tsayyoudon'tneedit

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

Becauseyoudidn'tsayyoudon'tneedit

Heymodel,createmodelwithoutnotificationOk

Supportparameterinmodelclass Comment < AR::Base attr_accessor :skip_comment_notification after_create do unless self.skip_comment_notification send_notification end endend

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

end

#skip_comment_notificationisusedonlyinedgecases.

DefaultBehaviourishardtomakeButitsolvescommunicationproblems

thatwillonlyincreaseovertime

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

Whatisthedifference?

FacebookService.register_user(...)

Comment.after_create :send_notification

Businessrules:UsercouldberegisteredfromfacebookCommentshouldsendanemailnotification

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

Modelstandsforshould

ServicestandsforcouldPleasedonotconfuseshouldwithmust

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

Wherearepresenters?

UserPresenter.new(user)# ORclass User include UserPresenterend

TradeanAPIforlessmethodsinobject

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

Moreeffectivepresenters?

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

ExampleofServiceimplementationwithwrapperMoreexampleatActiveRecordsourcecode

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

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

DatagridGemExampleofcollectionwrapper

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

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

WrappingDatahttps://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)

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

Serviceusageisinconvinientbecauseofvalidation

Customer.has_many :purchasesPurchase.has_many :ordered_itemsOrderItem.belongs_to :product

ManualOrder.ancestors.include?( ActiveRecord::Base) # => false

order = ManualOrder.new(attributes)if order.valid? order.save_all_those_records_at_once!

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

Wrappers/PresentersVeryspecificuse

WrapperaroundcollectionParsingserialisedobjectUnder-the-hoodclassinsideaserviceServiceusageisinconvinient

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

Themodelisstillfat.Whattodo?

UseConcerns

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

UseConcerns

class Comment < AR::Base include CommentNotification include FeedActivityGeneration include Archivableend

Railsdefault:app/models/concerns/*

Attention!

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

Attention!Peoplewithhighpressureorpropensitytosuicide

Nextslidecanbeconsideredoffensivetoyourreligion

SingleResponsibilityPrinciple

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

SUCKSTheprooffollows

ThereisnoasinglethingintheuniversethatfollowstheSRP

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

intheuniversethatfollowstheSRP

class Proton include Gravitation include ElectroMagnetism include StrongNuclearForce include WeekNuclearForceend

Whymanmadethingsshould?

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

Whymanmadethingsshould?TheworldisunreasonablycomplexttofollowSRP

Howamodelthatsupposetosimulatethosethingscanhaveasingleresponsibility?

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

Itcan't!

ModelConcernsareunavoidableifyouwanttohaveagoodmodel

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

ifyouwanttohaveagoodmodel

ConcernsareVerticalslicingUnlikeMVCwhichishorizontalslicing.

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

SplitmodelintoConcernsclass User < AR::Base

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

class User < AR::Base include FacebookProfileend

# Hybrid Concern that provides # instance and class methodsmodule FacebookProfile has_one :facebook_profile # simplified def connected_to_facebook? def self.register_from_facebook(attributes)

Ex.1User+Facebook

has_one :facebook_profile=>Model

#register_user_from_facebook=>Service

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

#register_user_from_facebook=>Serviceconnect_facebook_profile=>Serviceconnected_to_facebook?=>Model

Everyusershouldknowifitisconnectedtofacebookornot

Ex.2Delivercommentnotification

Comment#send_notification=>ModelDefaultBehaviourEvenifexceptionsexist

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

Evenifexceptionsexist

Basicapplicationarchitecture

View

Controller

Model

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

Model

Services Presenters

Concern Concern Concern

ConcernsBaseAttributesAssociations

has_one

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

has_onehas_manyhas_and_belongs_to_many

Butrarely

LibrariesusingConcerns

ActiveRecordActiveModelDeviseDatagrid

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

Datagrid

Summary

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

InjectServicebetweenModelandControllerifyouneedthem

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

Could?=>Service

Should?=>Model

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

SRPisamisleadingprincipleItshouldnotinhibityoufromhaving

aBetterApplicationModel

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

Fatmodels=>ThinConcerns

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

Reimplementotherperson'sAPIhasmorewisdomthaninventnewone.

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

Presentersareprettyspecific

Usethemin

Wrappingthecollection"private"classServiceusageisinconvenient

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

TheEndThanksforyourtime

http://gusiev.com

https://github.com/bogdan

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