クイック エンジニアリングブログ

株式会社クイック Web事業企画開発本部のエンジニアリングチームが運営する技術ブログです。

Laravel5.6とVue.jsでクリーンアーキテクチャを目指す

先日の台風により楽しみにしていたライブが中止になりブルーなぱふゅーむです。

最近新規サイトの構築でアーキテクチャについて、プロジェクトメンバーで議論する機会がありました。 そのプロジェクトは新しい試みとしてスクラム開発を行なってるのですが、今回はスクラム開発の話ではなく新規サイトのアーキテクチャについて話したいと思います!

スクラムについてはスクラムマスターのフルーツパーラーが記事を書いていますので興味がある方は是非!

スクラム開発はじめます - 株式会社クイックのWebサービス開発blog

技術要件

使用する技術セットについては以下になります。今回の話にはあまり関係がないところですので、詳細は割愛させていただきます。

全体構成

f:id:aimstogeek:20181004160749p:plain

アーキテクチャは大まかに分けてこのような構成になっています。

  • Controllerクラス・・・viewを返すControllerとデータのやりとりをするApiController
  • Serviceクラス・・・ユースケースを纏めたクラス
  • Repositoryクラス・・・DBとやりとりをするクラス
  • Modelクラス・・・Entity

Controllerクラス

Controllerは以下2種類のコントローラに分けています。

  • 単純にviewを返すController(ViewController)
  • 実際にデータの受け渡しを行うApiController

アプリケーションがRequestを受け取るとまずController(ViewController)はリクエストパラメータをviewに返します。この段階では特にデータの取得・削除などは行いません。単純にリクエストパラメータとともにviewを返すようなイメージです。

viewはいくつかのvueコンポーネントから構成されています。各vueコンポーネントはControllerから必要なパラメータを取得し、表示に必要な情報を取得するため再度非同期で各ApiControllerにRequestを送ります。

基本的にはcomponentとApiControllerは1対1の関係を考えています。ケースによっては多対1(複数コンポーネントから呼ばれるApiController)にはなりえるかもしれませんが、1対多はできる限り避けたいと考えています。理由としては1対多の関係になると、トランザクションをApiControllerではれなくなるためです。

下記はApiControllerの一例です。

<?php

namespace App\Http\Controllers\Api;

class UserController extends \App\Http\Controllers\ApiController
{
    /**
     *
     *
     * @param  int          $id
     * @return \Illuminate\Http\JsonResponse
     */
    public function detail(int $id): \Illuminate\Http\JsonResponse
    {
        $service = new \App\Services\UserService();

        return $this->renderJson([
            'user' => $service->fetchUser($id),
        ]);
    }

Userを取得する処理は後述するServiceクラスに任せます。

Serviceクラス

Serviceクラスはユースケースをまとめたものです。例としてUserServiceクラスをあげます。Userに関わる振る舞い(ユースケース)としては以下のようなものが考えられます。

  • ユーザ情報を取得
  • ユーザ情報を登録
  • ユーザ情報を編集

上記のような振る舞いをまとめたものがUserServiceクラスになります。 Serviceクラスで重要なことはビジネスロジックのみを記述し、データ取得などModelとのやりとりはRepositoryクラスに任せることです。(取得したデータをApiControllerに渡すために加工が必要な場合はServiceクラスで行います。)

設計の段階でServiceクラスとRepositoryクラスを分けるかどうかメンバー間で議論をしたのですが、Serviceクラスでビジネスロジックとデータ取得等DBとのやりとりを一緒に書くと、冗長な記述が多くなり、Fatロジックになりがちとの結論により分離することになりました。

一例としてユーザ情報を編集する処理がUserServiceクラス以外で必要になったとしましょう。その場合Serviceクラスをビジネスロジックの記述のみに抑えることにより、実際にDBにアクセスしてユーザ情報を編集するという処理を共通化することができます。

Repository層にDBとのやりとりを任せることで、各Serviceクラスから使いまわすことができるようになります。

<?php

namespace App\Services;

class UserService
{
    /**
     *
     *
     * @param  int          $id
     * @return \App\Models\User|null
     */
    public function fetchUser(int $id): ?\App\Models\User
    {
        $repo = new \App\Repositories\UserRepository();

        return $repo->find($id);
    }

Repositoryクラス

LaravelのEloquentモデルはEntityとしての役割と、DBのデータ操作をする2つの責務を持っていますが、その責務を分離したかったためRepositoryクラスを実装することになりました。Repositoryクラスはデータの取得・編集・削除など DBとのやりとりを行うクラスになります。ModelクラスはあくまでEntityとしての振る舞いを持つクラスにとどめます。

<?php

namespace App\Repositories;

class UserRepository
{
    /**
     *
     *
     * @param  int          $id
     * @return \App\Models\User|null
     */
    public function find(int $id): ?\App\Models\User
    {
        return \App\Models\User::find($id);
    }

Modelクラス

Modelクラスは単純にEntityとしての振る舞いのみになっています。DBとのやりとりは前述したRepositoryクラスに任せます。Modelはテーブルと1対1の関係になります。

今後に向けて

プロジェクトメンバーでこういったアーキテクチャの話ができたのは非常に有意義で楽しかったです!これがベストなアーキテクチャかどうかはまだ分かりませんが、個人的にはスケールしやすい、いい設計ができたのではないかと思っています。現在ようやく設計フェーズが終了し実装に入ってきたところです。今後もプロジェクトメンバーで議論しながらよりよい柔軟なアーキテクチャを目指していきたいですね!では!ʅ(◔౪◔ ) ʃ


\\ 『明日のはたらくを創る』仲間を募集中!! // 919.jp