Computer >> Máy Tính >  >> Lập trình >> Ruby

Cấu trúc lại ứng dụng Rails của bạn với các đối tượng dịch vụ

Đối tượng dịch vụ là một đối tượng Ruby thực hiện một hành động duy nhất. Nó gói gọn một quy trình trong miền hoặc logic nghiệp vụ của bạn. Hãy tưởng tượng rằng bạn cần tạo một phiên bản sách trong một ứng dụng thư viện tưởng tượng; trong một ứng dụng Rails đơn giản, bạn sẽ làm như sau:

class BookController < ApplicationController
  def create
    Book.new(*args)
  end
end

Điều này là tốt cho những điều đơn giản. Tuy nhiên, khi ứng dụng phát triển, bạn có thể kết thúc với rất nhiều bảng ghi sẵn xung quanh nó:

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

Các đối tượng dịch vụ cho phép bạn trừu tượng hóa hành vi này thành một lớp riêng biệt. Sau đó, mã của bạn trở nên đơn giản trở lại:

class BookController < ApplicationController
  def
    BookCreator.create_book
  end
end

Tại sao bạn cần đối tượng dịch vụ

Rails được thiết kế để hỗ trợ nguyên bản cấu trúc tổ chức MVC (ví dụ:mô hình, bộ điều khiển, chế độ xem và trình trợ giúp). Cấu trúc này phù hợp cho các ứng dụng đơn giản. Tuy nhiên, khi ứng dụng của bạn phát triển, bạn có thể bắt đầu thấy miền / logic nghiệp vụ nằm rải rác trên các mô hình và bộ điều khiển. Các lôgic như vậy không thuộc về bộ điều khiển hoặc mô hình, vì vậy chúng làm cho mã khó sử dụng lại và bảo trì. Đối tượng dịch vụ Rails là một mẫu có thể giúp bạn tách logic nghiệp vụ khỏi bộ điều khiển và mô hình, cho phép các mô hình chỉ đơn giản là các lớp dữ liệu và là điểm nhập bộ điều khiển tới API của bạn.

Chúng tôi nhận được rất nhiều lợi ích khi giới thiệu các dịch vụ đóng gói logic nghiệp vụ, bao gồm những điều sau:

  • Bộ điều khiển Lean Rails - Bộ điều khiển chỉ chịu trách nhiệm hiểu các yêu cầu và biến các tham số, phiên và cookie yêu cầu thành các đối số được chuyển vào đối tượng dịch vụ để thực hiện. Sau đó, bộ điều khiển chuyển hướng hoặc hiển thị theo phản hồi của dịch vụ. Ngay cả trong các ứng dụng lớn, các hành động của bộ điều khiển sử dụng các đối tượng dịch vụ thường không quá 10 dòng mã.

  • Bộ điều khiển có thể kiểm tra - Vì bộ điều khiển tinh gọn và đóng vai trò là cộng tác viên của dịch vụ, nên việc kiểm tra trở nên thực sự dễ dàng, vì chúng tôi chỉ có thể kiểm tra xem một số phương pháp nhất định trong bộ điều khiển có được gọi hay không khi một hành động nhất định xảy ra.

  • Khả năng kiểm tra quy trình kinh doanh một cách riêng biệt - Các dịch vụ dễ dàng và nhanh chóng để kiểm tra vì chúng là các đối tượng Ruby nhỏ đã được tách biệt khỏi môi trường của chúng. Chúng tôi có thể dễ dàng khai báo tất cả các cộng tác viên và chỉ kiểm tra xem các bước nhất định có được thực hiện trong dịch vụ của chúng tôi hay không.

  • Dịch vụ có thể sử dụng lại - Các đối tượng dịch vụ có thể được gọi bởi bộ điều khiển, các đối tượng dịch vụ khác, các công việc được xếp hàng đợi, v.v.

  • Tách biệt giữa khuôn khổ và miền doanh nghiệp - Bộ điều khiển Rails chỉ nhìn thấy các dịch vụ và tương tác với đối tượng miền bằng cách sử dụng chúng. Việc giảm khớp nối này làm cho khả năng mở rộng dễ dàng hơn, đặc biệt là khi bạn muốn chuyển từ thiết bị nguyên khối sang dịch vụ siêu nhỏ. Các dịch vụ của bạn có thể dễ dàng được trích xuất và chuyển sang một dịch vụ mới với sự sửa đổi tối thiểu.

Tạo đối tượng dịch vụ

Trước tiên, hãy tạo BookCreator mới trong một thư mục mới có tên là app / services cho ứng dụng quản lý thư viện tưởng tượng:

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

Tiếp theo, chúng ta hãy đặt tất cả logic của chúng ta bên trong một lớp Ruby mới:

# 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

Sau đó, chúng ta có thể gọi đối tượng dịch vụ trong bộ điều khiển hoặc bất kỳ nơi nào trong ứng dụng:

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

Đường tổng hợp đối tượng dịch vụ

Chúng ta có thể đơn giản hóa BookCreator.new(arguments).create chuỗi bằng cách thêm một phương thức lớp khởi tạo BookCreator và gọi create phương pháp cho chúng tôi:

# 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

Trong bộ điều khiển, người tạo sách bây giờ có thể được gọi như sau:

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

Để giữ cho mã của chúng tôi KHÔ (Không lặp lại chính bạn) và sử dụng lại hành vi này với các đối tượng dịch vụ khác, chúng tôi có thể tóm tắt lệnh gọi call phương thức thành một cơ sở ApplicationService lớp mà mọi đối tượng dịch vụ sẽ kế thừa từ:

class ApplicationService
  self.call(*args)
      new(*args).call
  end
end

Với mã này, chúng tôi có thể cấu trúc lại BookCreator kế thừa từ 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

Tạo Đối tượng Dịch vụ bằng BusinessProcess Gem

Với đá quý BusinessProcess, bạn không phải tạo lớp dịch vụ ứng dụng cơ sở hoặc xác định initialize bởi vì gem có tất cả các cấu hình này được tích hợp sẵn trong nó. Đối tượng dịch vụ của bạn chỉ phải kế thừa từ BusinessProcess::Base .

Trong tệp đá quý của bạn, hãy thêm phần sau:

gem 'business_process'

Và sau đó chạy bundle lệnh trong thiết bị đầu cuối của bạn

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

Hướng dẫn tạo Đối tượng dịch vụ tốt

Một phương pháp công khai

Một đối tượng dịch vụ được cho là thực hiện một hành động nghiệp vụ và thực hiện tốt hành động đó, vì vậy nó chỉ nên hiển thị một phương thức công khai duy nhất để thực hiện điều đó. Các phương thức khác phải là private và được gọi bởi phương thức public. Bạn có thể chọn đặt tên cho phương thức công khai bất cứ điều gì bạn muốn, miễn là cách đặt tên đó nhất quán trên tất cả các đối tượng dịch vụ. Trong ví dụ của chúng tôi, chúng tôi đã đặt tên nó là call . Các tên thông thường khác là performexecute .

Đặt tên cho các đối tượng dịch vụ theo vai trò mà chúng thực hiện

Tên của một đối tượng dịch vụ phải cho biết nó làm gì. Có một cách phổ biến để đặt tên cho các đối tượng dịch vụ với các từ kết thúc bằng “hoặc” và “er”. Ví dụ:nếu công việc của đối tượng dịch vụ là tạo một cuốn sách, hãy đặt tên nó là BookCreator và nếu công việc là đọc một cuốn sách, hãy đặt tên nó là BookReader.

Không khởi tạo trực tiếp các đối tượng dịch vụ

Sử dụng các phần trừu tượng như mẫu đường cú pháp hoặc đá quý như BusinessProcess để rút ngắn ký hiệu của việc gọi các đối tượng dịch vụ. Sử dụng phương pháp này sẽ cho phép bạn đơn giản hóa BookCreator.new(*args).call hoặc BookCreator.new.call(*args) vào BookCreator.call(*args), ngắn hơn và dễ đọc hơn.

Nhóm các đối tượng dịch vụ trong không gian tên

Giới thiệu các đối tượng dịch vụ, đặc biệt là trong một ứng dụng lớn, có nghĩa là bạn sẽ phát triển từ một đối tượng dịch vụ lên hàng chục đối tượng dịch vụ. Để cải thiện tổ chức mã, bạn nên nhóm các đối tượng dịch vụ chung vào không gian tên. Ví dụ, trong ứng dụng thư viện, chúng tôi sẽ nhóm tất cả các dịch vụ liên quan đến sách lại với nhau và nhóm tất cả các dịch vụ liên quan đến tác giả trong một không gian tên riêng biệt. Cấu trúc thư mục của chúng ta bây giờ sẽ giống như sau:

services
├── application_service.rb
└── book
├── book_creator.rb
└── book_reader.rb

Các đối tượng dịch vụ của chúng tôi sẽ giống như sau:

# 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

Các lệnh gọi của chúng ta bây giờ sẽ trở thành Book::BookCreator.call(*args) and Book::BookReader.call(*args) .

Một trách nhiệm cho mỗi đối tượng dịch vụ

Có một đối tượng dịch vụ làm nhiều hơn một việc đi ngược lại với tư duy "hành động kinh doanh" của các đối tượng dịch vụ. Thông thường, không khuyến khích có một đối tượng dịch vụ chung thực hiện nhiều hành động. Nếu bạn muốn chia sẻ mã giữa các đối tượng dịch vụ, hãy tạo mô-đun cơ sở hoặc mô-đun trợ giúp và sử dụng các mixin để đưa vào các đối tượng dịch vụ của bạn.

Giải cứu các ngoại lệ và nâng các ngoại lệ tùy chỉnh

Mục đích của đối tượng dịch vụ là đóng gói các chi tiết triển khai bên trong nó, chẳng hạn như tương tác giữa các dịch vụ hoặc thư viện của bên thứ ba hoặc thao tác cơ sở dữ liệu với Rails ActiveRecord. Nếu xảy ra lỗi, chẳng hạn như ActiveRecord ::RecordNotUnique, trong khi tương tác với ActiveRecord, dịch vụ cần giải cứu ngoại lệ đúng cách. Lỗi không được phép lan truyền lên ngăn xếp cuộc gọi. Nếu nó không thể được xử lý trong khối cứu hộ, hãy nêu ra một ngoại lệ được xác định tùy chỉnh cụ thể cho đối tượng dịch vụ đó.

Kết luận

Mẫu đối tượng dịch vụ có thể cải thiện đáng kể thiết kế tổng thể của ứng dụng khi bạn thêm các tính năng mới vào ứng dụng của mình. Nó sẽ làm cho cơ sở mã của bạn trở nên biểu cảm hơn, dễ bảo trì hơn và ít khó kiểm tra hơn.