Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save noel9999/172946f174e144c2d48e33535fc5d376 to your computer and use it in GitHub Desktop.
Save noel9999/172946f174e144c2d48e33535fc5d376 to your computer and use it in GitHub Desktop.
一個 controller 的 action 需要去戳外部 api 三次的邏輯需求,使用 fiber 整理邏輯
require 'fiber'
Result = Struct.new :success, :payload, :status do
def self.create(args = {})
args = { success: true, payload: {}, status: :ok }.merge(args.symbolize_keys)
new *args.values_at(:success, :payload, :status)
end
def success?
!!success
end
def error
{ message: 'Bad thing happends', errors: {} }.merge(payload)
end
end
class Webhook::CustomersController < ApplicationController
# 使用 fiber 組織邏輯,把每個步驟都劃分成一次 yield ,並且檢查結果成不成功,成功的話再把傳回給自己當參數繼續下去,失敗的話就 render
# 好處是 create 的邏輯變得很清晰、一目了然
# 但三個步驟拆出來的方法所需的參數可能有相依性(可能會有共用的參數),所以目前採用滾雪球的方式,每個步驟把自己新產生的結果都 merge 回參數,然後拋給下一個方法,有點詭異的作法
# 實作上也多了許多 code ,所成本跟好處比起來真的不太明顯...
def create
token = get_access_token(params[:merchant_id])
fiber = Fiber.new do
payload = Fiber.yield fetch_customer(token, params)
payload = Fiber.yield fetch_studioa_vip(payload)
create_or_update_customer(payload)
end
# Ruby 的 fiber 不支援 for 或是其他迭帶性質的方法使用,只能自己循環下去,有一些麻煩
while(fiber.alive?)
result = fiber.resume(result ||= nil)
(render json: result.payload, status: result.status and return) unless result.success?
end
render json: result.payload
end
protected
def query_studioa_vip(mobile_phone)
uri = URI(Settings.api_endpoint.query_vip)
json_parameters = {
"SYSPARAM": {
"DB_ID": "EPB",
"CHAR_SET": "eng",
"INCLUDE_NULL": "N"
},
"PARAMETER": {
"VIP_PHONE": mobile_phone
}
}
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req.body = json_parameters.to_json
Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(req)
end
end
def fetch_customer(token, params)
client = OpenApiClient.set(access_token: token).customers
customer = client.get("/#{params[:resource][:_id]}").json_body
mobile_phone, confirmed_at = customer.symbolize_keys.values_at(:mobile_phone, :confirmed_at)
if [mobile_phone, confirmed_at].all?(&:present?)
Result.create payload: { customer: customer, mobile_phone: mobile_phone, token: token }
else
Result.create payload: { message: "customer: #{customer['id']} is not signed up." }, status: :no_content, success: false
end
end
def fetch_studioa_vip(args = {})
args = args.symbolize_keys
res = query_studioa_vip(args[:mobile_phone])
if res['RESULT']['STATUS'].to_s != 'S'
Result.create(payload: args.merge(raw_customer: res.json_body['DATA']))
else
Result.create(payload: { errors: res.json_body }, status: :bad_request, success: false)
end
end
def create_or_update_customer(args = {})
args = args.symbolize_keys
service_result = (args[:raw_customer].present? ? UpdateCustomerService : CreateCustomerService).new(args).execute
if service_result.success?
Result.create payload: service_result.payload
else
Result.create payload: service_result.error, status: :bad_request
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment