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

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

description

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

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

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