架構設計準則 筆記 - fantasy0107/notes GitHub Wiki

自己覺得的演化

依序往下細分將邏輯慢慢分開

  1. model + controller
  2. cache + repository + service + presenter(transformer)
  3. checker + constant + support + Exception code
  4. validator + (concrete)

資料層級

Alt text

Controller 控制器

  1. 使用 Service 或 Concrete 的商業邏輯
  2. 做資料交易控制 (transaction)
  3. 使用 Checker 去檢查任何使用者傳進來的資料,確保資料的正確性

範例

class PostController extends Controller
{
    public function __construct(
        PostConcrete $PostConcrete,
        PostService $PostService,
        CommentService $CommentService,
        PostChecker $PostChecker
    )
    {
        $this->PostConcrete = $PostConcrete;
        $this->PostService = $PostService;
        $this->CommentService = $CommentService;
        $this->PostChecker = $PostChecker;
    }

    // 顯示文章
    public function show($post_id) {
        try {
            // 驗證資料
            $input = [
                'post_id' => $post_id
            ];
            $this->PostChecker->checkShow($input);

            // 撈取文章
            $Post = $this->PostConcrete->findPost($post_id);

            // 撈取文章留言
            $Comment = $this->CommentService->getCommentByPostId(post_id);
        } catch (Exception $exception) {
            throw $exception
        }
    }

    // 更新文章
    public function update($post_id) {
        try {
            // 驗證資料
            $input = request()->all();
            $input['post_id'] = $post_id;
            $this->PostChecker->checkUpdate($input);

            // 交易開始
            DB::beginTransaction();

            // 更新文章
            $Post = $this->PostService->update($post_id, $input);

            // 交易結束
            DB::commit();
        } catch (Exception $exception) {
            // 交易失敗
            DB::rollBack();
            throw $exception
        }
    }
}

Service 服務

  1. 商業邏輯
  2. 資料驗證
  3. 組合不同的 Repository 資料
  4. 需要跨表格處理資料才使用
  5. 牽涉到外部行為 : 如發送Email,使用外部API
  6. 使用PHP寫的邏輯 : 如根據購買的件數,有不同的折扣

範例

class PostService {
    public function __construct(
        PostRepository $PostRepository,
        PostTagRepository $PostTagRepository
    )
    {
        $this->PostRepository = $PostRepository;
        $this->PostTagRepository = $PostTagRepository;
    }

    // 撈取文章
    public function findPost($post_id) {
        try {
            // 撈取文章
            $Post = $this->PostRepository->find($post_id);
            // 撈取文章標籤
            $Post->tag = $this->PostTagRepository->getByPostId($post_id);

            return $Post;
        } catch (Exception $exception) {
            throw $exception
        }
    }

    public function clearPostCache($post_id) {
        try {
            $Post = $this->findPost($post_id);
            // 清除文章快取
            $this->PostRepository->cache()->clearPostCache($Post);
            // 清除文章標籤快取
            $this->PostTagCache->clearPostTagCache($Post->tag);
        } catch (Exception $exception) {
            throw $exception
        }
    }
}

Repository 資料庫

  1. 撈取屬於自己 Model 不同條件下的資料
  2. 快取控制
  3. method 名稱中指出這個方法是要撈什麼樣的資料
  4. 呼叫單一表格資料邏輯應使用Repository (資源庫)

PostRepository->getWeekTopPosts(); // 取得本週熱門文章

範例

class PostRepository {
    public function __construct(
        Post $Post,
        PostCache $PostCache
    )
    {
        $this->Post = $Post;
        $this->PostCache = $PostCache;
    }

    public function cache() {
        return $this->PostCache;
    }

    public function find($post_id) {
        try {
            $cache_key = $this->cache()->getPostCacheKey($post_id);
            if (Cache::has($cache_key)) {
                // 有快取資料
                $Post = Cache::get($cache_key);
                return $Post;
            }

            // 撈取資料庫文章資料
            $Post = $this->Post->find($post_id);

            if(!is_null($Post)) {
                // 紀錄快取
                $this->cache()->putPost($Post);
            }

            return $Post;
        } catch (Exception $exception) {
            throw $exception
        }
    }

    public function findLatestPost() {
        try {
            $cache_key = $this->cache()->getLatestPostCacheKey($post_id);
            if (Cache::has($cache_key)) {
                // 有快取資料
                $$Posts = Cache::get($cache_key);
                return $Post;
            }

            // 撈取資料庫文章資料
            $Post = $this->Post
                ->order('created_at', 'desc')
                ->first();

            if(!is_null($Post)) {
                // 紀錄快取
                $this->cache()->putLatestPost($Post);
            }

            return $Post;
        } catch (Exception $exception) {
            throw $exception
        }
    }
}

Model 模型

  1. 資料表存取相關設定
  2. 越乾淨越好
  3. Property : 如$table,$fillable…等
  4. Mutator: 包括 mutator 與 accessor。
  5. Method : relation 類的 method,如使用 hasMany() 與 belongsTo()。

Model 範例

class Post extends Model
{
    protected $table = 'post';
    
    protected $fillable = [];

    protected $primaryKey = 'id';

    protected $dates = ['created_at', 'updated_at'];

    protected $presenter = 'PostPresenter';
}

Presenter 資料呈現

  1. 協助 Model 做資料呈現處理
  2. 有點像 laravel model cast property
  3. 這一層可以做成 transformer 變成轉換回傳格式
class PostPresenter extends Presenter
{
    public function created_at_human_time()
    {
        return $this->created_at->diffForHumans();
    }
}

Presenter 範例

Cache 快取

  1. 協助 Repository 做快取資料的控制

快取 範例

class PostCache {

    public function getPostCacheKey($post_id) {
        // 撈取文章快取鍵值
    }

    public function putPost(Post $Post) {
        // 紀錄文章快取
    }

    public function clearPostCache(Post $Post) {
        // 清除文章快取
    }
}

Checker 檢查器

  1. 協助 Controller 做資料驗證
  2. Checker (檢查器) 只檢查多表格資料內容

範例

class PostValidator {
    public function checkFindPost($input){
        // 驗證文章資料
        $this->PostValidator->validatePostId($input);
        $this->PostValidator->validatePostContent($input);

        // 驗證會員資料
        $this->MemberValidator->validateMemberId($input);
    }
}

Validator 驗證器

  1. 協助 Checker 做資料驗證
  2. Validator (驗證)只檢查單一表格資料內容

範例

class PostValidator {
    public function validatePostId($input){
        // 設定驗證規則
        $rules = [
            'post_id' => [
                'required',
                'max:20',
            ],
        ];

        // 開始驗證
        $this->validator = Validator::make($input, $rules);

        if ($this->validator->fails()) {
            throw new Exception(
                '文章編號格式錯誤',
                PostExceptionCode::POST_ID_FORMAT_ERROR
            );
        }
    }
}

Concrete 服務組合

  1. 協助 Controller 組合不同 Service 的資料成商業邏輯

範例

class PostConcrete {
    public function __construct(
        PostService $PostService,
        UserService $UserService
    )
    {
        $this->PostService = $PostService;
        $this->UserService = $UserService;
    }

    // 撈取文章資料
    public function findPost($post_id){
        try {
            // 撈取文章
            $Post = $this->PostService->findPost($post_id);
            // 撈取文章作者資料
            $user_id = $Post->user_id;
            $Post->user = $this->UserService->findUser($user_id);

            return $Post;
        } catch (Exception $exception) {
            throw $exception
        }
    }
}

Constant 常數

  1. 共用變數名稱設定
  2. 皆為靜態變數

範例

class PostConstant {
    const POST_TYPE_PUBLIC = 'P';
    const POST_TYPE_DELETE = 'D';
}

Support 支援

  1. 共用方法
  2. 皆為靜態方法

範例

class PostSupport {
    // 撈取所有文章類型
    public static function getAllPostType() {
        $all_post_type = [
            PostConstant::POST_TYPE_PUBLIC,
            PostConstant::POST_TYPE_DELETE,
        ];
        return $all_post_type;
    }
}

ExceptionCode 例外代碼

  1. 共用例外代碼設定
  2. 錯誤代碼 (error_code) 會在不同的地方被存取 為了管理及閱讀性方便 分出這一層
  3. 皆為靜態變數

範例

class PostExceptionCode {
    const POST_ID_FORMAT_ERROR = 10000001;
    const POST_NOT_FOUND = 10000002;
    const POST_TAG_NOT_FOUND = 10000003;
}

注意

不能跨 2 階層以上存取

Controller 不能存取 Repository

低階層的不能存取高階層的資料

Model 不能存取 Repository

同一個資料類型,不能互相呼叫

Concrete 不能呼叫 Concrete

參考資料

架構設計準則 I
架構設計準則 II