20/09/2018, 08:36

MỘT VÍ DỤ VỀ ADAPTER OBJECT | DESIGN PATTERN IN RAILS

Design patterns là các giải pháp đã được tối ưu hóa, được tái sử dụng cho các vấn đề lập trình mà chúng ta gặp phải hàng ngày. Nó là một khuôn mẫu đã được suy nghĩ, giải quyết trong tình huống cụ thể rồi. Các vấn đề mà bạn gặp phải có thể bạn sẽ tự nghĩ ra cách giải quyết nhưng có thể nó chưa ...

  • Design patterns là các giải pháp đã được tối ưu hóa, được tái sử dụng cho các vấn đề lập trình mà chúng ta gặp phải hàng ngày. Nó là một khuôn mẫu đã được suy nghĩ, giải quyết trong tình huống cụ thể rồi.
  • Các vấn đề mà bạn gặp phải có thể bạn sẽ tự nghĩ ra cách giải quyết nhưng có thể nó chưa phải là tối ưu. Design Pattern giúp bạn giải quyết vấn đề một cách tối ưu nhất, cung cấp cho bạn các giải pháp trong lập trình OOP.
  • Nó không phải là ngôn ngữ cụ thể nào cả. Design patterns có thể thực hiện được ở phần lớn các ngôn ngữ lập trình. Ta thường gặp nó nhất trong lập trình OOP.
  • Adapter là objects với mục đích tạo lên 1 lớp trừu tượng tập hợp các việc gọi api của bên thứ ba, tác dụng của nó giúp chúng ta tập hợp công việc về api về 1 chỗ, sau này nếu có thay đổi thì sẽ dễ dàng update, và để hiểu hơn về nó thì chúng ta đi qua 1 ví dụ nhỏ nhé
  • Giả sử chúng ta có 1 bài toán lấy data api sau đó show ra browse:
  • Để fake api trả về data các bạn có thể sử dụng JSON Server để tạo nó có thể tham khảo ở link này: https://github.com/typicode/json-server
  • tiếp đến sẽ là việc lấy data đã dake ở trên từ url: http://localhost:3001/posts để show ra view.

Bad Solution

class JobsController < ApplicationController
  def show
    uri = URI.parse "http://localhost:3001/posts"
    http = Net::HTTP.new uri.host, uri.port
    request = Net::HTTP::Get.new uri.request_uri
    request["authorization"] = "token"
    api = http.request request

    @data = if api.code == "200"
      JSON.parse api.body
    else
      JSON.parse api.body["errors"]["messages"]
    end
  end
end
  • Nếu như gọi api đặt hết trong controller như trên thì nếu như nó được dụng ở nhiều chỗ, code chúng ta sẽ bị lặp, và rất khó để chỉnh sửa sau này,
  • Ở đây chúng ta có thể sử dụng service object, tuy nhiên ở đây chúng cần kiểm tra lỗi, đưa ra data, lỗi, ...; nó sẽ phá vỡ quy ước "keep one responsibility per service object" của service object, nghĩa là 1 service chỉ thực hiện 1 nhiệm vụ.

Solution: Adapter Object

Để giải quyết các vấn đề trên, mình sẽ nén tất cả các tác vụ trên cho vào 1 lớp, và ở controller chỉ cần gọi lại lớp đó.

class JobsController < ApplicationController
  def show
    api = Job::InfoAdapter.new token: "token"
    @data = api.success? ? api.data : api.errors_message
  end
end
  • Controller của mình đã trở nên rất "sạch", mang gần đúng với nhiệm vụ lý tưởng của nó là điều hướng.
  • Và tạo 1 class dặt trong thư mục /app/adapters/...
class Job::InfoAdapter
  SOURCE = "http://localhost:3001/posts"
  delegate :success?, :fail?, :data, :status_code,
    :errors_message, to: :handle_response

  def initialize args
    @token = args[:token]
    @response = get_response
  end

  private
  attr_reader :token, :response

  def get_response
    messages = Job::Serializers::Info.new token: token, source: SOURCE
    Net::HTTP.new(messages.uri.host, messages.uri.port).request messages.request
  end

  def handle_response
    Job::Deserializers::Info.new response: response
  end
end
  • Ở đây sẽ có thêm Deserializers objects và Serializer objects để hỗ trợ giúp aapter trở nên đơn giản hơn.
Job::Serializers::Info.new token: token, source: SOURCE
class Job::Serializers::Info
  attr_reader :token, :source, :uri

  def initialize args
    @token = args[:token]
    @source = args[:source]
    @uri = uri
  end

  def request
    request = Net::HTTP::Get.new uri.request_uri
    request["authorization"] = token
    request
  end

  def uri
    URI.parse source
  end
end
  • Serializer objects nằm trong /app/adapters/job/serializers
  • Nó có nhiệm vụ xử lí request trước khi gửi đến server
Job::Deserializers::Info.new response: response
class Job::Deserializers::Info
  def initialize args
    @response = args[:response]
  end

  def success?
    response.code == "200"
  end

  def fail?
    !success?
  end

  def data
    JSON.parse response.body
  end

  def status_code
    response.code
  end

  def errors_message
    JSON.parse response.body["errors"]["messages"]
  end

  private
  attr_reader :response
end
  • Deserializers objects đặt trong /app/adapters/job/deserializers
  • Nó giúp adapter object nhận response và xử lí nó để đưa ra các data thích hợp

Kết Luận

  • Tóm lại, adapter object không làm cho code chúng ta ngắn đi hay cải thiện performance, thậm chí nó còn làm code dài thêm tuy nhiên nó sẽ có ích khi code sử dụng lại code đó, hay maintainable code, Encapsulated logic, dễ dàng mở rộng code sau này.

0