💪

FizzBuzzで雑に学ぶレイヤードアーキテクチャ風API

2022/01/14に公開

概要

FizzBuzzを初めて書いてから6年ほど経過しましたが、久々に書いてみようと思ったので書きました!単純に書いても面白くないと思い、レイヤードアーキテクチャのAPI風にJavaScriptで書いてみたのでよかったら見てください🙇‍♂️

レイヤードアーキテクチャとは?

ざっくりした理解ですが、APIにおけるレイヤードアーキテクチャの具体例は下記のイメージになります。

Endpoint
↓↑ JSON
Handler: リクエスト/レスポンス生成
↓↑ Model                 Model
Usecase: Repositoryでデータ処理   ←-------|
↓↑ Model                                ↓ 
Repository: DBとのやりとりを行う        Adapter: 外部API通信
↓↑ Model                                ↓↑ Model
Database                             ExternalAPI

これは「関心の分離」と「依存方向の限定」を行い、システムの保守性 (変更容易性) を向上させることを目的としています。それぞれの関心事ごとにレイヤーを分けて実装して、レイヤーごとの依存方向は上位から下位への1方向にしましょうっていう雰囲気ですね。

RailsやLaravelだとフレームワークが提供するMVCアーキテクチャを使うのが一般的ですが、自分が好きなGo言語とかでAPI書くときは上記のようにレイヤーわけしながら実装することが業務上でも多いです。

もう少しちゃんと知りたい方は下記の書籍を読むと良いかもしれません。

エリック・エヴァンスのドメイン駆動設計 - amazon

FizzBuzzをAPI化する

APIがやることは基本的に上記の図の内容になります。
今回は下記のような流れで実装しました。

  1. "fizz"は3で割り切れるとき, "buzz"は5で割り切れるとき等の概念を表すモデルを実装(Token)
  2. Tokensを取得するリポジトリを実装(今回はDBが無いのでモック的に定義)
  3. リポジトリからTokensを取得して、割り切れた文字列を結合して返すユースケースを実装
  4. リクエストを受け取り, ユースケースで処理をさせ, レスポンスを定義するハンドラを実装
  5. 各レイヤーをインスタンス化して、コンストラクタに依存インスタンスを注入 (DI)
  6. エントリーポイントを実装 (呼び出し元)

実際のAPI開発でも、下の層から実装していくとスムーズに実装ができます。

なぜDIをするの?

色々な理由がありますが、自分が実感してる利点は「各レイヤーのユニットテストが書きやすい」という点だと考えています。実際に動くコードは実態のあるレイヤーをインスタンス化してコンストラクタで渡して、テストコードは仮の戻り値を返すモックレイヤーをインスタンス化してコンストラクタで渡して上げれば良い感じに動作します。

実際に書いたコード

こちらになります、ざっくりしか確認していませんが多分合っていると思います。
ビジネスロジックは抽象化しているので、fizzやbuzz以外の文字列や、別の数字で割り切れるときに変更しても動作します。

// モデル
class Token {
    constructor(name, dividNumber) {
        this.name = name
        this.dividNumber = dividNumber
    }
    
    isDivisible(n) {
        return n % this.dividNumber === 0
    }
}

// リポジトリ (データストアへのアクセス)
class FizzBuzzRepository {
    findAll() {
        return [
            new Token("fizz", 3),
            new Token("buzz", 5)
        ]
    }
}

// ユースケース (ビジネスロジック)
class FizzBuzzUsecase {
    constructor(fizzBuzzRepository) {
        this.fizzBuzzRepository = fizzBuzzRepository
    }
    
    exec(n) {
      const tokens = this.fizzBuzzRepository.findAll()
      const dividedTokens = []
      for(var token of tokens) {
          if (!token.isDivisible(n)) continue
          dividedTokens.push(token)
      }
      return dividedTokens.length !== 0 ? dividedTokens.reduce((x, y) => x + y.name, "") : n
    }
}

// ハンドラ (リクエスト/レスポンスの処理)
class FizzBuzzHandler {
    constructor(fizzBuzzUsecase) {
        this.fizzBuzzUsecase = fizzBuzzUsecase
    }

    get(request) {
        const result = this.fizzBuzzUsecase.exec(request.n);
        return {answer: result}
    }
}

// DI (静的型付けならInterfaceの定義も必要)
fizzBuzzRepository = new FizzBuzzRepository()
fizzBuzzUsecase = new FizzBuzzUsecase(fizzBuzzRepository)
fizzBuzzHandler = new FizzBuzzHandler(fizzBuzzUsecase)

// テスト
for (var i=1; i < 100 ; i++) {
  request = {n: i}
  response = fizzBuzzHandler.get(request)
  console.log(response)
}

上記を実行した結果は画像の通りになります。

さいごに

レイヤードアーキテクチャについて、友人に説明してもイメージできないとの話があったので、誰もが知ってる有名な FizzBuzz で雰囲気再現してみました。個人的にソフトウェアアーキテクチャの話は好きですが、界隈的にこの手の話題は燃えやすい印象もあります。本記事はざっくり書いたとはいえレイヤードアーキテクチャのコードってこんな感じだよって伝えたい意図があるので、もし誤解を生む表現があったり、間違っている記載があれば遠慮なくコメントで教えていただけますと幸いです!

Discussion