やっとブログが完成しました!
rails newしたのが10/10でしたので、見せられる状態になるまで9日掛かりました。
自分の見積もりは1週間くらいかなと思っていたのですが、
API周りのコードやフローで苦労したので、ちょっと長くかかってしまいました。
よかったら見てください→https://blog-new-owzi.onrender.com
githubはこちら
今回ははてなAPIのサービスクラスの設計について記事にします。
基本概念
サービスクラスってなんなの?なんで必要なの?
サービスクラスは一言で言うと、「別サービスから受けとったデータや複雑なビジネスロジックをRailsに渡す加工所」です。
サービスクラスが外部サービスと通信してデータを受け取って、Railsが読み取れる値に解析します。
その解析されたデータがコントローラーやモデル、ビューに渡っていきます。
外部サービスを使っていなくても、直接コントローラーやモデルに書きにくい処理、
例えば支払い処理みたいなのも、サービスクラスを使うことがあります。
今回はどんなことをするの?
はてなブログのサービスクラスにやってもらいたいことは以下のとおりです。
1. .envファイルの情報を使い、どのURLにアクセスするかを決める 2. HTTPリクエストを送る(GET, POSTなど) 3. レスポンス(XMLやJSON)を受け取る ※今回はXML 4. 必要な情報を抜き出して(解析)、Rubyのオブジェクトに変える
実装例: HatenaBlogService
今回実装したサービスクラスの初期のコードを載せてみます。
このサービスクラスでは、はてなブログの記事を取得していきます。
とはいえ、丸々載せると読みづらいので、ちょっと簡単にしましょう。
class HatenaBlogService
BASE_URL = "https://blog.hatena.ne.jp"
# ポイント①
def initialize
@username = ENV['HATENA_USERNAME']
@blog_id = ENV['HATENA_BLOG_ID']
@api_key = ENV['HATENA_API_KEY']
end
# ポイント②
def fetch_articles(limit: 10)
# 外部APIから記事を取得してパースする
response = fetch_atom_feed
articles = parse_articles(response)
articles.first(limit)
end
private
def fetch_atom_feed
# HTTP通信の処理
end
def parse_articles(xml_body)
# XML解析の処理
end
end
ポイント①: 初期化メソッド
def initialize @username = ENV['HATENA_USERNAME'] @blog_id = ENV['HATENA_BLOG_ID'] @api_key = ENV['HATENA_API_KEY'] end
ここでは環境変数(.envファイル)から認証情報を読み込んで、インスタンス変数に保存しています。
initializeでまとめて設定しておくことで、1回の初期化で使い回すことができます。
ここではfetch_articlesで「記事の取得」のみを行いますが、
今後「複数の記事一覧の取得」や「記事投稿」をしたくなった場合、このまま使い回せるようになります。
ポイント②: 単一責任の原則(SRP)
def fetch_articles(limit: 10)
# 外部APIから記事を取得してパースする
response = fetch_atom_feed
articles = parse_articles(response)
articles.first(limit)
end
private
def fetch_atom_feed
# HTTP通信の処理
end
def parse_articles(xml_body)
# XML解析の処理
end
fetch_articlesメソッドでは、記事の取得のみに集中してもらっています。
途中で出てくる「HTTP通信の処理」や、「XML解析の処理」はそれぞれ、
fetch_atom_feedやparse_articlesとして、privateメソッド に実装します。
こうすると、1つのメソッドが1つの目的だけを持つようにする(=単一責任) という設計になります。
コードも読みやすく、エラー起きたときに原因が特定しやすくなりますね!
ポイント③: メソッド名は分かりやすく
各所のメソッド名は「どんな処理をしているのか」わかりやすい名前にするのが大事です。
私はfetch_atom_feedが分かりにくかったのですが、
言葉にすれば「Atom形式の記事一覧を取得する」ということです。
Atom形式とは簡単にいうと、「XMLで書かれたフィード形式」のことです。
別な形式にRSSというのがありますが、それと同様、記事タイトルやリンク、公開日時などを配信する規格です。
メソッドの命名については、AIに聞いたり、調べてみましょう!
こちらのサイトで一度調べてみるのもおすすめです。
コントローラーに渡す
サービスクラスを使うと、コントローラーはとてもわかりやすいコードになります。
class ArticlesController < ApplicationController
def index
service = HatenaBlogService.new
@articles = service.fetch_articles(limit: 10)
end
end
コントローラーの中では、「サービスクラスを呼び出して、結果をビューに渡す」ことだけに集中しています。
この設計は「Tell, Don't Ask」の原則に則っていますね!
次回: エラーハンドリング
長くなったので、今日はここまでにします。(息切れ)
設計の部分は、AIにコードを書いてもらったとしても自分で分かっていないとリファクタリングができないので私も引き続き学んでいきます。
次回はエラーハンドリング編です。
APIとの認証や通信でエラーが起きたときに、WEBアプリが落ちないようにするにはどうしたらいいでしょうか?
この実装が難しくて、苦労しました。
頑張って記事書くので、よかったらまた見てみてください。