Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การรีแฟคเตอร์แอป Rails ของคุณด้วยออบเจกต์บริการ

ออบเจ็กต์บริการคืออ็อบเจ็กต์ 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 ของคุณแสดงออกมากขึ้น ดูแลรักษาง่ายขึ้น และทดสอบได้ยากขึ้น