おかりなの記事

プログラミング学習の記録です。

ブログのサービスクラスの設計について

やっとブログが完成しました!
rails newしたのが10/10でしたので、見せられる状態になるまで9日掛かりました。
自分の見積もりは1週間くらいかなと思っていたのですが、
API周りのコードやフローで苦労したので、ちょっと長くかかってしまいました。
よかったら見てください→https://blog-new-owzi.onrender.com
githubはこちら

github.com

今回ははてな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_feedparse_articlesとして、privateメソッド に実装します。
こうすると、1つのメソッドが1つの目的だけを持つようにする(=単一責任) という設計になります。
コードも読みやすく、エラー起きたときに原因が特定しやすくなりますね!

ポイント③: メソッド名は分かりやすく

各所のメソッド名は「どんな処理をしているのか」わかりやすい名前にするのが大事です。 私はfetch_atom_feedが分かりにくかったのですが、
言葉にすれば「Atom形式の記事一覧を取得する」ということです。 Atom形式とは簡単にいうと、「XMLで書かれたフィード形式」のことです。
別な形式にRSSというのがありますが、それと同様、記事タイトルやリンク、公開日時などを配信する規格です。

メソッドの命名については、AIに聞いたり、調べてみましょう!
こちらのサイトで一度調べてみるのもおすすめです。

codic.jp

コントローラーに渡す

サービスクラスを使うと、コントローラーはとてもわかりやすいコードになります。

class ArticlesController < ApplicationController
  def index
    service = HatenaBlogService.new
    @articles = service.fetch_articles(limit: 10)
  end
end

コントローラーの中では、「サービスクラスを呼び出して、結果をビューに渡す」ことだけに集中しています。
この設計は「Tell, Don't Ask」の原則に則っていますね!

次回: エラーハンドリング

長くなったので、今日はここまでにします。(息切れ)
設計の部分は、AIにコードを書いてもらったとしても自分で分かっていないとリファクタリングができないので私も引き続き学んでいきます。

次回はエラーハンドリング編です。
APIとの認証や通信でエラーが起きたときに、WEBアプリが落ちないようにするにはどうしたらいいでしょうか?
この実装が難しくて、苦労しました。
頑張って記事書くので、よかったらまた見てみてください。

APIの認証情報が読み込めてなかった

今更ながら,はてブではマークダウン記法でブログの内容を記述することに気付きました。
これから気をつけます!
作っているブログですが、残りはブログ記事の表示をさせるところまでできました。
はてなAPIを叩くべく、調べながら実装していってるところです。
今日あったことをまとめておこうと思います。

使用技術

はてなブログAPIの認証情報を入れる

.envファイルに以下を記述する。

HATENA_USERNAME=自分のユーザー名
HATENA_BLOG_ID=ブログID
HATENA_API_KEY=APIキー

このとき、間にスペースをいれたり、シングルクォーテーションは入れないことに注意する。

はてなブログAPIのServiceクラスを書く

ここの中身についての詳細は、きちんと実装できてから書きます!
とりあえず、こんな感じで書いていました。

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

~~~

問題発生! コンソールでの確認

$ rails consoleで、きちんと読み込めているか確認。

new-blog(dev)> service = HatenaBlogService.new
=> #<HatenaBlogService:1234123412341234 @api_key=nil, @blog_id=nil, @username=nil>

値が nil になっていました。

続いて、Dockerでのコンテナでの確認。

$ docker compose exec web bash

echo $HATENA_USERNAME

echo $HATENA_BLOG_ID

echo $HATENA_API_KEY

そもそもサービスクラスが渡っていなかったようです。

原因は Gemfile と compose.yml

調べていくと、

  • gem 'dotenv-rails'が入っていなかった
  • compose.ymlにて環境変数管理ができていないこと こと

上記2点が原因でした。

compose.ymlにて環境変数管理

compose.ymlにて以下を追記。

services:
  web:
    build: .
    ports:
      - "3000:3000"
    # 追記は以下
    env_file:
      - .env

または

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - HATENA_USERNAME=${HATENA_USERNAME}
      - HATENA_BLOG_ID=${HATENA_BLOG_ID}
      - HATENA_API_KEY=${HATENA_API_KEY}

ここで、認証情報を直接書かないことに注意です。

gem 'dotenv-rails'をいれる

Gemfileに 追記。

gem 'dotenv-rails', groups: [:development, :test]

dotenv-railsにて、アプリ直下の.envファイルが自動的に読み込まれるようになります。

これで設定が終わりました。
Dockerの再設定をして、再度確認しましょう。
これで読み込まれるようになっているはずです。

まとめ

以下の流れで認証情報が読み込めるようになりました。

  1. .envファイル作成
  2. compose.ymlでenv_file指定
  3. コンテナ再起動で設定反映
  4. 動作確認

認証情報が見られちゃうと、最悪ブログ消されてしまうので注意して実装していきましょう!
誰かの役に立てると幸いです。

ブログ開設!

はじめまして!
RUNTEQにてプログラミングを学んでいる、おかりなと申します!
入学して120日が経とうとしています。
このブログで自分の学習のアウトプットの機会を増やせたらいいなと思います。
よろしくお願いいたします。