fighting fat models (Богдан Гусев)

63
Fighting with fat models Bogdan Gusiev

Upload: fwdays

Post on 13-Apr-2017

219 views

Category:

Engineering


2 download

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 (Богдан Гусев)