Rails Service Objects: คู่มือที่ครอบคลุม

เผยแพร่แล้ว: 2022-03-11

Ruby on Rails มาพร้อมกับทุกสิ่งที่คุณต้องการเพื่อสร้างต้นแบบแอปพลิเคชันของคุณอย่างรวดเร็ว แต่เมื่อ Codebase ของคุณเริ่มเติบโต คุณจะเจอสถานการณ์ที่รูปแบบเดิมๆ ของ Fat Model, Skinny Controller แตกสลาย เมื่อตรรกะทางธุรกิจของคุณไม่พอดีกับโมเดลหรือตัวควบคุม นั่นคือเวลาที่ออบเจ็กต์บริการเข้ามา และให้เราแยกการดำเนินการทางธุรกิจทั้งหมดออกเป็นออบเจ็กต์ Ruby ของตัวเอง

ตัวอย่างรอบการร้องขอด้วยออบเจกต์บริการ Rails

ในบทความนี้ ฉันจะอธิบายเมื่อจำเป็นต้องใช้ออบเจ็กต์บริการ วิธีการเขียนวัตถุบริการที่สะอาดและจัดกลุ่มเข้าด้วยกันเพื่อความมีสติของผู้มีส่วนร่วม กฎเกณฑ์ที่เข้มงวดที่ฉันกำหนดให้กับออบเจ็กต์บริการเพื่อผูกเข้ากับตรรกะทางธุรกิจของฉันโดยตรง และวิธีที่จะไม่เปลี่ยนอ็อบเจ็กต์บริการของคุณให้กลายเป็นพื้นที่ทิ้งขยะสำหรับโค้ดทั้งหมดที่คุณไม่รู้ว่าต้องทำอย่างไร

เหตุใดฉันจึงต้องการออบเจ็กต์บริการ

ลองทำสิ่งนี้: คุณจะทำอย่างไรเมื่อแอปพลิเคชันของคุณต้องการทวีตข้อความจาก params[:message] ?

หากคุณเคยใช้ vanilla Rails มาก่อน คุณอาจเคยทำสิ่งนี้:

 class TweetController < ApplicationController def create send_tweet(params[:message]) end private def send_tweet(tweet) client = Twitter::REST::Client.new do |config| config.consumer_key = ENV['TWITTER_CONSUMER_KEY'] config.consumer_secret = ENV['TWITTER_CONSUMER_SECRET'] config.access_token = ENV['TWITTER_ACCESS_TOKEN'] config.access_token_secret = ENV['TWITTER_ACCESS_SECRET'] end client.update(tweet) end end

ปัญหาคือคุณได้เพิ่มอย่างน้อยสิบบรรทัดในคอนโทรลเลอร์ของคุณ แต่จริงๆ แล้วมันไม่ได้อยู่ที่นั้น แล้วถ้าคุณต้องการใช้ฟังก์ชันเดียวกันนี้ในคอนโทรลเลอร์อื่นล่ะ คุณย้ายสิ่งนี้ไปสู่ข้อกังวลหรือไม่? เดี๋ยวก่อน แต่รหัสนี้ไม่อยู่ในคอนโทรลเลอร์เลย เหตุใด Twitter API จึงไม่สามารถมาพร้อมกับออบเจ็กต์ที่เตรียมไว้เพียงชิ้นเดียวให้ฉันโทรได้

ครั้งแรกที่ฉันทำสิ่งนี้ ฉันรู้สึกเหมือนทำอะไรสกปรก ตัวควบคุม Rails แบบลีนที่สวยงามของฉันก่อนหน้านี้เริ่มอ้วนขึ้น และฉันไม่รู้ว่าต้องทำอย่างไร ในที่สุด ฉันก็ซ่อมคอนโทรลเลอร์ด้วยวัตถุบริการ

ก่อนที่คุณจะเริ่มอ่านบทความนี้ ลองสมมุติว่า:

  • แอปพลิเคชั่นนี้จัดการบัญชี Twitter
  • The Rails Way หมายถึง “วิธีการทำสิ่งต่าง ๆ ของ Ruby on Rails แบบธรรมดา” และหนังสือเล่มนี้ไม่มีอยู่จริง
  • ฉันเป็นผู้เชี่ยวชาญของ Rails… ซึ่งฉันบอกทุกวันว่าฉันเป็น แต่ฉันมีปัญหาในการเชื่อ ดังนั้นลองแสร้งทำเป็นว่าฉันเป็นคนหนึ่งจริงๆ

วัตถุบริการคืออะไร?

ออบเจ็กต์บริการคือออบเจ็กต์ Old Ruby Objects (PORO) แบบธรรมดาที่ออกแบบมาเพื่อเรียกใช้การดำเนินการเดียวในตรรกะของโดเมนของคุณและทำได้ดี ลองพิจารณาตัวอย่างด้านบน: วิธีการของเรามีเหตุผลที่จะทำสิ่งเดียวอยู่แล้ว นั่นคือการสร้างทวีต จะเกิดอะไรขึ้นถ้าตรรกะนี้ถูกห่อหุ้มไว้ภายในคลาส Ruby เดียวที่เราสามารถสร้างอินสแตนซ์และเรียกใช้เมธอดได้ สิ่งที่ต้องการ:

 tweet_creator = TweetCreator.new(params[:message]) tweet_creator.send_tweet # Later on in the article, we'll add syntactic sugar and shorten the above to: TweetCreator.call(params[:message])

นี้มันสวยมาก; วัตถุบริการ TweetCreator ของเราที่สร้างขึ้นเมื่อสามารถเรียกได้จากทุกที่และจะทำสิ่งหนึ่งได้เป็นอย่างดี

การสร้างวัตถุบริการ

ขั้นแรก มาสร้าง TweetCreator ใหม่ในโฟลเดอร์ใหม่ชื่อ app/services :

 $ mkdir app/services && touch app/services/tweet_creator.rb

และทิ้งตรรกะทั้งหมดของเราไว้ในคลาส Ruby ใหม่:

 # app/services/tweet_creator.rb class TweetCreator def initialize(message) @message = message end def send_tweet client = Twitter::REST::Client.new do |config| config.consumer_key = ENV['TWITTER_CONSUMER_KEY'] config.consumer_secret = ENV['TWITTER_CONSUMER_SECRET'] config.access_token = ENV['TWITTER_ACCESS_TOKEN'] config.access_token_secret = ENV['TWITTER_ACCESS_SECRET'] end client.update(@message) end end

จากนั้นคุณสามารถเรียก TweetCreator.new(params[:message]).send_tweet ได้ทุกที่ในแอปของคุณ และมันจะใช้งานได้ Rails จะโหลดวัตถุนี้อย่างน่าอัศจรรย์เพราะจะโหลดทุกอย่างโดยอัตโนมัติภายใต้ app/ ตรวจสอบสิ่งนี้โดยเรียกใช้:

 $ rails c Running via Spring preloader in process 12417 Loading development environment (Rails 5.1.5) > puts ActiveSupport::Dependencies.autoload_paths ... /Users/gilani/Sandbox/nazdeeq/app/services

ต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับวิธีการ autoload หรือไม่ อ่านคู่มือการโหลดอัตโนมัติและโหลดซ้ำค่าคงที่

การเพิ่มน้ำตาลซินแทคติกเพื่อสร้างออบเจกต์บริการ Rails ให้น้อยลง

ฟังนะ รู้สึกดีในทางทฤษฎี แต่ TweetCreator.new(params[:message]).send_tweet เป็นเพียงคำพูดหนึ่งคำเท่านั้น ใช้คำซ้ำซากมากเกินไป… เหมือน HTML (ba-dum tiss! ) จริงๆ แล้ว ทำไมคนถึงใช้ HTML เมื่อ HAML อยู่ใกล้ๆ หรือแม้แต่สลิม ฉันเดาว่าเป็นบทความอื่นสำหรับเวลาอื่น กลับไปที่งานในมือ:

TweetCreator เป็นชื่อคลาสสั้นๆ ที่ดี แต่ความยุ่งยากพิเศษในการสร้างอินสแตนซ์ของอ็อบเจกต์และการเรียกเมธอดนั้นยาวเกินไป! หาก Ruby มีลำดับความสำคัญในการเรียกบางสิ่งและให้เรียกใช้งานตัวเองทันทีด้วยพารามิเตอร์ที่กำหนด... โอ้ เดี๋ยวก่อน! มันคือ Proc#call

Proccall เรียกใช้บล็อกโดยตั้งค่าพารามิเตอร์ของบล็อกเป็นค่าในพารามิเตอร์โดยใช้สิ่งที่ใกล้เคียงกับวิธีการเรียกความหมาย ส่งคืนค่าของนิพจน์สุดท้ายที่ประเมินในบล็อก

 aproc = Proc.new {|scalar, values| values.map {|value| valuescalar } } aproc.call(9, 1, 2, 3) #=> [9, 18, 27] aproc[9, 1, 2, 3] #=> [9, 18, 27] aproc.(9, 1, 2, 3) #=> [9, 18, 27] aproc.yield(9, 1, 2, 3) #=> [9, 18, 27]

เอกสาร

หากสิ่งนี้ทำให้คุณสับสน ให้ฉันอธิบาย proc สามารถ call -ed เพื่อดำเนินการเองด้วยพารามิเตอร์ที่กำหนด ซึ่งหมายความว่าหาก TweetCreator เป็น proc เราสามารถเรียกมันด้วย TweetCreator.call(message) และผลลัพธ์จะเทียบเท่ากับ TweetCreator.new(params[:message]).call ซึ่งดูค่อนข้างคล้ายกับ TweetCreator.new(params[:message]).send_tweet รุ่นเก่าของเรา TweetCreator.new(params[:message]).send_tweet

มาทำให้วัตถุบริการของเรามีพฤติกรรมเหมือน proc กันเถอะ!

อันดับแรก เนื่องจากเราอาจต้องการใช้พฤติกรรมนี้ซ้ำกับออบเจ็กต์บริการทั้งหมดของเรา มายืมจาก Rails Way และสร้างคลาสที่เรียกว่า ApplicationService :

 # app/services/application_service.rb class ApplicationService def self.call(*args, &block) new(*args, &block).call end end

คุณเห็นสิ่งที่ฉันทำที่นั่นไหม ฉันได้เพิ่มเมธอดของคลาสที่เรียกว่า call ซึ่งสร้างอินสแตนซ์ใหม่ของคลาสด้วยอาร์กิวเมนต์หรือบล็อกที่คุณส่งผ่าน และเรียกใช้การ call บนอินสแตนซ์ ตรงตามที่เราต้องการ! สิ่งสุดท้ายที่ต้องทำคือการเปลี่ยนชื่อเมธอดจากคลาส TweetCreator เป็น call และให้คลาสสืบทอดจาก ApplicationService :

 # app/services/tweet_creator.rb class TweetCreator < ApplicationService attr_reader :message def initialize(message) @message = message end def call client = Twitter::REST::Client.new do |config| config.consumer_key = ENV['TWITTER_CONSUMER_KEY'] config.consumer_secret = ENV['TWITTER_CONSUMER_SECRET'] config.access_token = ENV['TWITTER_ACCESS_TOKEN'] config.access_token_secret = ENV['TWITTER_ACCESS_SECRET'] end client.update(@message) end end

และสุดท้าย เรามาปิดท้ายกันด้วยการเรียกวัตถุบริการของเราในตัวควบคุม:

 class TweetController < ApplicationController def create TweetCreator.call(params[:message]) end end

การจัดกลุ่มออบเจ็กต์บริการที่คล้ายกันเพื่อความมีสติ

ตัวอย่างข้างต้นมีวัตถุบริการเพียงชิ้นเดียว แต่ในโลกแห่งความเป็นจริง สิ่งต่าง ๆ อาจซับซ้อนกว่านั้น ตัวอย่างเช่น จะเกิดอะไรขึ้นถ้าคุณมีบริการหลายร้อยรายการ และครึ่งหนึ่งเกี่ยวข้องกับการดำเนินธุรกิจ เช่น การมีบริการ Follower ที่ติดตามบัญชี Twitter อื่น พูดตามตรง ฉันจะเป็นบ้าตายถ้าโฟลเดอร์หนึ่งมีไฟล์ที่ดูไม่ซ้ำกัน 200 ไฟล์ ยังดีที่มีรูปแบบอื่นจาก Rails Way ที่เราสามารถคัดลอกได้—ฉันหมายถึง ใช้เป็นแรงบันดาลใจ: เนมสเปซ

สมมติว่าเราได้รับมอบหมายให้สร้างวัตถุบริการที่ติดตามโปรไฟล์ Twitter อื่นๆ

ลองดูชื่อของวัตถุบริการก่อนหน้าของเรา: TweetCreator ดูเหมือนว่าบุคคลหรืออย่างน้อยที่สุดก็มีบทบาทในองค์กร คนที่สร้างทวีต ฉันชอบตั้งชื่อออบเจ็กต์บริการของฉันราวกับว่ามันเป็นเพียงแค่: บทบาทในองค์กร ตามแบบแผนนี้ ฉันจะเรียกวัตถุใหม่ของฉัน: ProfileFollower

ตอนนี้ เนื่องจากฉันเป็นผู้ปกครองสูงสุดของแอปนี้ ฉันจะสร้างตำแหน่งผู้บริหารในลำดับชั้นบริการของฉัน และมอบหมายความรับผิดชอบสำหรับบริการทั้งสองนี้ไปยังตำแหน่งนั้น ฉันจะเรียกตำแหน่งผู้บริหารใหม่นี้ว่า TwitterManager

เนื่องจากตัวจัดการนี้ไม่ได้ทำอะไรนอกจากจัดการ มาสร้างโมดูลและซ้อนอ็อบเจ็กต์บริการของเราภายใต้โมดูลนี้ โครงสร้างโฟลเดอร์ของเราจะมีลักษณะดังนี้:

 services ├── application_service.rb └── twitter_manager ├── profile_follower.rb └── tweet_creator.rb

และวัตถุบริการของเรา:

 # services/twitter_manager/tweet_creator.rb module TwitterManager class TweetCreator < ApplicationService ... end end
 # services/twitter_manager/profile_follower.rb module TwitterManager class ProfileFollower < ApplicationService ... end end

และการโทรของเราจะกลายเป็น TwitterManager::TweetCreator.call(arg) และ TwitterManager::ProfileManager.call(arg)

Service Objects เพื่อจัดการการทำงานของฐานข้อมูล

ตัวอย่างข้างต้นทำการเรียก API แต่วัตถุบริการยังสามารถใช้ได้เมื่อมีการเรียกทั้งหมดไปยังฐานข้อมูลของคุณแทนที่จะเป็น API ซึ่งจะเป็นประโยชน์อย่างยิ่งหากการดำเนินการทางธุรกิจบางอย่างต้องการการอัปเดตฐานข้อมูลหลายรายการในธุรกรรม ตัวอย่างเช่น รหัสตัวอย่างนี้จะใช้บริการเพื่อบันทึกการแลกเปลี่ยนสกุลเงินที่เกิดขึ้น

 module MoneyManager # exchange currency from one amount to another class CurrencyExchanger < ApplicationService ... def call ActiveRecord::Base.transaction do # transfer the original currency to the exchange's account outgoing_tx = CurrencyTransferrer.call( from: the_user_account, to: the_exchange_account, amount: the_amount, currency: original_currency ) # get the exchange rate rate = ExchangeRateGetter.call( from: original_currency, to: new_currency ) # transfer the new currency back to the user's account incoming_tx = CurrencyTransferrer.call( from: the_exchange_account, to: the_user_account, amount: the_amount * rate, currency: new_currency ) # record the exchange happening ExchangeRecorder.call( outgoing_tx: outgoing_tx, incoming_tx: incoming_tx ) end end end # record the transfer of money from one account to another in money_accounts class CurrencyTransferrer < ApplicationService ... end # record an exchange event in the money_exchanges table class ExchangeRecorder < ApplicationService ... end # get the exchange rate from an API class ExchangeRateGetter < ApplicationService ... end end

ฉันจะส่งคืนอะไรจากวัตถุบริการของฉัน

เราได้พูดถึงวิธีการ call วัตถุบริการของเราแล้ว แต่วัตถุควรส่งคืนอะไร มีสามวิธีในการเข้าถึงสิ่งนี้:

  • คืน true หรือ false
  • ส่งคืนค่า
  • คืน Enum

คืน true หรือ false

อันนี้ง่าย: หากการกระทำทำงานตามที่ตั้งใจไว้ ให้คืนค่า true ; มิฉะนั้นให้คืนค่า false :

 def call ... return true if client.update(@message) false end

คืนมูลค่า

หากวัตถุบริการของคุณดึงข้อมูลจากที่ใดที่หนึ่ง คุณอาจต้องการคืนค่านั้น:

 def call ... return false unless exchange_rate exchange_rate end

ตอบกลับด้วย Enum

หากออบเจ็กต์บริการของคุณซับซ้อนกว่านี้เล็กน้อย และคุณต้องการจัดการกับสถานการณ์ที่แตกต่างกัน คุณสามารถเพิ่ม enums เพื่อควบคุมการไหลของบริการของคุณ:

 class ExchangeRecorder < ApplicationService RETURNS = [ SUCCESS = :success, FAILURE = :failure, PARTIAL_SUCCESS = :partial_success ] def call foo = do_something return SUCCESS if foo.success? return FAILURE if foo.failure? PARTIAL_SUCCESS end private def do_something end end

จากนั้นในแอปของคุณ คุณสามารถใช้:

 case ExchangeRecorder.call when ExchangeRecorder::SUCCESS foo when ExchangeRecorder::FAILURE bar when ExchangeRecorder::PARTIAL_SUCCESS baz end

ฉันไม่ควรใส่ Service Objects ใน lib/services แทน app/services หรือไม่

นี้เป็นอัตนัย ความคิดเห็นของผู้คนแตกต่างกันว่าจะวางวัตถุบริการไว้ที่ไหน บางคนใส่ไว้ใน lib/services ในขณะที่บางคนสร้าง app/services ตกอยู่ค่ายหลัง. คู่มือเริ่มต้นใช้งานของ Rails อธิบายโฟลเดอร์ lib/ เป็นที่สำหรับวาง “โมดูลเพิ่มเติมสำหรับแอปพลิเคชันของคุณ”

ในความเห็นที่ต่ำต้อยของฉัน "โมดูลแบบขยาย" หมายถึงโมดูลที่ไม่ห่อหุ้มตรรกะของโดเมนหลักและโดยทั่วไปสามารถใช้ในโครงการต่างๆ ในคำพูดที่ชาญฉลาดของคำตอบ Stack Overflow แบบสุ่ม ให้ใส่โค้ดในนั้นที่ "สามารถกลายเป็นอัญมณีของตัวเองได้"

Service Objects เป็นแนวคิดที่ดีหรือไม่?

ขึ้นอยู่กับกรณีการใช้งานของคุณ ฟังนะ ความจริงที่ว่าคุณกำลังอ่านบทความนี้อยู่ในขณะนี้ บ่งบอกว่าคุณกำลังพยายามเขียนโค้ดที่ไม่ได้อยู่ในโมเดลหรือคอนโทรลเลอร์ ฉันเพิ่งอ่านบทความนี้เกี่ยวกับวิธีที่ออบเจ็กต์บริการเป็นรูปแบบการต่อต้าน ผู้เขียนมีความคิดเห็นของเขา แต่ฉันไม่เห็นด้วยด้วยความเคารพ

เพียงเพราะบุคคลอื่นใช้วัตถุบริการมากเกินไปไม่ได้หมายความว่าสิ่งเหล่านั้นไม่ดีโดยเนื้อแท้ ในการเริ่มต้นของฉัน Nazdeeq เราใช้ออบเจ็กต์บริการและโมเดลที่ไม่ใช่ ActiveRecord แต่ความแตกต่างระหว่างสิ่งที่ปรากฏแก่ฉันคือ: ฉันเก็บ การดำเนินการทางธุรกิจ ทั้งหมดไว้ในออบเจกต์บริการ ในขณะที่ยังคงรักษาทรัพยากรที่ไม่ต้องการความคงอยู่จริง ๆ ในโมเดลที่ไม่ใช่ ActiveRecord ท้ายที่สุดแล้ว คุณเป็นผู้ตัดสินใจว่ารูปแบบใดที่เหมาะกับคุณ

อย่างไรก็ตาม ฉันคิดว่าวัตถุที่ให้บริการโดยทั่วไปเป็นความคิดที่ดีหรือไม่ อย่างแน่นอน! พวกเขาจัดระเบียบโค้ดของฉันอย่างเป็นระเบียบ และสิ่งที่ทำให้ฉันมั่นใจในการใช้ PORO ก็คือ Ruby ชอบวัตถุ ไม่จริง รูบี้ ชอบ สิ่งของ มันบ้า บ้ามาก แต่ฉันชอบมัน! กรณีตรงประเด็น:

 > 5.is_a? Object # => true > 5.class # => Integer > class Integer ?> def woot ?> 'woot woot' ?> end ?> end # => :woot > 5.woot # => "woot woot"

ดู? 5 เป็นวัตถุอย่างแท้จริง

ในหลายภาษา ตัวเลขและประเภทดั้งเดิมอื่นๆ ไม่ใช่วัตถุ Ruby ติดตามอิทธิพลของภาษา Smalltalk โดยให้วิธีการและตัวแปรอินสแตนซ์ทุกประเภท สิ่งนี้ทำให้การใช้ Ruby เป็นเรื่องง่าย เนื่องจากกฎที่ใช้กับวัตถุมีผลกับ Ruby ทั้งหมด Ruby-lang.org

เมื่อใดที่ฉันไม่ควรใช้วัตถุบริการ

อันนี้ง่าย ฉันมีกฎเหล่านี้:

  1. รหัสของคุณจัดการกับการกำหนดเส้นทาง พารามิเตอร์ หรือทำสิ่งอื่นๆ ของตัวควบคุมหรือไม่
    ถ้าใช่ อย่าใช้วัตถุบริการ เพราะรหัสของคุณอยู่ในตัวควบคุม
  2. คุณกำลังพยายามแบ่งปันรหัสของคุณในตัวควบคุมอื่นหรือไม่?
    ในกรณีนี้ อย่าใช้วัตถุบริการ—ใช้ข้อกังวล
  3. รหัสของคุณเป็นเหมือนโมเดลที่ไม่ต้องการความคงอยู่หรือไม่?
    ถ้าเป็นเช่นนั้น อย่าใช้วัตถุบริการ ใช้โมเดลที่ไม่ใช่ ActiveRecord แทน
  4. รหัสของคุณเป็นการดำเนินการทางธุรกิจที่เฉพาะเจาะจงหรือไม่? (เช่น "นำถังขยะออกไป" "สร้าง PDF โดยใช้ข้อความนี้" หรือ "คำนวณภาษีศุลกากรโดยใช้กฎที่ซับซ้อนเหล่านี้")
    ในกรณีนี้ ให้ใช้วัตถุบริการ รหัสนั้นอาจไม่เหมาะสมกับคอนโทรลเลอร์หรือรุ่นของคุณ

แน่นอนว่านี่เป็นกฎ ของฉัน ดังนั้นคุณสามารถปรับเปลี่ยนให้เข้ากับกรณีการใช้งานของคุณเองได้ สิ่งเหล่านี้ทำงานได้ดีสำหรับฉัน แต่ระยะของคุณอาจแตกต่างกันไป

กฎการเขียน Good Service Objects

ฉันมีกฎสี่ข้อสำหรับการสร้างวัตถุบริการ สิ่งเหล่านี้ไม่ได้เขียนด้วยหิน และถ้าคุณต้องการทำลายมัน จริงๆ คุณก็ทำได้ แต่ฉันอาจจะขอให้คุณเปลี่ยนมันในการตรวจสอบโค้ด เว้นแต่ว่าเหตุผลของคุณจะดี

กฎข้อที่ 1: วิธีสาธารณะเพียงหนึ่งวิธีต่อออบเจ็กต์บริการ

ออบเจ็กต์บริการเป็นการดำเนินการทางธุรกิจ เดียว คุณสามารถเปลี่ยนชื่อวิธีการสาธารณะของคุณได้หากต้องการ ฉันชอบใช้ call แต่ codebase ของ Gitlab CE เรียกมันว่า execute และคนอื่นอาจใช้ perform ใช้อะไรก็ได้ที่คุณต้องการ—คุณสามารถเรียกมันว่า nermin สำหรับทุกอย่างที่ฉันใส่ใจ อย่าสร้างวิธีการสาธารณะสองวิธีสำหรับวัตถุบริการเดียว แบ่งมันออกเป็นสองวัตถุถ้าคุณต้องการ

กฎข้อที่ 2: ตั้งชื่อวัตถุบริการเหมือนบทบาทใบ้ที่บริษัท

ออบเจ็กต์บริการเป็นการดำเนินการ ทางธุรกิจ เดียว ลองนึกภาพถ้าคุณจ้างคนหนึ่งที่บริษัทเพื่อทำงานนั้น คุณจะเรียกพวกเขาว่าอะไร? หากงานของพวกเขาคือการสร้างทวีต ให้เรียกพวกเขาว่า TweetCreator หากงานของพวกเขาคืออ่านทวีตที่เฉพาะเจาะจง ให้เรียกพวกเขาว่า TweetReader

กฎข้อที่ 3: อย่าสร้างวัตถุทั่วไปเพื่อดำเนินการหลายอย่าง

ออบเจ็กต์บริการ เป็นการดำเนินการ ทางธุรกิจเดียว ฉันแบ่งการทำงานออกเป็นสองส่วน: TweetReader และ ProfileFollower สิ่งที่ฉันไม่ได้ทำคือสร้างอ็อบเจ็กต์ทั่วไปชื่อ TwitterHandler และถ่ายโอนฟังก์ชัน API ทั้งหมดในนั้นทิ้ง กรุณาอย่าทำเช่นนี้ สิ่งนี้ขัดกับความคิด "การดำเนินการทางธุรกิจ" และทำให้วัตถุบริการดูเหมือน Twitter Fairy หากคุณต้องการแบ่งปันรหัสระหว่างวัตถุทางธุรกิจ เพียงแค่สร้างวัตถุหรือโมดูล BaseTwitterManager และผสมผสานสิ่งนั้นเข้ากับวัตถุบริการของคุณ

กฎข้อที่ 4: จัดการข้อยกเว้นภายในวัตถุบริการ

เป็นครั้งที่สิบหก: อ อบเจ็กต์บริการเป็นการดำเนินการทางธุรกิจเดียว ฉันไม่สามารถพูดได้เพียงพอ หากคุณมีคนที่อ่านทวีต พวกเขาจะให้ทวีตกับคุณหรือพูดว่า “ทวีตนี้ไม่มีอยู่จริง” ในทำนองเดียวกัน อย่าปล่อยให้วัตถุบริการตื่นตระหนก กระโดดขึ้นไปบนโต๊ะทำงานของตัวควบคุม และบอกให้หยุดการทำงานทั้งหมดเพราะ “เกิดข้อผิดพลาด!” เพียงคืนค่า false และปล่อยให้ตัวควบคุมดำเนินการต่อไปจากที่นั่น

เครดิตและขั้นตอนต่อไป

บทความนี้จะเกิดขึ้นไม่ได้หากไม่มีชุมชนนักพัฒนา Ruby ที่ Toptal ถ้าฉันพบปัญหา ชุมชนเป็นกลุ่มวิศวกรที่มีความสามารถที่เป็นประโยชน์มากที่สุดที่ฉันเคยพบ

หากคุณกำลังใช้ออบเจกต์บริการ คุณอาจพบว่าตัวเองสงสัยว่าจะบังคับคำตอบบางอย่างขณะทำการทดสอบได้อย่างไร ฉันแนะนำให้อ่านบทความนี้เกี่ยวกับวิธีการสร้างวัตถุบริการจำลองใน Rspec ที่จะส่งคืนผลลัพธ์ที่คุณต้องการเสมอโดยไม่ต้องกดวัตถุบริการ!

หากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับกลเม็ดของ Ruby ฉันขอแนะนำให้สร้าง Ruby DSL: คู่มือการเขียนโปรแกรมเมตาขั้นสูงโดยเพื่อน Toptaler Mate Solymosi เขาแยกแยะว่าไฟล์ routes.rb ไม่รู้สึกเหมือน Ruby และช่วยคุณสร้าง DSL ของคุณเอง