ออบเจ็กต์บริการคืออ็อบเจ็กต์ Ruby ที่ทำการดำเนินการเดียว มันสรุปกระบวนการในโดเมนหรือตรรกะทางธุรกิจของคุณ ลองนึกภาพว่าคุณต้องสร้างหนังสือในแอปพลิเคชันห้องสมุดจินตภาพ ในแอป Rails ธรรมดา คุณต้องทำสิ่งต่อไปนี้:
class BookController < ApplicationController
def create
Book.new(*args)
end
end
นี่เป็นสิ่งที่ดีสำหรับสิ่งง่ายๆ อย่างไรก็ตาม เมื่อแอปเติบโตขึ้น คุณอาจพบว่ามีต้นแบบจำนวนมากล้อมรอบ:
class BookController < ApplicationController
def create
default_args = { genre: find_genre(), author: find_author() }
Book.new(attrs.merge(default_args))
end
private
def find_genre
// ...
end
def find_author
// ...
end
end
ออบเจ็กต์บริการช่วยให้คุณสามารถสรุปพฤติกรรมนี้ในคลาสที่แยกจากกัน จากนั้นโค้ดของคุณจะกลายเป็นเรื่องง่ายอีกครั้ง:
class BookController < ApplicationController
def
BookCreator.create_book
end
end
เหตุใดคุณจึงต้องมีออบเจ็กต์บริการ
Rails ได้รับการออกแบบมาเพื่อรองรับโครงสร้างองค์กร MVC (เช่น โมเดล ผู้ควบคุม มุมมอง และผู้ช่วย) โดยกำเนิด โครงสร้างนี้เพียงพอสำหรับการใช้งานทั่วไป อย่างไรก็ตาม เมื่อแอปพลิเคชันของคุณเติบโตขึ้น คุณอาจเริ่มเห็นตรรกะของโดเมน/ธุรกิจกระจัดกระจายอยู่ในโมเดลและตัวควบคุม ตรรกะดังกล่าวไม่ได้เป็นของคอนโทรลเลอร์หรือโมเดล ดังนั้นจึงทำให้โค้ดนำกลับมาใช้ใหม่และบำรุงรักษาได้ยาก ออบเจ็กต์บริการ Rails เป็นรูปแบบที่สามารถช่วยคุณแยกตรรกะทางธุรกิจออกจากตัวควบคุมและโมเดล ทำให้โมเดลเป็นเพียงชั้นข้อมูลและจุดเริ่มต้นตัวควบคุมไปยัง API ของคุณ
เราได้รับประโยชน์มากมายเมื่อเราแนะนำบริการเพื่อสรุปตรรกะทางธุรกิจ รวมถึงสิ่งต่อไปนี้:
-
ตัวควบคุม Lean Rails - ผู้ควบคุมมีหน้าที่รับผิดชอบในการทำความเข้าใจคำขอและเปลี่ยนพารามิเตอร์คำขอ เซสชัน และคุกกี้เป็นอาร์กิวเมนต์ที่ส่งผ่านไปยังวัตถุบริการเพื่อดำเนินการ ตัวควบคุมจะเปลี่ยนเส้นทางหรือแสดงผลตามการตอบสนองของบริการ แม้แต่ในแอปพลิเคชันขนาดใหญ่ การทำงานของตัวควบคุมโดยใช้วัตถุบริการมักจะมีโค้ดไม่เกิน 10 บรรทัด
-
ตัวควบคุมที่ทดสอบได้ - เนื่องจากคอนโทรลเลอร์มีขนาดเล็กและทำหน้าที่เป็นผู้ทำงานร่วมกันในบริการ การทดสอบจึงทำได้ง่ายมาก เนื่องจากเราสามารถตรวจสอบได้เพียงว่าวิธีการบางอย่างภายในคอนโทรลเลอร์นั้นถูกเรียกเมื่อมีการกระทำบางอย่างเกิดขึ้น
-
ความสามารถในการทดสอบกระบวนการทางธุรกิจแบบแยกส่วน - บริการทดสอบได้ง่ายและรวดเร็ว เนื่องจากเป็นวัตถุ Ruby ขนาดเล็กที่แยกออกจากสภาพแวดล้อม เราสามารถ stub ผู้ทำงานร่วมกันทั้งหมดได้อย่างง่ายดายและตรวจสอบว่ามีการดำเนินการตามขั้นตอนบางอย่างภายในบริการของเราหรือไม่
-
บริการที่นำกลับมาใช้ใหม่ได้ - วัตถุบริการสามารถเรียกได้โดยผู้ควบคุม ออบเจ็กต์บริการอื่นๆ งานที่อยู่ในคิว ฯลฯ
-
การแยกระหว่างกรอบงานและโดเมนธุรกิจ - ตัวควบคุม Rails เห็นเฉพาะบริการและโต้ตอบกับวัตถุโดเมนที่ใช้ การคัปปลิ้งที่ลดลงนี้ทำให้ความสามารถในการปรับขนาดง่ายขึ้น โดยเฉพาะอย่างยิ่งเมื่อคุณต้องการย้ายจากโมโนลิธไปเป็นไมโครเซอร์วิส บริการของคุณสามารถแยกออกและย้ายไปยังบริการใหม่ได้อย่างง่ายดายโดยมีการดัดแปลงเพียงเล็กน้อย
การสร้างวัตถุบริการ
ขั้นแรก มาสร้าง BookCreator ใหม่ในโฟลเดอร์ใหม่ที่เรียกว่าแอพ/บริการสำหรับแอปพลิเคชันการจัดการห้องสมุดในจินตนาการ:
$ mkdir app/services && touch app/services/book_creator.rb
ต่อไป มาทิ้งตรรกะทั้งหมดของเราไว้ในคลาส Ruby ใหม่:
# app/services/book_creator.rb
class BookCreator
def initialize(title:, description:, author_id:, genre_id:)
@title = title
@description = description
@author_id = author_id
@genre_id = genre_id
end
def create_book
Boook.create!(
title: @title
description: @description
author_id: @author_id
genre_id: @genre_id
)
rescue ActiveRecord::RecordNotUnique => e
# handle duplicate entry
end
end
end
จากนั้น เราสามารถเรียกวัตถุบริการในคอนโทรลเลอร์หรือที่ใดก็ได้ภายในแอปพลิเคชัน:
class BookController < ApplicationController
def create
BookCreator.new(title: params[:title], description: params[:description], author_id: params[:author_id], genre_id: params[:genre_id]).create_book
end
end
น้ำตาลซินแทคติกของวัตถุบริการ
เราสามารถลดความซับซ้อนของ BookCreator.new(arguments).create
เชนโดยเพิ่มวิธีการเรียนที่ยกตัวอย่าง BookCreator
และเรียก create
วิธีสำหรับเรา:
# app/services/book_creator.rb
class BookCreator
def initialize(title:, description:, author_id:, genre_id:)
@title = title
@description = description
@author_id = author_id
@genre_id = genre_id
end
def call(*args)
new(*args).create_book
end
private
def create_book
Boook.create!(
title: @title
description: @description
author_id: @author_id
genre_id: @genre_id
)
rescue ActiveRecord::RecordNotUnique => e
# handle duplicate entry
end
end
end
ในตัวควบคุม ตอนนี้สามารถเรียกผู้สร้างหนังสือได้ดังนี้:
class BookController < ApplicationController
def create
BookCreator.call(
title: params[:title],
description: params[:description],
author_id: params[:author_id],
genre_id: params[:genre_id])
end
end
เพื่อให้โค้ดของเราแห้ง (อย่าทำซ้ำตัวเอง) และนำพฤติกรรมนี้กลับมาใช้ใหม่กับออบเจ็กต์บริการอื่นๆ เราสามารถสรุป call
วิธีการเป็นฐาน ApplicationService
คลาสที่ทุกอ็อบเจ็กต์บริการจะสืบทอดมาจาก:
class ApplicationService
self.call(*args)
new(*args).call
end
end
ด้วยรหัสนี้ เราสามารถ refactor BookCreator
เพื่อสืบทอดจาก ApplicationService
:
# app/services/book_creator.rb
class BookCreator < ApplicationService
def initialize(title:, description:, author_id:, genre_id:)
@title = title
@description = description
@author_id = author_id
@genre_id = genre_id
end
def call
create_book
end
private
def create_book
# ...
end
end
การสร้างวัตถุบริการโดยใช้ BusinessProcess Gem
ด้วย BusinessProcess gem คุณไม่จำเป็นต้องสร้างคลาสบริการแอปพลิเคชันพื้นฐานหรือกำหนด initialize
วิธีเพราะอัญมณีมีการกำหนดค่าเหล่านี้ทั้งหมดอยู่ภายใน วัตถุบริการของคุณเพียงแค่ต้องสืบทอดจาก BusinessProcess::Base
.
ในไฟล์ gem ของคุณ ให้เพิ่มสิ่งต่อไปนี้:
gem 'business_process'
แล้วเรียกใช้ bundle
คำสั่งในเทอร์มินัลของคุณ
class BookCreator < BusinessProcess::Base
# Specify requirements
needs :title
needs :description
needs :author_id
needs :genre_id
# Specify process (action)
def call
create_book
end
private
def create_book
# ...
end
end
คำแนะนำในการสร้างออบเจ็กต์การบริการที่ดี
วิธีสาธารณะหนึ่งวิธี
ออบเจ็กต์บริการควรจะดำเนินการทางธุรกิจอย่างใดอย่างหนึ่งและทำได้ดี ดังนั้นจึงควรเปิดเผยวิธีการสาธารณะเพียงวิธีเดียวสำหรับการทำเช่นนั้น วิธีอื่นควรเป็นแบบส่วนตัวและเรียกโดยวิธีสาธารณะ คุณสามารถเลือกตั้งชื่อเมธอดสาธารณะได้ตามต้องการ ตราบใดที่การตั้งชื่อนั้นสอดคล้องกันในออบเจกต์บริการทั้งหมด ในตัวอย่างของเรา เราได้ตั้งชื่อว่า call
. ชื่อทั่วไปอื่น ๆ คือ perform
และ execute
.
ชื่อออบเจ็กต์บริการตามบทบาทที่ดำเนินการ
ชื่อของวัตถุบริการควรระบุสิ่งที่ทำ มีวิธีที่นิยมในการตั้งชื่อวัตถุบริการด้วยคำที่ลงท้ายด้วย "หรือ" และ "เอ้อ" ตัวอย่างเช่น หากงานของวัตถุบริการคือการสร้างหนังสือ ให้ตั้งชื่อว่า BookCreator และหากงานคือการอ่านหนังสือ ให้ตั้งชื่อว่า BookReader
อย่ายกตัวอย่างวัตถุบริการโดยตรง
ใช้สิ่งที่เป็นนามธรรม เช่น รูปแบบน้ำตาลซินแทกติก หรืออัญมณี เช่น BusinessProcess เพื่อย่อสัญกรณ์ของการเรียกออบเจ็กต์บริการ การใช้วิธีนี้จะทำให้ BookCreator.new(*args).call
ง่ายขึ้น หรือ BookCreator.new.call(*args)
ลงใน BookCreator.call(*args),
ซึ่งสั้นกว่าและอ่านง่ายกว่า
จัดกลุ่มออบเจ็กต์บริการในเนมสเปซ
การแนะนำออบเจ็กต์บริการ โดยเฉพาะอย่างยิ่งในแอปพลิเคชันขนาดใหญ่ หมายความว่าคุณจะเติบโตจากออบเจ็กต์บริการหนึ่งรายการเป็นอ็อบเจ็กต์บริการหลายสิบรายการ เพื่อปรับปรุงการจัดระเบียบโค้ด แนวทางปฏิบัติที่ดีในการจัดกลุ่มออบเจ็กต์บริการทั่วไปลงในเนมสเปซ ตัวอย่างเช่น ในแอปพลิเคชันห้องสมุด เราจะจัดกลุ่มบริการที่เกี่ยวข้องกับหนังสือทั้งหมดไว้ด้วยกัน และจัดกลุ่มบริการที่เกี่ยวข้องกับผู้แต่งทั้งหมดในเนมสเปซที่แยกจากกัน โครงสร้างโฟลเดอร์ของเราจะมีลักษณะดังนี้:
services
├── application_service.rb
└── book
├── book_creator.rb
└── book_reader.rb
วัตถุบริการของเราจะมีลักษณะดังนี้:
# services/book/book_creator.rb
module Book
class BookCreator < ApplicationService
...
end
end
# services/twitter_manager/book_reader.rb
module Book
class BookReader < ApplicationService
...
end
end
การโทรของเราจะกลายเป็น Book::BookCreator.call(*args) and Book::BookReader.call(*args)
.
ความรับผิดชอบหนึ่งรายการต่อออบเจ็กต์บริการ
การมีออบเจ็กต์บริการที่ทำมากกว่าหนึ่งอย่างขัดกับกรอบความคิด "การดำเนินการทางธุรกิจ" ของออบเจกต์บริการ ตามอัตภาพ เราไม่แนะนำให้มีวัตถุบริการทั่วไปที่ดำเนินการหลายอย่าง หากคุณต้องการแชร์รหัสระหว่างออบเจ็กต์บริการ ให้สร้างโมดูลฐานหรือโมดูลตัวช่วย และใช้มิกซ์อินเพื่อรวมไว้ในออบเจ็กต์บริการของคุณ
ช่วยเหลือข้อยกเว้นและเพิ่มข้อยกเว้นที่กำหนดเอง
วัตถุประสงค์ของอ็อบเจ็กต์บริการคือการสรุปรายละเอียดการใช้งานภายในนั้น เช่น การโต้ตอบระหว่างบริการหรือไลบรารีของบุคคลที่สาม หรือการจัดการฐานข้อมูลด้วย Rails ActiveRecord หากเกิดข้อผิดพลาด เช่น ActiveRecord::RecordNotUnique ขณะโต้ตอบกับ ActiveRecord บริการจำเป็นต้องกู้ข้อยกเว้นอย่างเหมาะสม ข้อผิดพลาดไม่ควรได้รับอนุญาตให้เผยแพร่ขึ้น call stack หากจัดการไม่ได้ภายในกลุ่มช่วยเหลือ ให้แจ้งข้อยกเว้นที่กำหนดขึ้นเองเฉพาะสำหรับออบเจกต์บริการนั้น
บทสรุป
รูปแบบออบเจ็กต์บริการสามารถปรับปรุงการออกแบบโดยรวมของแอปพลิเคชันของคุณได้อย่างมากเมื่อคุณเพิ่มคุณสมบัติใหม่ให้กับแอปพลิเคชันของคุณ มันจะทำให้ codebase ของคุณแสดงออกมากขึ้น ดูแลรักษาง่ายขึ้น และทดสอบได้ยากขึ้น