Token导航 LogoToken导航TokenDH.com
研究检索external-servicegithub未标认证来源可访问clear审计异常

service-object-patterns服务对象模式

Agent Skill

service-object-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

4,493

周安装

130

GitHub Stars

8

下载量

1,117
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:service-object-patterns(服务对象模式)
来源仓库:https://github.com/kaakati/rails-enterprise-dev
仓库路径:skills/service-object-patterns
安装命令:
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill 'Service Object Patterns'
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill 'Service Object Patterns'

简介

service-object-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。

  • 适用于 Rails 企业级开发中的服务对象建模与业务逻辑封装。
  • 返回符合 RESTful 习惯的对象设计范例与 ActiveRecord 扩展技巧。
  • 部分示例可能包含版本特定语法,请注意 Rails 版本兼容性。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Service Object Patterns Skill

This skill provides comprehensive guidance for implementing Service Objects in Rails applications following consistent patterns and conventions.

When to Use This Skill

  • Creating new service objects for business logic
  • Refactoring fat models or controllers
  • Designing service interfaces
  • Implementing result objects for service responses
  • Organizing services into namespaces

When to Use Service Objects

Use Service Objects When:

  • Business logic spans multiple models
  • Operation has multiple steps/side effects
  • Logic doesn't naturally belong to one model
  • Need to orchestrate external services
  • Complex validation or business rules
  • Operation needs transaction management

Don't Use Service Objects When:

  • Simple CRUD operations
  • Logic clearly belongs to one model
  • Single-line delegation
  • No side effects beyond model updates

Directory Structure

app/services/
├── application_service.rb          # Base class
├── tasks_manager/
│   ├── create_task.rb
│   ├── assign_carrier.rb
│   ├── complete_task.rb
│   └── bundling/
│       ├── bundle_tasks.rb
│       └── optimize_routes.rb
├── billing_manager/
│   ├── generate_invoice.rb
│   ├── process_payment.rb
│   └── calculate_fees.rb
├── notifications_manager/
│   ├── send_sms.rb
│   └── send_push_notification.rb
└── integrations/
    ├── salla/
    │   └── sync_orders.rb
    └── shipping/
        └── create_label.rb

Naming Convention

# Pattern: {Domain}Manager::{Action} or {Domain}Manager::{SubDomain}::{Action}

# Examples:
TasksManager::CreateTask
TasksManager::Bundling::BundleTasks
BillingManager::GenerateInvoice
IntegrationsManager::Salla::SyncOrders

Base Service Class

# app/services/application_service.rb
class ApplicationService
  def self.call(...)
    new(...).call
  end

  private

  attr_reader :params

  def initialize(**params)
    @params = params
  end
end

Basic Service Pattern

# app/services/tasks_manager/create_task.rb
module TasksManager
  class CreateTask < ApplicationService
    def initialize(account:, merchant:, params:)
      @account = account
      @merchant = merchant
      @params = params
    end

    def call
      validate_params!

      ActiveRecord::Base.transaction do
        task = build_task
        assign_zone(task)
        task.save!
        schedule_notifications(task)
        task
      end
    end

    private

    attr_reader :account, :merchant, :params

    def validate_params!
      raise ArgumentError, "Recipient required" unless params[:recipient_id]
      raise ArgumentError, "Address required" unless params[:address]
    end

    def build_task
      account.tasks.build(
        merchant: merchant,
        recipient_id: params[:recipient_id],
        description: params[:description],
        amount: params[:amount],
        status: 'pending'
      )
    end

    def assign_zone(task)
      zone = ZoneFinder.new(account, params[:address]).find
      task.zone = zone
    end

    def schedule_notifications(task)
      TaskNotificationJob.perform_later(task.id)
    end
  end
end

# Usage:
task = TasksManager::CreateTask.call(
  account: current_account,
  merchant: merchant,
  params: task_params
)

Result Object Pattern

For services that need structured success/failure responses:

# app/services/service_result.rb
class ServiceResult
  attr_reader :data, :error, :errors

  def initialize(success:, data: nil, error: nil, errors: [])
    @success = success
    @data = data
    @error = error
    @errors = errors
  end

  def success?
    @success
  end

  def failure?
    !@success
  end

  def self.success(data = nil)
    new(success: true, data: data)
  end

  def self.failure(error = nil, errors: [])
    new(success: false, error: error, errors: errors)
  end
end
# app/services/tasks_manager/assign_carrier.rb
module TasksManager
  class AssignCarrier < ApplicationService
    def initialize(task:, carrier:)
      @task = task
      @carrier = carrier
    end

    def call
      return ServiceResult.failure("Task already assigned") if task.carrier.present?
      return ServiceResult.failure("Carrier not available") unless carrier_available?
      return ServiceResult.failure("Carrier not in zone") unless carrier_in_zone?

      ActiveRecord::Base.transaction do
        task.update!(carrier: carrier, assigned_at: Time.current)
        notify_carrier
        notify_recipient
      end

      ServiceResult.success(task.reload)
    rescue ActiveRecord::RecordInvalid => e
      ServiceResult.failure(e.message, errors: task.errors.full_messages)
    end

    private

    attr_reader :task, :carrier

    def carrier_available?
      carrier.active? && carrier.available?
    end

    def carrier_in_zone?
      return true unless task.zone
      carrier.zones.include?(task.zone)
    end

    def notify_carrier
      CarrierNotificationJob.perform_later(carrier.id, task.id)
    end

    def notify_recipient
      RecipientNotificationJob.perform_later(task.id, :carrier_assigned)
    end
  end
end

# Usage in controller:
result = TasksManager::AssignCarrier.call(task: @task, carrier: @carrier)

if result.success?
  render json: result.data, status: :ok
else
  render json: { error: result.error, errors: result.errors }, status: :unprocessable_entity
end

Dry-Monads Pattern (Alternative)

If using the dry-monads gem:

# Gemfile
gem 'dry-monads'

# app/services/tasks_manager/complete_task.rb
module TasksManager
  class CompleteTask
    include Dry::Monads[:result, :do]

    def initialize(task:, otp:, photos: [])
      @task = task
      @otp = otp
      @photos = photos
    end

    def call
      yield validate_otp
      yield validate_photos
      yield complete_task
      yield process_payment
      yield notify_parties

      Success(task.reload)
    end

    private

    attr_reader :task, :otp, :photos

    def validate_otp
      return Failure(:invalid_otp) unless task.otp == otp
      Success()
    end

    def validate_photos
      return Failure(:photos_required) if task.requires_photos? && photos.empty?
      Success()
    end

    def complete_task
      task.update!(
        status: 'completed',
        completed_at: Time.current
      )
      Success()
    rescue ActiveRecord::RecordInvalid => e
      Failure(e.message)
    end

    def process_payment
      # Payment processing logic
      Success()
    end

    def notify_parties
      TaskCompletionNotificationJob.perform_later(task.id)
      Success()
    end
  end
end

# Usage:
result = TasksManager::CompleteTask.new(task: @task, otp: params[:otp]).call

case result
in Success(task)
  render json: task
in Failure(:invalid_otp)
  render json: { error: "Invalid OTP" }, status: :unprocessable_entity
in Failure(error)
  render json: { error: error }, status: :unprocessable_entity
end

Service Composition

For complex operations that coordinate multiple services:

# app/services/tasks_manager/process_delivery.rb
module TasksManager
  class ProcessDelivery < ApplicationService
    def initialize(task:, carrier:, params:)
      @task = task
      @carrier = carrier
      @params = params
    end

    def call
      ActiveRecord::Base.transaction do
        validate_delivery!
        complete_task!
        process_cod! if task.cod?
        generate_invoice!
        notify_all_parties!
      end

      ServiceResult.success(task.reload)
    rescue StandardError => e
      ServiceResult.failure(e.message)
    end

    private

    attr_reader :task, :carrier, :params

    def validate_delivery!
      result = DeliveryValidator.call(task: task, params: params)
      raise result.error unless result.success?
    end

    def complete_task!
      result = CompleteTask.call(
        task: task,
        otp: params[:otp],
        photos: params[:photos]
      )
      raise result.error unless result.success?
    end

    def process_cod!
      result = BillingManager::ProcessCod.call(
        task: task,
        carrier: carrier,
        amount: task.cod_amount
      )
      raise result.error unless result.success?
    end

    def generate_invoice!
      BillingManager::GenerateInvoice.call(task: task)
    end

    def notify_all_parties!
      NotificationsManager::DeliveryComplete.call(task: task)
    end
  end
end

Service with External API

# app/services/integrations/shipping/create_label.rb
module Integrations
  module Shipping
    class CreateLabel < ApplicationService
      TIMEOUT = 30.seconds

      def initialize(task:, shipping_company:)
        @task = task
        @shipping_company = shipping_company
      end

      def call
        response = make_api_request

        if response.success?
          label = create_label_record(response.body)
          ServiceResult.success(label)
        else
          handle_error(response)
        end
      rescue Faraday::TimeoutError
        ServiceResult.failure("Shipping API timeout")
      rescue Faraday::ConnectionFailed
        ServiceResult.failure("Unable to connect to shipping API")
      end

      private

      attr_reader :task, :shipping_company

      def make_api_request
        client.post('/labels', label_payload)
      end

      def client
        @client ||= Faraday.new(url: shipping_company.api_url) do |f|
          f.request :json
          f.response :json
          f.options.timeout = TIMEOUT
          f.headers['Authorization'] = "Bearer #{shipping_company.api_key}"
        end
      end

      def label_payload
        {
          sender: sender_details,
          recipient: recipient_details,
          package: package_details
        }
      end

      def create_label_record(response_body)
        task.create_shipping_label!(
          tracking_number: response_body['tracking_number'],
          label_url: response_body['label_url'],
          shipping_company: shipping_company
        )
      end

      def handle_error(response)
        error_message = response.body['error'] || "API Error: #{response.status}"
        Rails.logger.error("Shipping API Error: #{error_message}")
        ServiceResult.failure(error_message)
      end
    end
  end
end

Service with Background Jobs

# app/services/tasks_manager/bulk_import.rb
module TasksManager
  class BulkImport < ApplicationService
    def initialize(account:, file:, user:)
      @account = account
      @file = file
      @user = user
    end

    def call
      import = create_import_record
      schedule_processing(import)
      ServiceResult.success(import)
    end

    private

    attr_reader :account, :file, :user

    def create_import_record
      account.task_imports.create!(
        file: file,
        user: user,
        status: 'pending',
        total_rows: count_rows
      )
    end

    def schedule_processing(import)
      BulkImportJob.perform_later(import.id)
    end

    def count_rows
      # Count rows in uploaded file
      CSV.read(file.path).count - 1  # Minus header
    end
  end
end

Testing Services

# spec/services/tasks_manager/create_task_spec.rb
require 'rails_helper'

RSpec.describe TasksManager::CreateTask do
  let(:account) { create(:account) }
  let(:merchant) { create(:merchant, account: account) }
  let(:recipient) { create(:recipient, account: account) }

  let(:valid_params) do
    {
      recipient_id: recipient.id,
      description: "Test delivery",
      amount: 100,
      address: "123 Test St"
    }
  end

  describe '.call' do
    context 'with valid params' do
      it 'creates a task' do
        expect {
          described_class.call(
            account: account,
            merchant: merchant,
            params: valid_params
          )
        }.to change(Task, :count).by(1)
      end

      it 'assigns the zone' do
        task = described_class.call(
          account: account,
          merchant: merchant,
          params: valid_params
        )

        expect(task.zone).to be_present
      end

      it 'schedules notification' do
        expect {
          described_class.call(
            account: account,
            merchant: merchant,
            params: valid_params
          )
        }.to have_enqueued_job(TaskNotificationJob)
      end
    end

    context 'with invalid params' do
      it 'raises error without recipient' do
        invalid_params = valid_params.except(:recipient_id)

        expect {
          described_class.call(
            account: account,
            merchant: merchant,
            params: invalid_params
          )
        }.to raise_error(ArgumentError, "Recipient required")
      end
    end
  end
end

Service Interface Guidelines

Method Visibility

class MyService
  # PUBLIC: Only .call is public (entry point)
  def self.call(...)
    new(...).call
  end

  def call
    # Main logic
  end

  private

  # PRIVATE: All other methods are private
  attr_reader :params

  def validate!
    # validation
  end

  def process
    # processing
  end
end

Input Validation

def initialize(user:, params:)
  @user = user
  @params = params
  validate_input!
end

private

def validate_input!
  raise ArgumentError, "User required" unless @user
  raise ArgumentError, "Params required" unless @params
end

Transaction Management

def call
  ActiveRecord::Base.transaction do
    step_one
    step_two
    step_three
  end
rescue ActiveRecord::RecordInvalid => e
  # Handle validation errors
  ServiceResult.failure(e.message)
rescue StandardError => e
  # Handle other errors
  Rails.logger.error("Service error: #{e.message}")
  ServiceResult.failure("An error occurred")
end

Comprehensive Error Handling

Custom Error Classes

# app/services/errors.rb
module Services
  module Errors
    class ServiceError < StandardError
      attr_reader :context

      def initialize(message = nil, context: {})
        @context = context
        super(message)
      end
    end

    class ValidationError < ServiceError; end
    class AuthorizationError < ServiceError; end
    class ExternalServiceError < ServiceError; end
    class TimeoutError < ServiceError; end
    class RateLimitError < ServiceError; end
    class ResourceNotFoundError < ServiceError; end
  end
end

# Usage in service
module TasksManager
  class CreateTask < ApplicationService
    def call
      validate_authorization!
      validate_params!

      task = build_and_save_task
      ServiceResult.success(task)
    rescue Services::Errors::ValidationError => e
      ServiceResult.failure(e.message, errors: e.context[:errors])
    rescue Services::Errors::AuthorizationError => e
      ServiceResult.failure("Not authorized", context: e.context)
    end

    private

    def validate_authorization!
      unless @user.can?(:create_task, @account)
        raise Services::Errors::AuthorizationError.new(
          "User not authorized to create tasks",
          context: { user_id: @user.id, account_id: @account.id }
        )
      end
    end

    def validate_params!
      errors = []
      errors << "Recipient required" unless @params[:recipient_id]
      errors << "Address required" unless @params[:address]

      if errors.any?
        raise Services::Errors::ValidationError.new(
          "Validation failed",
          context: { errors: errors }
        )
      end
    end
  end
end

Error Handler Concern

# app/services/concerns/error_handling.rb
module Services
  module Concerns
    module ErrorHandling
      extend ActiveSupport::Concern

      included do
        rescue_from StandardError, with: :handle_standard_error
        rescue_from ActiveRecord::RecordInvalid, with: :handle_record_invalid
        rescue_from Services::Errors::ServiceError, with: :handle_service_error
      end

      private

      def handle_standard_error(exception)
        log_error(exception)
        track_error(exception)
        ServiceResult.failure("An unexpected error occurred")
      end

      def handle_record_invalid(exception)
        log_error(exception)
        ServiceResult.failure(
          "Validation failed",
          errors: exception.record.errors.full_messages
        )
      end

      def handle_service_error(exception)
        log_error(exception, context: exception.context)
        ServiceResult.failure(exception.message, context: exception.context)
      end

      def log_error(exception, context: {})
        Rails.logger.error({
          error_class: exception.class.name,
          error_message: exception.message,
          backtrace: exception.backtrace.first(5),
          context: context,
          service: self.class.name
        }.to_json)
      end

      def track_error(exception)
        # Integrate with error tracking service (Sentry, Rollbar, etc.)
        if defined?(Sentry)
          Sentry.capture_exception(exception, extra: {
            service: self.class.name,
            params: sanitized_params
          })
        end
      end

      def sanitized_params
        # Remove sensitive data before logging
        @params.except(:password, :token, :api_key)
      end
    end
  end
end

# Usage
module TasksManager
  class CreateTask < ApplicationService
    include Services::Concerns::ErrorHandling

    def call
      # Implementation
    end
  end
end

Retry Mechanisms

# app/services/concerns/retriable.rb
module Services
  module Concerns
    module Retriable
      extend ActiveSupport::Concern

      RETRYABLE_ERRORS = [
        Faraday::TimeoutError,
        Faraday::ConnectionFailed,
        Services::Errors::TimeoutError,
        ActiveRecord::Deadlocked
      ].freeze

      def with_retry(max_attempts: 3, backoff: 2, &block)
        attempt = 1

        begin
          yield
        rescue *RETRYABLE_ERRORS => e
          if attempt < max_attempts
            sleep_duration = backoff**attempt
            Rails.logger.warn(
              "Retrying after error (attempt #{attempt}/#{max_attempts}): #{e.message}. " \
              "Sleeping #{sleep_duration}s"
            )
            sleep(sleep_duration)
            attempt += 1
            retry
          else
            Rails.logger.error("Max retry attempts (#{max_attempts}) exceeded: #{e.message}")
            raise
          end
        end
      end
    end
  end
end

# Usage
module Integrations
  module Shipping
    class CreateLabel < ApplicationService
      include Services::Concerns::Retriable

      def call
        with_retry(max_attempts: 3, backoff: 2) do
          response = make_api_request
          process_response(response)
        end
      rescue Faraday::TimeoutError => e
        ServiceResult.failure("Shipping API timeout after retries")
      end
    end
  end
end

Circuit Breaker Pattern

# app/services/concerns/circuit_breaker.rb
module Services
  module Concerns
    module CircuitBreaker
      extend ActiveSupport::Concern

      class_methods do
        def circuit_breaker(service_name, failure_threshold: 5, timeout: 60)
          @circuit_state ||= {}
          @circuit_state[service_name] ||= {
            failures: 0,
            last_failure_time: nil,
            state: :closed  # :closed, :open, :half_open
          }
        end

        def circuit_open?(service_name)
          circuit = @circuit_state[service_name]
          return false unless circuit

          if circuit[:state] == :open
            # Check if timeout period has passed
            if Time.current - circuit[:last_failure_time] > circuit[:timeout]
              circuit[:state] = :half_open
              false
            else
              true
            end
          else
            false
          end
        end

        def record_success(service_name)
          circuit = @circuit_state[service_name]
          return unless circuit

          circuit[:failures] = 0
          circuit[:state] = :closed
        end

        def record_failure(service_name)
          circuit = @circuit_state[service_name]
          return unless circuit

          circuit[:failures] += 1
          circuit[:last_failure_time] = Time.current

          if circuit[:failures] >= circuit[:failure_threshold]
            circuit[:state] = :open
            Rails.logger.warn("Circuit breaker opened for #{service_name}")
          end
        end
      end

      def with_circuit_breaker(service_name, &block)
        if self.class.circuit_open?(service_name)
          raise Services::Errors::ExternalServiceError.new(
            "Circuit breaker open for #{service_name}",
            context: { service: service_name }
          )
        end

        result = yield
        self.class.record_success(service_name)
        result
      rescue StandardError => e
        self.class.record_failure(service_name)
        raise
      end
    end
  end
end

# Usage
module Integrations
  module Shipping
    class CreateLabel < ApplicationService
      include Services::Concerns::CircuitBreaker

      circuit_breaker :shipping_api, failure_threshold: 5, timeout: 60

      def call
        with_circuit_breaker(:shipping_api) do
          response = make_api_request
          process_response(response)
        end
      rescue Services::Errors::ExternalServiceError => e
        ServiceResult.failure(e.message)
      end
    end
  end
end

Instrumentation & Monitoring

Performance Instrumentation

# app/services/concerns/instrumentation.rb
module Services
  module Concerns
    module Instrumentation
      extend ActiveSupport::Concern

      included do
        around_action :instrument_service_call, only: :call
      end

      private

      def instrument_service_call
        start_time = Time.current
        service_name = self.class.name.underscore.tr('/', '.')

        ActiveSupport::Notifications.instrument(
          "service.call",
          service: service_name,
          params: sanitized_params
        ) do
          result = yield

          duration = (Time.current - start_time) * 1000  # Convert to ms

          log_performance(service_name, duration, result)
          track_metrics(service_name, duration, result)

          result
        end
      end

      def log_performance(service_name, duration, result)
        Rails.logger.info({
          service: service_name,
          duration_ms: duration.round(2),
          success: result.success?,
          timestamp: Time.current.iso8601
        }.to_json)
      end

      def track_metrics(service_name, duration, result)
        # StatsD integration
        if defined?(StatsD)
          StatsD.increment("service.calls", tags: [
            "service:#{service_name}",
            "status:#{result.success? ? 'success' : 'failure'}"
          ])

          StatsD.timing("service.duration", duration, tags: [
            "service:#{service_name}"
          ])
        end

        # Prometheus integration
        if defined?(PrometheusExporter)
          PrometheusExporter::Client.default.send_json(
            type: "service_call",
            service: service_name,
            duration: duration,
            success: result.success?
          )
        end
      end
    end
  end
end

Structured Logging

# app/services/concerns/loggable.rb
module Services
  module Concerns
    module Loggable
      extend ActiveSupport::Concern

      private

      def log_info(message, context = {})
        log(:info, message, context)
      end

      def log_warn(message, context = {})
        log(:warn, message, context)
      end

      def log_error(message, context = {})
        log(:error, message, context)
      end

      def log_debug(message, context = {})
        log(:debug, message, context)
      end

      def log(level, message, context = {})
        Rails.logger.public_send(level, {
          service: self.class.name,
          message: message,
          timestamp: Time.current.iso8601,
          **context
        }.to_json)
      end

      def log_service_start(context = {})
        log_info("Service started", {
          params: sanitized_params,
          **context
        })
      end

      def log_service_complete(result, context = {})
        log_info("Service completed", {
          success: result.success?,
          duration_ms: context[:duration_ms],
          **context
        })
      end

      def log_external_api_call(api_name, endpoint, context = {})
        log_info("External API call", {
          api: api_name,
          endpoint: endpoint,
          **context
        })
      end
    end
  end
end

# Usage
module Integrations
  module Shipping
    class CreateLabel < ApplicationService
      include Services::Concerns::Loggable

      def call
        log_service_start(task_id: @task.id)
        start_time = Time.current

        log_external_api_call("Shipping API", "/labels", {
          task_id: @task.id,
          shipping_company: @shipping_company.name
        })

        response = make_api_request
        result = process_response(response)

        duration = (Time.current - start_time) * 1000
        log_service_complete(result, duration_ms: duration)

        result
      end
    end
  end
end

Metrics Collection

# app/services/concerns/metrics.rb
module Services
  module Concerns
    module Metrics
      extend ActiveSupport::Concern

      def track_counter(metric_name, value = 1, tags: {})
        if defined?(StatsD)
          StatsD.increment(
            "service.#{metric_name}",
            value,
            tags: format_tags(tags)
          )
        end
      end

      def track_gauge(metric_name, value, tags: {})
        if defined?(StatsD)
          StatsD.gauge(
            "service.#{metric_name}",
            value,
            tags: format_tags(tags)
          )
        end
      end

      def track_timing(metric_name, duration_ms, tags: {})
        if defined?(StatsD)
          StatsD.timing(
            "service.#{metric_name}",
            duration_ms,
            tags: format_tags(tags)
          )
        end
      end

      def track_histogram(metric_name, value, tags: {})
        if defined?(StatsD)
          StatsD.histogram(
            "service.#{metric_name}",
            value,
            tags: format_tags(tags)
          )
        end
      end

      private

      def format_tags(tags)
        default_tags.merge(tags).map { |k, v| "#{k}:#{v}" }
      end

      def default_tags
        {
          service: self.class.name.underscore.tr('/', '.'),
          environment: Rails.env
        }
      end
    end
  end
end

# Usage
module TasksManager
  class CreateTask < ApplicationService
    include Services::Concerns::Metrics

    def call
      start_time = Time.current

      task = build_and_save_task
      track_counter("task.created", tags: { merchant_id: @merchant.id })

      duration = (Time.current - start_time) * 1000
      track_timing("task.creation_time", duration)

      ServiceResult.success(task)
    end
  end
end

ActiveSupport Notifications

# app/services/tasks_manager/create_task.rb
module TasksManager
  class CreateTask < ApplicationService
    def call
      ActiveSupport::Notifications.instrument(
        "task.create",
        account_id: @account.id,
        merchant_id: @merchant.id
      ) do |payload|
        task = build_and_save_task

        payload[:task_id] = task.id
        payload[:zone_id] = task.zone_id

        ServiceResult.success(task)
      end
    end
  end
end

# config/initializers/service_notifications.rb
ActiveSupport::Notifications.subscribe("task.create") do |name, start, finish, id, payload|
  duration = (finish - start) * 1000

  Rails.logger.info({
    event: name,
    duration_ms: duration.round(2),
    account_id: payload[:account_id],
    task_id: payload[:task_id]
  }.to_json)

  # Send metrics
  StatsD.increment("task.created", tags: [
    "account:#{payload[:account_id]}",
    "merchant:#{payload[:merchant_id]}"
  ]) if defined?(StatsD)

  StatsD.timing("task.creation_duration", duration, tags: [
    "account:#{payload[:account_id]}"
  ]) if defined?(StatsD)
end

Error Tracking Integration

# app/services/concerns/error_tracking.rb
module Services
  module Concerns
    module ErrorTracking
      extend ActiveSupport::Concern

      private

      def capture_exception(exception, context: {})
        # Sentry integration
        if defined?(Sentry)
          Sentry.capture_exception(exception, extra: {
            service: self.class.name,
            context: context,
            params: sanitized_params
          })
        end

        # Rollbar integration
        if defined?(Rollbar)
          Rollbar.error(exception, {
            service: self.class.name,
            context: context,
            params: sanitized_params
          })
        end

        # Airbrake integration
        if defined?(Airbrake)
          Airbrake.notify(exception, {
            service: self.class.name,
            context: context,
            params: sanitized_params
          })
        end
      end

      def capture_message(message, level: :info, context: {})
        if defined?(Sentry)
          Sentry.capture_message(message, level: level, extra: {
            service: self.class.name,
            context: context
          })
        end
      end
    end
  end
end

# Usage
module TasksManager
  class CreateTask < ApplicationService
    include Services::Concerns::ErrorTracking

    def call
      task = build_and_save_task
      ServiceResult.success(task)
    rescue StandardError => e
      capture_exception(e, context: {
        account_id: @account.id,
        merchant_id: @merchant.id,
        params: @params
      })
      ServiceResult.failure("Failed to create task")
    end
  end
end

Comprehensive Service Template

# app/services/tasks_manager/create_task.rb
module TasksManager
  class CreateTask < ApplicationService
    include Services::Concerns::ErrorHandling
    include Services::Concerns::Retriable
    include Services::Concerns::Instrumentation
    include Services::Concerns::Loggable
    include Services::Concerns::Metrics
    include Services::Concerns::ErrorTracking

    def initialize(account:, merchant:, params:, user:)
      @account = account
      @merchant = merchant
      @params = params
      @user = user
    end

    def call
      log_service_start(account_id: @account.id, merchant_id: @merchant.id)
      start_time = Time.current

      validate_authorization!
      validate_params!

      task = with_retry(max_attempts: 3) do
        build_and_save_task
      end

      track_counter("task.created", tags: { merchant_id: @merchant.id })
      duration = (Time.current - start_time) * 1000
      track_timing("task.creation_time", duration)

      log_service_complete(ServiceResult.success(task), duration_ms: duration)

      ServiceResult.success(task)
    rescue Services::Errors::ServiceError => e
      capture_exception(e, context: { account_id: @account.id })
      ServiceResult.failure(e.message, context: e.context)
    rescue StandardError => e
      log_error("Unexpected error", error: e.message, backtrace: e.backtrace.first(5))
      capture_exception(e, context: { account_id: @account.id })
      ServiceResult.failure("An unexpected error occurred")
    end

    private

    attr_reader :account, :merchant, :params, :user

    def validate_authorization!
      unless user.can?(:create_task, account)
        raise Services::Errors::AuthorizationError.new(
          "User not authorized",
          context: { user_id: user.id, account_id: account.id }
        )
      end
    end

    def validate_params!
      errors = []
      errors << "Recipient required" unless params[:recipient_id]
      errors << "Address required" unless params[:address]

      if errors.any?
        raise Services::Errors::ValidationError.new(
          "Validation failed",
          context: { errors: errors }
        )
      end
    end

    def build_and_save_task
      ActiveRecord::Base.transaction do
        task = account.tasks.build(
          merchant: merchant,
          recipient_id: params[:recipient_id],
          description: params[:description],
          amount: params[:amount],
          status: 'pending'
        )

        assign_zone(task)
        task.save!

        log_info("Task created", task_id: task.id)
        schedule_notifications(task)

        task
      end
    end

    def assign_zone(task)
      zone = ZoneFinder.new(account, params[:address]).find
      task.zone = zone
      log_debug("Zone assigned", zone_id: zone&.id)
    end

    def schedule_notifications(task)
      TaskNotificationJob.perform_later(task.id)
      log_debug("Notifications scheduled", task_id: task.id)
    end
  end
end

Pre-Creation Checklist

Before creating a service:

# 1. Check existing service structure
ls app/services/
ls app/services/*/ 2>/dev/null

# 2. Review existing service patterns
head -50 $(find app/services -name '*.rb' | head -1)

# 3. Check naming conventions
grep -r 'class.*Manager' app/services/ --include='*.rb' | head -10

# 4. Verify namespace exists
ls app/services/{namespace}/ 2>/dev/null

# 5. Check for existing concerns
ls app/services/concerns/ 2>/dev/null

# 6. Review error handling patterns
grep -r 'ServiceError' app/services/ --include='*.rb' | head -5

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

27.04%
按下载量换算302

windsurf

24.28%
按下载量换算271

OpenCode

16.2%
按下载量换算181

Codex

14.41%
按下载量换算161

Antigravity

7.27%
按下载量换算81

Gemini CLI

3.55%
按下载量换算40

安全审计

Gen Agent Trust Hub

未通过

Socket

通过

Snyk

通过

权限和风险

external-service

该 Skill 可能调用第三方服务、云服务或外部模型 API,使用前需要确认账号、额度、数据发送范围和服务条款。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。来源字段存在多来源差异,先按来源优先级自动处理,无法消解时进入异常复核队列。

来源信息

继续浏览同类 Skills