43
WHERE DOES THE FAT GOES? UTILIZANDO FORM OBJECTS PARA SIMPLIFICAR SEU CÓDIGO

Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Embed Size (px)

DESCRIPTION

Como adicionar novas camadas à sua aplicação MVC para ajudar a manutenção e evolução do código.

Citation preview

Page 1: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

WHERE DOES THE FAT GOES?UTILIZANDO FORM OBJECTS PARA SIMPLIFICAR SEU CÓDIGO

Page 2: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Guilherme Cavalcanti

github.com/guiocavalcanti

Page 3: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código
Page 4: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

APLICAÇÕES MONOLÍTICAS

• Dependências compartilhadas

• Difícil de modificar

• Difícil de evoluirO Que São?

Page 5: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

NÃO VOU FALAR DE REST

• Mas o assunto ainda são aplicações monolíticas

• Outras estratégias para decompor

• Form Object

Page 6: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

ROTEIRO • O problema

• Sintomas

• Form objectsSobre O Que Vamos Falar?

Page 7: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

O Problema

Page 8: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

MV "F*" C

• Separação de concerns

• Baldes

• Views: apresentação

• Controller: Telefonista

• Model

• Persistência

• Domain logic

Page 9: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

M V C

Page 10: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Código Inicial

Page 11: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

APLICAÇÃO

• Criação de usuário

• Criação de loja

• Envio de emails

• Auditoria

E-Commerce

Page 12: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FAT CONTROLLER

• Inicialização

• Validação (humano)

• Database stuff

• Auditoria (IP)

• Email

• Rendering/redirect

   def  create          @user  =  User.new(user_params)          @store  =  @user.build_store(store_params)  !        captcha  =  CaptchaQuestion.find(captcha_id)          unless  captcha.valid?(captcha_answer)              flash[:error]  =  'Captcha  answer  is  invalid'              render  :new  and  return          end  !        ActiveRecord::Base.transaction  do              @user.save!              @store.save!              @user.store  =  @store          end  !        IpLogger.log(request.remote_ip)          SignupEmail.deliver(@user)  !        redirect_to  accounts_path  !    rescue  ActiveRecord::RecordInvalid          render  :new      end

Page 13: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

SLIM MODEL

• Validação

• Relacionamentos

class  User  <  ActiveRecord::Base      has_one  :store      validates  :name,  presence:  true  !    accepts_nested_attributes_for  :store  end

class  Store  <  ActiveRecord::Base      belongs_to  :user      validates  :url,  presence:  true  end

Page 14: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

PROBLEMAS

• E se precisássemos de mais de um controller para criar conta?

• Vários pontos de saída

• Acoplamento entre modelos (user e store)

Mas O Que Isso Significa?

Page 15: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

CODE SMELLSMartin FowlerRefactoring: Improving The Design Of Existing Code Ruby

Page 16: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

CODE SMELLS

• Divergent change

• This smell refers to making unrelated changes in the same location.

• Feature Envy

• a method that seems more interested in a class other than the one it actually is in

   def  create          @user  =  User.new(user_params)          @store  =  @user.build_store(store_params)  !        captcha  =  CaptchaQuestion.find(captcha_id)          unless  captcha.valid?(captcha_answer)              flash[:error]  =  'Captcha  answer  is  invalid'              render  :new  and  return          end  !        ActiveRecord::Base.transaction  do              @user.save!              @store.save!              @user.store  =  @store          end  !        IpLogger.log(request.remote_ip)          SignupEmail.deliver(@user)  !        redirect_to  accounts_path  !    rescue  ActiveRecord::RecordInvalid          render  :new      end

Page 17: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

SANDI METZ' RULES FOR DEVELOPERSRubyrogues.ComPoodr.Com

Page 18: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

SANDI RULES

• Classes can be no longer than one hundred lines of code.

• Methods can be no longer than five lines of code.

• Pass no more than four parameters into a method.

• Controllers can instantiate only one object.

   def  create          @user  =  User.new(user_params)          @store  =  @user.build_store(store_params)  !        captcha  =  CaptchaQuestion.find(captcha_id)          unless  captcha.valid?(captcha_answer)              flash[:error]  =  'Captcha  answer  is  invalid'              render  :new  and  return          end  !        ActiveRecord::Base.transaction  do              @user.save!              @store.save!              @user.store  =  @store          end  !        IpLogger.log(request.remote_ip)          SignupEmail.deliver(@user)  !        redirect_to  accounts_path  !    rescue  ActiveRecord::RecordInvalid          render  :new      end

Page 19: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Refactor I

Page 20: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Fat Model, Slim Controller

Page 21: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

SLIM CONTROLLER

• Inicialização

• Rendering/redirect    def  create          @user  =  User.new(params)          @user.remote_ip  =  request.remote_ip          @user.save  !        respond_with(@user,  location:  accounts_path)      end

Page 22: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

• Classes can be no longer than one hundred lines of code.

• Methods can be no longer than five lines of code.

• Pass no more than four parameters into a method.

• Controllers can instantiate only one object.

Page 23: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FAT MODEL

• Criação de Store

• Validação (humano)

• Database stuff

• Auditoria (IP)

• Email

   class  User  <  ActiveRecord::Base        attr_accessor  :remote_ip,  :captcha_id,  :captcha_answer  !        has_one  :store  !        validates  :name,  presence:  true          validate  :ensure_captcha_answered,  on:  :create          accepts_nested_attributes_for  :store  !        after_create  :deliver_email          after_create  :log_ip  !        protected  !        def  deliver_email              SignupEmail.deliver(@user)          end  !        def  log_ip              IpLogger.log(self.remote_ip)          end  !        def  ensure_captcha_answered              captcha  =  CaptchaQuestion.find(self.captcha_id)  !            unless  captcha.valid?(self.captcha_answer)                  errors.add(:captcha_answer,  :invalid)              end          end      end

Page 24: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

CODE SMELLS• Divergent change

• This smell refers to making unrelated changes in the same location.

• Feature Envy

• a method that seems more interested in a class other than the one it actually is in

• Inappropriate Intimacy

• too much intimate knowledge of another class or method's inner workings, inner data, etc.

   class  User  <  ActiveRecord::Base        attr_accessor  :remote_ip,  :captcha_id,  :captcha_answer  !        has_one  :store  !        validates  :name,  presence:  true          validate  :ensure_captcha_answered,  on:  :create          accepts_nested_attributes_for  :store  !        after_create  :deliver_email          after_create  :log_ip  !        protected  !        def  deliver_email              SignupEmail.deliver(@user)          end  !        def  log_ip              IpLogger.log(self.remote_ip)          end  !        def  ensure_captcha_answered              captcha  =  CaptchaQuestion.find(self.captcha_id)  !            unless  captcha.valid?(self.captcha_answer)                  errors.add(:captcha_answer,  :invalid)              end          end      end

Page 25: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

ACTIVE RECORD

• Precisa do ActiveRecord (specs)

• Acesso a métodos de baixo nível

• update_attributes

• A instância valida a sí mesma

• Difícil de testar

Regras De Negócio No Active Record?

Page 26: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Refactor II

Page 27: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Form Objects

Um Passo A Frente

Page 28: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

NOVOS BALDES• Novas camadas

• Melhor separação de concerns

• Por muito tempo o Rails não estimulava isso

Page 29: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FORM OBJECTS

• Delega persistência

• Realiza validações

• Dispara Callbacks

• app/forms

module  Form      extend  ActiveSupport::Concern      include  ActiveModel::Model      include  DelegateAccessors  !    included  do          define_model_callbacks  :persist      end  !    def  submit          return  false  unless  valid?          run_callbacks(:persist)  {  persist!  }          true      end  !    def  transaction(&block)          ActiveRecord::Base.transaction(&block)      end  end

Page 30: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FORM: O BÁSICO

• Provê accessors

• Delega responsabilidades

• Infra de callbacks

• Realiza validações

• Inclusive customizadas

class  AccountForm      include  Form  !    attr_accessor  :captcha_id,  :captcha_answer  !    delegate_accessors  :name,          :password,  :email,  to:  :user  !    delegate_accessors  :name,  :url,            to:  :store,  prefix:  true  !    validates  :captcha_answer,  captcha:  true      validates  :name,  :store_url,            presence:  true  end

Page 31: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FORM: ATRIBUTOS

• Alguns são da class

• Alguns são delegados

• delegate_accessors

   attr_accessor  :captcha_id,  :captcha_answer  !delegate_accessors  :name,          :password,  :email,  to:  :user  !delegate_accessors  :name,  :url,            to:  :store,  prefix:  true

Page 32: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FORM: VALIDAÇÃO

• Fácil de compor em outros FormObjects

• Não modifica a lógica do Form Object

• Pode ser testada em isolamento

#  account_form.rb  validates  :captcha_answer,  captcha:  true

!#  captcha_validator.rbclass  CaptchaValidator      def  validate_each(r,  attr,  val)          captcha  =  CaptchaQuestion.find(r)  !        unless  captcha.valid?(val)              r.errors.add(attr,  :invalid)          end      end  end  

Page 33: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FORM: CALLBACKS

• Dispara callbacks

• Callbacks implementados em classe a parte

• Reutilizáveis

• Pode ser testado em isolamento

#  account_form.rb  after_persist  SendSignupEmail,  LogIp  !!!class  SendSignupEmail      class  <<  self          def  after_persist(form)              SignupEmail.deliver(form.user)          end      end  end  !class  LogIp      class  <<  self          def  after_persist(form)              IpLogger.log(form.remote_ip)          end      end  end

Page 34: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

FORM: PERSISTÊNCIA

• Delega para os models

• Precisa do ActiveRecord :(

#  account_form.rb  !    protected  !    def  store          @store  ||=  Store.new      end  !    def  user          @user  ||=  User.new      end  !    def  persist!          transaction  do              user.save              store.save              user.store  =  store          end      end

Page 35: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

SLIM CONTROLLER

• Inicialização

• Rendering/redirect    def  create          @form  =  AccountForm.new(accout_params)          @form.remote_ip  =  request.remote_ip          @form.submit  !        respond_with(@form,  location:  accounts_path)      end

Page 36: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

SLIM MODEL

• Apenas relacionamentos

• Sem validações

• Sem callbacks

   class  Store  <  ActiveRecord::Base          belongs_to  :user      end  !    class  User  <  ActiveRecord::Base          has_one  :store      end

Page 37: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

CODE SMELL

• Divergent change

• This smell refers to making unrelated changes in the same location.

   def  persist!          transaction  do              user.save              store.save              user.store  =  store          end      end

Page 38: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Perpetuity Implementação do DataMapper Pattern

Page 39: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

PERPETUITY

• Desacopla persistência de lógica de domínio

• Funciona com qualquer PORO

form  =  AccountForm.new  form.name  =  ‘Guilherme'  form.store_url  =  ‘http://...’  !Perpetuity[Account].insert  account

Page 40: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

Reform Infraestrutura para form objects

Page 41: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

REFORM

• Desacopla persistência de lógica de domínio

• Nesting

• Relacionamentos

• Coerção (usando o Virtus)

@form.save  do  |data,  nested|     u  =  User.create(nested[:user])     s  =  Store.create(nested[:store])     u.stores  =  s  end

Page 42: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

OBRIGADO! [email protected]

Page 43: Where Does the Fat Goes? Utilizando Form Objects Para Simplificar seu Código

• http://pivotallabs.com/form-backing-objects-for-fun-and-profit/

• http://robots.thoughtbot.com/activemodel-form-objects

• http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

• http://www.reddit.com/r/ruby/comments/1qbiwr/any_form_object_fans_out_there_who_might_want_to/

• http://panthersoftware.com/blog/2013/05/13/user-registration-using-form-objects-in-rails/

• http://reinteractive.net/posts/158-form-objects-in-rails

• https://docs.djangoproject.com/en/dev/topics/forms/#form-objects

• http://engineering.nulogy.com/posts/building-rich-domain-models-in-rails-separating-persistence/

• http://robots.thoughtbot.com/sandi-metz-rules-for-developers

• https://github.com/brycesenz/freeform

• http://nicksda.apotomo.de/2013/05/reform-decouple-your-forms-from-your-models/

• http://joncairns.com/2013/04/fat-model-skinny-controller-is-a-load-of-rubbish/

• http://engineering.nulogy.com/posts/building-rich-domain-models-in-rails-separating-persistence/

• https://www.youtube.com/watch?v=jk8FEssfc90