Làm thế nào để bạn xử lý các ngoại lệ trong Rails?

Xử lý các ngoại lệ trong các ứng dụng API của bạn là một điều khá quan trọng và nếu bạn muốn giữ mọi thứ KHÔ, bạn nên nghĩ cách thực hiện nó theo cách thích hợp. Trong khóa học API Ruby on Rails của chúng tôi, tôi đã chỉ ra cách triển khai xử lý lỗi bằng cách sử dụng ErrorSerializer và ActiveModelSerializers gem và ở đây tôi sẽ chỉ cho bạn cách tiếp cận tốt hơn nữa đối với chủ đề này khi bạn có thể thống nhất MỌI lỗi trên toàn bộ ứng dụng API

Làm thế nào để bạn xử lý các ngoại lệ trong Rails?

API REST của Ruby On Rails
hướng dẫn đầy đủ

Tạo các ứng dụng API chuyên nghiệp mà bạn có thể kết nối mọi thứ vào. Tìm hiểu cách viết mã như các chuyên gia bằng cách sử dụng Phát triển theo hướng thử nghiệm

Tham gia khóa học này

CẬP NHẬT. Gần đây, tôi đã có một cách thậm chí còn hiệu quả hơn để xử lý Lỗi trong các ứng dụng Web Rails bằng cách sử dụng "dry-monads". Nó vẫn sử dụng phương pháp này để tuần tự hóa các lỗi cho JSON. mục đích API, nhưng ánh xạ thực tế có thể được thực hiện theo cách gọn gàng hơn

Cách tiếp cận cuối cùng

Không cần thiết phải đề cập đến toàn bộ quá trình suy nghĩ về cách chúng tôi đi đến kết quả cuối cùng, nhưng nếu bạn quan tâm đến bất kỳ phần cụ thể nào, hãy nói điều đó trong phần nhận xét. Các giả định cơ bản là giữ cho mọi thứ KHÔ và thống nhất trên toàn bộ ứng dụng

Vì vậy, đây là mã

lỗi tiêu chuẩn
    # app/lib/errors/standard_error.rb

    module Errors
      class StandardError < ::StandardError
        def initialize(title: nil, detail: nil, status: nil, source: {})
          @title = title || "Something went wrong"
          @detail = detail || "We encountered unexpected error, but our developers had been already notified about it"
          @status = status || 500
          @source = source.deep_stringify_keys
        end

        def to_h
          {
            status: status,
            title: title,
            detail: detail,
            source: source
          }
        end

        def serializable_hash
          to_h
        end

        def to_s
          to_h.to_s
        end

        attr_reader :title, :detail, :status, :source
      end
    end

Trước hết, chúng tôi cần có lỗi Cơ sở, đây sẽ là lỗi dự phòng cho bất kỳ ngoại lệ nào do ứng dụng của chúng tôi đưa ra. Khi chúng tôi sử dụng API JSON trong phản hồi của mọi máy chủ, chúng tôi muốn luôn trả về lỗi ở định dạng mà API JSON mô tả

Chúng tôi đã trích xuất tất cả các phần dành riêng cho lỗi cho mọi mã trạng thái HTML mà chúng tôi muốn hỗ trợ, có dự phòng là 500

Lỗi chi tiết hơn

Như bạn có thể thấy lỗi cơ bản này chỉ là một giàn giáo mà chúng ta có thể sử dụng để ghi đè lên các thuộc tính cụ thể của đối tượng lỗi. Sau khi triển khai điều đó, chúng tôi có thể khởi tạo một số lỗi cụ thể theo trường hợp để gửi thông báo mô tả nhiều hơn cho khách hàng của chúng tôi

    # app/lib/errors/unauthorized.rb

    module Errors
      class Unauthorized < Errors::StandardError
        def initialize
          super(
            title: "Unauthorized",
            status: 401,
            detail: message || "You need to login to authorize this request.",
            source: { pointer: "/request/headers/authorization" }
          )
        end
      end
    end



    # app/lib/errors/not_found.rb

    module Errors
      class NotFound < Errors::StandardError
        def initialize
          super(
            title: "Record not Found",
            status: 404,
            detail: "We could not find the object you were looking for.",
            source: { pointer: "/request/url/:id" }
          )
        end
      end
    end

Tất cả các lỗi đều rất rõ ràng và nhỏ, không có bất kỳ logic không cần thiết nào liên quan. Điều đó hợp lý vì chúng ta không muốn cho họ cơ hội thất bại một cách bất ngờ, phải không?

Dù sao việc xác định các đối tượng lỗi chỉ là một nửa công việc

Nối tiếp lỗi trong Ứng dụng Ruby

Cách tiếp cận trên cho phép chúng tôi sử dụng một cái gì đó như

    ...
    def show
      Article.find(params[:id])
    rescue ActiveRecord::RecordNotFound
      e = Errors::NotFound.new
      render json: ErrorSerializer.new(e), status: e.status
    end
    ...

Để tuần tự hóa các phản hồi tiêu chuẩn, chúng tôi sử dụng đá quý fast_jsonapi từ Netflix. Nó khá tốt đối với cách tiếp cận thông thường, nhưng để xử lý lỗi không nhiều nên chúng tôi quyết định viết ErrorSerializer của riêng mình

    # app/serializers/error_serializer.rb

    class ErrorSerializer
      def initialize(error)
        @error = error
      end

      def to_h
        serializable_hash
      end

      def to_json(payload)
        to_h.to_json
      end

      private

      def serializable_hash
        {
          errors: Array.wrap(error.serializable_hash).flatten
        }
      end

      attr_reader :error
    end

logic rất đơn giản. Nó chấp nhận một đối tượng với các phương thức trạng thái, tiêu đề, chi tiết và nguồn, đồng thời tạo các phản hồi được tuần tự hóa ở định dạng

    # json response

    {
      "errors": [
        {
          "status": 401,
          "title": "Unauthorized",
          "detail": "You need to login to authorize this request.",
          "source": {
            "pointer": "/request/headers/authorization"
          }
        }
      ]
    }

Vấn đề duy nhất ở đây là việc xử lý tất cả các lỗi đó trong mọi hành động của hệ thống sẽ dẫn đến rất nhiều bản sao mã không KHÔ lắm phải không? . RecordNotFound sẽ rất phức tạp. Sau đó, đây là những gì chúng tôi đã kết thúc trong API ApplicationController của mình

    # app/controllers/application_controller.rb

    class ApplicationController < ActionController::API
      ...
      include Api::ErrorHandler
      ...
    end

Chúng tôi chỉ bao gồm mô-đun ErrorHandler, nơi chúng tôi triển khai tất cả các ánh xạ và logic chịu trách nhiệm cho tất cả việc xử lý lỗi

    module Api::ErrorHandler
      extend ActiveSupport::Concern

      ERRORS = {
        'ActiveRecord::RecordNotFound' => 'Errors::NotFound',
        'Driggl::Authenticator::AuthorizationError' => 'Errors::Unauthorized',
        'Pundit::NotAuthorizedError' => 'Errors::Forbidden'
      }

      included do
        rescue_from(StandardError, with: lambda { |e| handle_error(e) })
      end

      private

      def handle_error(e)
        mapped = map_error(e)
        # notify about unexpected_error unless mapped
        mapped ||= Errors::StandardError.new
        render_error(mapped)
      end

      def map_error(e)
        error_klass = e.class.name
        return e if ERRORS.values.include?(error_klass)
        ERRORS[error_klass]&.constantize&.new
      end

      def render_error(error)
        render json: Api::V1::ErrorSerializer.new([error]), status: error.status
      end
    end

Ở trên cùng, chúng tôi đã thêm một trình ánh xạ đẹp cho tất cả các lỗi mà chúng tôi dự kiến ​​sẽ xảy ra ở đâu đó. Sau đó, chúng tôi giải cứu khỏi lỗi mặc định cho khối cứu hộ, đó là StandardError và gọi phương thức handle_error với đối tượng được nâng lên

Bên trong phương pháp này, chúng tôi chỉ thực hiện ánh xạ lỗi tăng lên với những gì chúng tôi đã chuẩn bị phản hồi của máy chủ. Nếu không có cái nào phù hợp, chúng tôi sẽ quay lại Lỗi của mình. StandardError để khách hàng luôn nhận được thông báo lỗi đẹp trong phản hồi của máy chủ

Chúng tôi cũng có thể thêm các trình thông báo bổ sung cho bất kỳ lỗi nào không được ánh xạ trong mô-đun trình xử lý, vì vậy quản trị viên ứng dụng sẽ có thể theo dõi các kết quả không mong muốn

Lỗi gia tăng trong ứng dụng

Ở Driggl, chúng tôi đã cố gắng tạo ra một giải pháp thống nhất để xử lý toàn bộ lỗi trên ứng dụng API của mình. Bằng cách này, chúng tôi có thể tăng lỗi của mình một cách rõ ràng mà không lặp lại bất kỳ khối cứu hộ nào và ApplicationController của chúng tôi sẽ luôn xử lý đúng cách

    def show
      Article.find!(params[:id])
    end

hoặc

________số 8

Xử lý lỗi xác thực

Chà, đó là một giải pháp hay, nhưng có một điều chúng tôi đã cố tình bỏ qua cho đến nay và đó là. lỗi xác thực

Vấn đề với việc xác thực là chúng ta không thể viết đối tượng lỗi cho yêu cầu không hợp lệ giống như chúng ta đã làm cho phần còn lại, bởi vì

  • thông báo lỗi khác nhau dựa trên loại đối tượng và dựa trên các thuộc tính không hợp lệ
  • một phản hồi JSON có thể có nhiều lỗi trong mảng được trả về

Điều này yêu cầu chúng tôi thêm một lỗi nữa, có tên là Không hợp lệ, đây là phiên bản mở rộng của những gì chúng tôi đã có trước đây

    # app/lib/errors/invalid.rb

    module Errors
      class Invalid < Errors::StandardError
        def initialize(errors: {})
          @errors = errors
          @status = 422
          @title = "Unprocessable Entity"
        end

        def serializable_hash
          errors.reduce([]) do |r, (att, msg)|
            r << {
              status: status,
              title: title,
              detail: msg,
              source: { pointer: "/data/attributes/#{att}" }
            }
          end
        end

        private

        attr_reader :errors
      end
    end

Bạn có thể thấy sự khác biệt chính ở đây là phương thức serialized_hash và phương thức khởi tạo. Phương thức khởi tạo cho phép chúng tôi chuyển hàm băm thông báo lỗi vào đối tượng lỗi của chúng tôi, vì vậy chúng tôi có thể tuần tự hóa chính xác lỗi cho từng thuộc tính và thông báo tương ứng

ErrorSerializer của chúng tôi sẽ xử lý ngay lập tức, trả về

    # app/lib/errors/unauthorized.rb

    module Errors
      class Unauthorized < Errors::StandardError
        def initialize
          super(
            title: "Unauthorized",
            status: 401,
            detail: message || "You need to login to authorize this request.",
            source: { pointer: "/request/headers/authorization" }
          )
        end
      end
    end



    # app/lib/errors/not_found.rb

    module Errors
      class NotFound < Errors::StandardError
        def initialize
          super(
            title: "Record not Found",
            status: 404,
            detail: "We could not find the object you were looking for.",
            source: { pointer: "/request/url/:id" }
          )
        end
      end
    end
0

Tuy nhiên, điều cuối cùng là tăng nó ở đâu đó, vì vậy trình xử lý sẽ lấy dữ liệu lỗi chính xác để tiếp tục

Trong kiến ​​trúc chúng ta có, đó không phải là vấn đề lớn. Sẽ thật khó chịu nếu chúng ta cập nhật và tạo các đối tượng như thế này

    # app/lib/errors/unauthorized.rb

    module Errors
      class Unauthorized < Errors::StandardError
        def initialize
          super(
            title: "Unauthorized",
            status: 401,
            detail: message || "You need to login to authorize this request.",
            source: { pointer: "/request/headers/authorization" }
          )
        end
      end
    end



    # app/lib/errors/not_found.rb

    module Errors
      class NotFound < Errors::StandardError
        def initialize
          super(
            title: "Record not Found",
            status: 404,
            detail: "We could not find the object you were looking for.",
            source: { pointer: "/request/url/:id" }
          )
        end
      end
    end
1

Vì điều này sẽ buộc chúng tôi phải giải cứu ActiveRecord. Lỗi RecordInvalid trong mọi hành động và khởi tạo đối tượng lỗi tùy chỉnh của chúng tôi ở đó như thế này

    # app/lib/errors/unauthorized.rb

    module Errors
      class Unauthorized < Errors::StandardError
        def initialize
          super(
            title: "Unauthorized",
            status: 401,
            detail: message || "You need to login to authorize this request.",
            source: { pointer: "/request/headers/authorization" }
          )
        end
      end
    end



    # app/lib/errors/not_found.rb

    module Errors
      class NotFound < Errors::StandardError
        def initialize
          super(
            title: "Record not Found",
            status: 404,
            detail: "We could not find the object you were looking for.",
            source: { pointer: "/request/url/:id" }
          )
        end
      end
    end
2

Điều này một lần nữa sẽ dẫn đến việc lặp lại rất nhiều khối giải cứu trên ứng dụng

Tuy nhiên, ở Driggl, chúng tôi tận dụng kiến ​​trúc Trailblazer, với các hợp đồng và hoạt động, cho phép chúng tôi dễ dàng thống nhất mọi hành động của bộ điều khiển trong hệ thống

    # app/lib/errors/unauthorized.rb

    module Errors
      class Unauthorized < Errors::StandardError
        def initialize
          super(
            title: "Unauthorized",
            status: 401,
            detail: message || "You need to login to authorize this request.",
            source: { pointer: "/request/headers/authorization" }
          )
        end
      end
    end



    # app/lib/errors/not_found.rb

    module Errors
      class NotFound < Errors::StandardError
        def initialize
          super(
            title: "Record not Found",
            status: 404,
            detail: "We could not find the object you were looking for.",
            source: { pointer: "/request/url/:id" }
          )
        end
      end
    end
3

Tôi sẽ không đi sâu vào chi tiết của Trailblazer trong bài viết này, nhưng vấn đề là chúng tôi có thể xử lý các lỗi xác thực khi đã ở trong quá trình vận hành. định nghĩa phương thức và mọi thứ hoạt động như một nét duyên dáng trên toàn bộ ứng dụng, giữ cho mọi thứ vẫn đẹp và KHÔ

    # app/lib/errors/unauthorized.rb

    module Errors
      class Unauthorized < Errors::StandardError
        def initialize
          super(
            title: "Unauthorized",
            status: 401,
            detail: message || "You need to login to authorize this request.",
            source: { pointer: "/request/headers/authorization" }
          )
        end
      end
    end



    # app/lib/errors/not_found.rb

    module Errors
      class NotFound < Errors::StandardError
        def initialize
          super(
            title: "Record not Found",
            status: 404,
            detail: "We could not find the object you were looking for.",
            source: { pointer: "/request/url/:id" }
          )
        end
      end
    end
4

Tóm lược

Bạn có thể nghĩ rằng đó là rất nhiều mã, nhưng thực sự, đối với các ứng dụng lớn, nó chẳng là gì so với việc lặp lại nó trong hàng trăm bộ điều khiển và các tệp khác. Trong biểu mẫu này, chúng tôi đã quản lý để thống nhất tất cả các lỗi của mình trên toàn bộ ứng dụng API và chúng tôi không cần phải lo lắng nữa về các lỗi không mong muốn khi gửi tới ứng dụng khách

Tôi hy vọng điều này cũng sẽ hữu ích cho bạn và nếu bạn tìm thấy bất kỳ cải tiến nào cho phương pháp này, vui lòng cho tôi biết trong phần nhận xét

Các cách khác nhau để xử lý ngoại lệ là gì?

Dưới đây là danh sách các cách tiếp cận khác nhau để xử lý ngoại lệ trong Java. .
cố gắng. bắt khối
cuối cùng chặn
từ khóa ném và ném

3 khối được sử dụng để xử lý ngoại lệ là gì?

Khối "catch" được sử dụng để xử lý ngoại lệ. Nó phải được đặt trước khối try, điều đó có nghĩa là chúng ta không thể sử dụng khối catch một mình. Nó có thể được theo sau bởi khối cuối cùng sau. Khối "cuối cùng" được sử dụng để thực thi mã cần thiết của chương trình.