この記事は、2021年のアトラエアドベントカレンダー22日目の記事です。
昨日の記事は、@kinoshitatraeさんによる「モードレスUIデザイン ~ ユーザーをクリエイティブにする」でした。
こんにちは!People Techカンパニー、アトラエに22卒として入社予定で、内定者インターンをしている家令(かれい)と申します。Wevoxでバックエンドエンジニアをやっています。
今回は内定者インターンを始めてから多くの時間を費やしてきた「Wevoxのマイクロサービス化に伴う、APIのGo移行」について紹介したいと思います。
Wevoxのマイクロサービス化について
Wevoxのアーキテクチャは現在、モノリシックからマイクロサービスへと移行している真っ只中にあります。モノリシックの時代には、Ruby on Railsが多くの責務を担っていたのですが、以下の方針でマイクロサービス化を事業全体で推し進めています。
- FrontはRailsから分離し、SPA化を進める
- Railsの機能を切り出し、Goで小さなAPIにする
- 新機能はコア部分はGoで作り、認証等はRailsに任せる
並列処理を基本とするGoに置き換えることで処理の高速化を実現できる、Dockerとの相性が良くメモリの消費量を抑えることができるなど、Goへの移行のメリットは数多く存在するわけですが、Goへと移行されたAPIにRailsからリクエストを送り、Front側に値を返す部分をメインで実装していたので、その内容についてまとめてみたいと思います。
※Go側の実装については触れていないのでご注意を!
Rails側の実装方針
Rails側の実装方針としては以下の手順で行なっていました。
- app/apis/~ディレクトリを作成し、GoのAPIをコールする処理を書く
- 対象のコントローラに、1.で作成した処理を追加し、Front側に値を返す
- 期待している値が返ってきているか入念な動作確認をし、デバッグをする
それぞれ見ていきましょう
1. app/apis/~ディレクトリを作成し、GoのAPIをコールする処理を書く
GoのAPIをコールする処理は大きく5つの層に分けて実装していきます。
- Service層
- Usecase層
- Response層
- Model層
- Base層
この構成では、Service層⇄Usecase層⇄Response層⇄Model層で責務を分離し、オブジェクト指向に則り、処理の抽象化・隠蔽化をすることで、変更に強い設計になっています。Base層では、各層で必要になる共通の値を設定しています。
具体的なコードはそれぞれ以下の通りです
class Hoge::GetFugaService
private_class_method :new
def self.call(id:)
new(id: id).send(:call)
end
private
def initialize(id:)
@id = id
end
def call
Hoge::Usecase::FugaGet.new(id: @id).execute.result
end
end
module Hoge
module Usecase
class FugaGet
PATH = Hoge::Url::Fuga_GET
attr_accessor :id
def initialize(id:)
self.id = id
end
def execute
# 実コードではhostとportを指定してGoのAPIへリクエストを飛ばし、レスポンスを受け取っています
response = get(PATH)
case response
when Net::HTTPOK then
return Hoge::Response::FugaGet.new(
Hoge::Response::Base::HTTP_OK, response
)
when Net::HTTPBadRequest then
return Hoge::Response::FugaGet.new(
Hoge::Response::Base::HTTP_BAD_REQUEST, response
)
when Net::HTTPInternalServerError
return Hoge::Response::FugaGet.new(
Hoge::Response::Base::HTTP_INTERNAL_SERVER_ERROR, response
)
else
return Hoge::Response::FugaGet.new(
Hoge::Response::Base::UNEXPECTED_ERROR, response
)
end
end
end
end
end
module Hoge
module Response
class FugaGet
include Hoge::Response::Base
attr_accessor :status, :error_hash, :success_hash
def initialize(status, response)
self.status = status
if error?(status)
self.error_hash = hash_json(response.body)
elsif status == UNEXPECTED_ERROR
self.error_hash = ERROR_HASH
else
self.success_hash = hash_json(response.body)
end
end
def result
return parse_fuga(success_hash) if status == HTTP_OK
error_hash
end
private
def parse_fuga(success_hash)
Hoge::Model::Response::FugaGet.new(
fuga: success_hash[:fuga]
)
end
end
end
end
module Hoge
module Model
module Response
class FugaGet
attr_accessor :fuga
def initialize(fuga:)
@fuga = fuga
end
end
end
end
end
module Hoge
module Response
module Base
HTTP_OK = 200
HTTP_CREATED = 201
HTTP_NO_CONTENT = 204
HTTP_BAD_REQUEST = 400
HTTP_NOT_FOUND = 404
HTTP_INTERNAL_SERVER_ERROR = 500
UNEXPECTED_ERROR = 600
UNEXPECTED_ERROR_CODE = "ERR-NOT-IMPLEMENTED"
ERROR_HASH = { errors: [{ code: UNEXPECTED_ERROR_CODE }] }
def hash_json(body)
JSON.parse(body).deep_symbolize_keys
end
def error?(status)
status == Hoge::Response::Base::HTTP_BAD_REQUEST ||
status == Hoge::Response::Base::HTTP_INTERNAL_SERVER_ERROR
end
end
end
end
- Service層からUsecase層に必要な値を渡す
- Usecase層で、指定されたエンドポイント(Hoge::Url::GETという定数に記載)にリクエストを飛ばし、GoのAPIから受け取ったレスポンスをステータスと一緒にResponse層に渡す
- Response層で、GoのAPIから受け取ったレスポンスをFront側に渡す形に整えたModel層に合わせて、Usecase層に返す
- Usecase層に返されたResponse層の値をService層で呼び出す
ここまでで一旦、GoのAPIをコールする処理の作成が完了したので、次に実際にコールする箇所を特定し、処理を追加していきます。
2. 対象のコントローラに、1.で作成した処理を追加し、Front側に値を返す
1.で用意したGoのAPIをコールする処理の内、Service層を使ってGoのAPIを叩きます。
module Apis
module Views
class FugaController < ActionController::API
def index
@fuga = Hoge::GetFugaService.call(id: 1)
end
end
end
end
そして、GoのAPIから取得できた値をjbuilderを使ってFront側に返します
json.fuga @fuga
jbuilderについての参照資料はこちら→https://qiita.com/tanutanu/items/1dd1ea027dd142e84dc8
3. 期待している値が返ってきているか入念な動作確認をし、デバッグをする
最後に入念な動作確認を行います。
今回追加した処理で意図したエンドポイントに正しくリクエストが飛ばされているのか、open_apiに記載されている要件通りGoのAPIとの疎通ができているのか、Front側に期待している値を渡せているのか、正しくエラーハンドリングができているのかなどを一つ一つ確認し、不備があれば1~2の処理を修正します。
終わりに
長らくAPIのリプレイスを行なってきた中で、
- コミュニケーションを丁寧に行うこと
- 想定外を想定すること
がとても重要だと感じました。
前者に関しては、Front⇄Rails⇄Goとそれぞれ別の人が実装している中で、FrontとGoの橋渡しをするRails側の実装者が、いかに関係者の認識の齟齬をなくし、手戻りが発生しないよう前提を揃えながら進めていけるかがとても大事だと感じました。
後者に関しては、一つでも間違ったリプレイスをしてしまうと、回答結果が命のWevoxにおいては、間違った回答データをユーザーさんに渡してしまうことに繋がるため、品質を担保する上で意識しなければならないことだと思いました。
この二つは別の場面でも重要なことだと思うので、これからも肝に銘じて実装していきたいなと思います。
最後まで読んでいただき、ありがとうございました!
明日は@yuki_engさんの記事です!お楽しみに!