hinataメディアAPIのリファクタリングを通して感じたこと

こんにちは!バックエンドエンジニアの北條です。

23卒大学生で、今はインターンとして業務に携わっています。

現在「hinataメディア(以下メディア)で使用しているAPIリファクタリングをする」というタスクを行なっているのですが、新しい発見や学びが多くありました。

メディアのエンジニア向けにドキュメントを作成したのですが、タスクの背景や個人的な感想を交えたカジュアルな記録も残したいなと思い記事にしました。

タスクの始まり

メディアはバックエンドとフロントエンドが分かれており、バックエンドはAPIを作成するのが主な業務になります。

書き方にルールが決まっていなかったのでAPIの作成が人によって違うなど、今後さらにAPIが増えたときにメンテナンスできなくなるのではないかという状態でした。

いつか整備したいんだよね〜とチームの方が言っていたので、興味半分で1on1のときにマネージャーに話してみたら「じゃあ、やってみましょう!」ということに。

チームの方にサポートしてもらいながらではありますが、まったくイメージがつかないまま期待と不安に溢れた状態でタスクが始まるのでした。

課題

メディアではOpenAPIによりパラメータや応答データを決め、それに沿うようにバックエンド、フロントエンドを実装していきます。

パラメータとして受け取る値や返す値の形に制約はあるのですが、受け取った値をどのように加工するかというロジック部分の制約がありません。

上記のように、メディアAPIはロジック部分を自由に書けてしまうという問題点があります。

情報を格納すべき場所で情報の検索や加工を行なっていたりなど、逆もまた然りです。

また、開発者によって実装の仕方もテストの仕方も違うのでレビューが大変という問題もありました。

以上まとめると

  • 実装方法の型や方針がないので構造的な制約もなく役割が曖昧

  • 結果的にレビューが大変

ということになります。

解決策

具体的な解決策は下記の通りです。

  • 役割を決めて層で分ける

  • 抽象クラスを定義する

  • Builderパターンを用いる

また、これらは全て現状よりもレビューをしやすくなるということも踏まえています。

役割を決めて層で分ける

層を分けるというものは元々行なっていたものの口約束だけになってしまい層を分けている意味を成していませんでした。

なので情報を格納すべき層(画像Builder部分)、情報を加工する層(画像Service部分)などディレクトリに定義付けを行うことで役割を明確にします。

今までは変更箇所全てのファイルを見て実装が正しいかレビューをしていましたが、層によって見るべきポイントが分かれているためレビューもしやすくなります。

抽象クラスを定義する

抽象クラスを作成することで具象クラスでのオーバーライドを強制させ、誰が開発してもある程度同じ形になるようにしました。

下記はインスタント珈琲を作るコードです。

今後色々な飲み物を追加したいとなったときは抽象クラス(Drinkクラス)を継承し、materialメソッドを実装するだけで済みます。

class Drink
  def break_time
    material
    drip
  end

  def material
    raise NotImplementedError, '材料を用意してください'
  end

  def drip
    'お湯を注ぐ'
  end
end

class Coffee < Drink
  def material
    '珈琲の粉を加える'
  end
end

class BlackTea < Drink
  def material
    '紅茶の粉を加える'
  end
end

Builderパターンを用いる

BuilderパターンとはGoFにより考案されたデザインパターンの一種です。

モノを作る過程を用意し、インスタンス化するクラスや引数を変えるだけで別のモノを生成するというのがBuilderパターンです。

Builderパターンを用いることで今後APIを量産するとなっても開発しやすくかつレビューのしやすい状態を目指しました。

また、一つ一つの属性をメソッドとして切り出すことでテストもしやすくなっています。

Builderパターンを用いて任意の飲み物を作成するコードを書いてみました。メソッド部分を抽象化することでさらに幅広い飲み物にも対応することができます。

class Drink
  attr_accessor :water, :material, :sugar, :salt, :milk
end

class DrinkBuilder
  def initialize
    @object = Drink.new
  end

  def make(params)
    water(params)
    material(params)
    sugar(params)
    salt(params)
    milk(params)
    result
  end

  def water(params)
    @object.water = params[:water]
  end

  def material(params)
    @object.material = params[:material]
  end

  def sugar(params)
    @object.sugar = params[:sugar]
  end

  def salt(params)
    @object.salt = params[:salt]
  end

  def milk(params)
    @object.milk = params[:milk]
  end

  def result
    @object
  end
end

コーヒー = DrinkBuilder.new.make(water: 10, material: '珈琲の粉', sugar: 0, salt: 0, milk: 0)
ミルクティー = DrinkBuilder.new.make(water: 7, material: '紅茶の葉', sugar: 2, salt: 0, milk: 3)

最後に

メディアAPIリファクタリングをしてみてメディアのAPIの仕組み、抽象クラスやBuilderパターンといったオブジェクト指向についての知識、RubyRspecの知識など様々なことを学べる機会になりました。

オブジェクト指向などの知識はRubyといった言語に囚われないため、他のチームの方とも話し合いが出来ます。 技術の向上ももちろんですが、エンジニア同士での意思疎通をしやすくするような知識を蓄えていくことも重要だなと感じました。

反省点としてはタスクの工数管理が全然できていなかったなと思います。タスクに対してどのくらいの期間で実装できるのか。自分がどのくらいできて、どのくらいできないのかを認識し、適切な工数を見積もるという力もつけていきたいなと思いました。

vivitにはやりたいと思ったことに挑戦できる機会がたくさんあると思います。今後も挑戦に対し貪欲な姿勢で頑張っていきます!!

vivitでは新卒採用、キャリア採用を募集しております。少しでも気になった方は是非お話しを聞きに来てください!

新卒採用

www.wantedly.com

キャリア採用

www.wantedly.com

www.wantedly.com

www.wantedly.com