Đố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à perform
và execute
.
Đặ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.