What is a service?

  • In Convenient Service, a service is a plain Ruby class that includes a config (ConvenientService::Standard::Config most of the time).

  • A service either implements result directly, or composes other services via steps.

    ##
    # Without steps.
    #
    class Service
      include ConvenientService::Standard::Config
    
      def result
        success(value: 42)
      end
    end
    
    ##
    # With steps.
    #
    class OrganizerService
      include ConvenientService::Standard::Config
    
      step Service, out: :value
    end
    
    result = OrganizerService.result
    
    result.success?
    # => true
    
    result.data[:value]
    # => 42
  • Invoking a service always returns a result object, not a raw value. It is inspired by JSend, but not JSend-compatible. Its status is always one of success, failure, or error.

    class FindUser
      include ConvenientService::Standard::Config
    
      attr_reader :id
    
      def initialize(id:)
        @id = id
      end
    
      def result
        return error("Id is `nil`") if id.nil?
    
        users = {1 => {name: "John"}}
    
        return failure("User with id `#{id}` does not exist") unless users.key?(id)
    
        success(user: users[id])
      end
    end
    
    FindUser.result(id: 1).success?
    # => true
    
    FindUser.result(id: 2).failure?
    # => true
    
    FindUser.result(id: nil).error?
    # => true
  • failure is for an anticipated negative outcome (a business rule was not met, e.g. a user was not found).

  • error is for an unexpected condition (e.g. an invalid argument was passed).

  • Besides result, a service can also be called with call, at the class or instance level.

  • result always returns the full result object. call returns the result's data hash on success, nil on failure, and raises on error.

    FindUser.call(id: 1)
    # => {user: {name: "John"}}
    
    FindUser.call(id: 2)
    # => nil
    
    FindUser.call(id: nil)
    # raises ConvenientService::Result::Exceptions::ErrorResultIsCalled
    
    FindUser.new(id: 1).call
    # => {user: {name: "John"}}
  • The first style is a regular service; the second is an organizer service.

See also

Sources