打造 Laravel 優美架構

談可維護性與彈性設計

Yish

mombuyish

@yishlai

yish

Laravel MVC 目前結構

View

渲染畫面、畫面最終輸出的結果

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row">
            @foreach($posts as $post)
                <div>
                    <img src="{{ $post->name }}">
    
                    <div class="caption">
                        <h3>{{ $post->title }}</h3>
                        <p>{{$post->description }}</p>
                    </div>
                </div>
            @endforeach
        </div>

        <div>
            {!! $posts->links() !!}
        </div>
    </div>
@stop

Example

Response

回傳指定格式資料

[
    {
        id: 1,
        user_id: 2,
        title: "Hayley Satterfield",
        description: "Voluptatem ab est enim id.",
        status: 1,
        created_at: "2017-05-13 10:16:00",
        updated_at: "2017-05-13 10:16:00"
    },
    {
        id: 2,
        user_id: 3,
        title: "Rusty Batz",
        description: "Dolore et non eos molestiae voluptates sit.",
        status: 1,
        created_at: "2017-05-13 10:16:00",
        updated_at: "2017-05-13 10:16:00"
    },
....
]

Example

Controller

放置接口呼叫方法,並  response 結果

<?php

//PostController

public function store(Request $request)
{
    $post = $this->postService->create($request->all());

    return redirect()->to(route('posts.show', $post->id));
}

Example

Model

僅用於放置 Relations, Scope 和 Eloquent 類的方法

<?php

//Post

namespace App\Entities;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = [
        'subject', 'description', 'status'
    ];

    protected $table = 'posts';

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

Example

Laravel 的架構很好

but

多人協作時每個人定義不同

像是有人會...

在 route 寫 query and response

<?php

//web.php

Route::get('/', function (Post $post) {
    return view('posts.index', compact('post'));
});

//api.php

Route::get('/', function(Post $post) {
    return response()->json($post->all());
});

在 controller 寫驗證、商業邏輯、查詢與資料庫操作

ABC

他們總有一個共同的理由

反正都可以動

BUT

超難維護

你要先了解做的那個人思考過程

還有他的想法

就是過去的你

加上功能要做什麼

這跟觀落陰沒有兩樣

所以

如果一個專案很多人維護

* 也許是未來的你(通常就是)

就必須要有規範

逐步改善,加入規範

改善方法

情境

有個原本的代碼在 controller,長這個樣子

把查詢邏輯抽離到獨立的 class: Repository

UserRepository

然後再將她 DI 到 Controller

只是將查詢邏輯放到 Repository 就讓修改需求更容易

假設要修改查詢邏輯

改 Repository 即可

Controller

Model

View

Response

HTTP REQUEST

middleware

Foundation

Repository

Roadmap

Repository

與 Entities 和 DB 做查詢邏輯與資源庫

現實總沒那麼單純

文章建立

* 加入標籤

把商業邏輯放在 Service

把 Repository DI 到 Service

再將她 DI 到 Service

發信通知訂閱者

不會影響查詢邏輯(REPOSITORY)和回傳結果

Controller

Model

View

Response

HTTP REQUEST

middleware

Foundation

Repository

Roadmap

Service

Service

放置商業邏輯與流程

回傳格式轉換

得到資料

性別要在前台顯示中文

但我不能影響到查詢和商業邏輯,僅能影響外觀顯示

你也許會想到

Accessors/Mutators

https://laravel.com/docs/5.4/eloquent-mutators#accessors-and-mutators

很方便,但渲染外觀這件事給 Model?

是要讓 Model 腫起來嗎

他負責的事情已經很多了

可以利用 Service injection

https://laravel.com/docs/5.4/blade#service-injection 

Controller

Model

View

Response

HTTP REQUEST

middleware

Foundation

Repository

Roadmap

Service

Presenter

Presenter

轉換渲染顯示

前後端分離!

假如我們有個街口是查詢 user

等等!我只要名字跟性別就好了

這時候有人會想,我改 repository 的 get 欄位啊

可是瑞凡,我後台要看到資料啊

在 Reponse 之前必須作轉換處理

//UserTransformer
//UserController

需求突然要加入user狀態

No problem

//UserTransformer

Controller

Model

View

Response

HTTP REQUEST

middleware

Foundation

Repository

Roadmap

Service

Presenter

Transformer

Transformer

​轉換資料

為時麼專案 API 回傳格式都不統一啊?

我也想,但每次想法都不一樣(?

加入 Formatter,讓每次資料 response 前經過這層處理制定格式

統一格式處理

Controller

Model

View

Response

HTTP REQUEST

middleware

Foundation

Repository

Roadmap

Service

Presenter

Formatter

Transformer

Formatter

制定回傳格式

文章要有 Emoji 的功能

獨立功能

Post.php


Comment 也要加

Controller

Model

View

Response

HTTP REQUEST

middleware

validation

Foundation

Repository

Roadmap

Service

Presenter

Formatter

Transformer

Foundation

Foundation

​獨立運行方法/額外掛載功能

Q&A

Q1

我在每個專案都要這樣用嗎?

A1

依照情境和專案複雜度。

舉例來說如果今天 CURD 相當單純沒有邏輯判斷(或簡單邏輯判斷)直接用上 Repository or Eloquent 也沒有問題。

Q2

這些做法是怎麼產生的?

A2

需求,在寫測試時因為職責分配不清導致難以寫 test case,表示不單一職責,一個類負責太多事。

Q3

A3

總結

單一職責

每個累就只會有一種改變的理由

共識

和夥伴們一起取得共識

One more thing

https://github.com/Mombuyish/Laravel-Oh-Generators

$ php artisan make:service UserService
<?php

class PostsController extends Controller
{
    public function show(Post $post, PostFormatter $formatter, PostTransformer $transformer)
    {
        return response()->json($formatter->format($transform->transform($post)));
    }
}

// or using help methods.

    public function show(Post $post)
    {
        return response()->json(
            format(request(), PostFormatter::class, 
            transform(PostTransformer::class, $post)));
    }

Result

Thank you for listening.

打造 Laravel 優美架構 - 談可維護性與彈性設計

By Yi-hsuan Lai

打造 Laravel 優美架構 - 談可維護性與彈性設計

  • 1,036