你真的懂怎麼寫`服務層`嗎?

真正的服務層是怎麼寫的?
其實很多系統架構裏面都有服務層,但是服務對很多開發人員來說都有很多不同的定義和寫法。甚至在我待過的公司裏都有不同的寫法和編寫模式。每個人每個團隊每個項目都有對服務不同的理解。那到底什麼是服務,怎麼理解纔是對的呢?

你們有沒有過無數個夜晚裏嚴重懷疑人生,琢磨着到底哪一種服務纔是對的?哪一種纔是最好的寫法,哪一種才能達到服務的真正意義?因爲這種執着,我開始在國外的各種網站,大神們寫過的開源大項目裏面和文章裏面總結出一個大多數研發夥伴們認可的理解方式和編寫方式。

要理解什麼是服務,我們先來給服務一個定義,在系統架構裏面處於什麼角色作用是什麼。


服務定義

角色:服務是系統架構裏面的業務處理層。
作用:主要是爲了高度解耦和封裝不同場景的業務和功能到對應的服務,然而達到高度中心化的業務代碼。

這個定義沒毛病吧?贊同的童鞋在評論裏舉個手哈 👋。
好,有了一個優雅高尚的服務定義,我們來用一個通俗易懂的例子來理解服務。


理解服務

  • 假設是一個控制器,現在拿到了一個衣服對象參數,然後人擁有一個洗衣服方法
  • 現在人需要洗衣服,但是手洗效率太低了,所以我們寫了一個多功能的洗衣機服務給到人去使用
  • 洗衣機這個服務裏面有很多不同洗衣服的方法,但是其實具體洗衣機裏面的每一個清洗方法人是不知道怎麼實現的,人都是直接按照提供的功能直接使用。
  • 所以所有服務裏面的方法都是解耦在服務裏面,服務要提供的方法是可以方便人使用的。

這樣說是不是很好理解了?所以最簡單的理解就是:

服務是用來封裝業務邏輯代碼,是一個獨立的邏輯層,高度封裝解耦後提供給控制器或者其他需要用到這個服務的地方使用的。


編寫思路

錯誤例子

把所有洗衣機的方法提供給人使用,那就等同於讓人來決定所有洗衣機的參數和清洗步驟。那人放衣服到洗衣機後,要選擇先加水,加多少水,然後清洗開始,清洗多久,再甩乾等等。

就想想這個洗衣機就不想用了,洗個衣服那麼多選項,還要想那個設置順序纔是對的! 我太難了!洗個雞腿哦!(ノ`□ ´)ノ⌒┻━┻

⭕️ 正確例子

洗衣機服務實現了很多不同的常用洗衣服的模式, 比如快速清洗,毛衣清洗,地毯清洗,風乾,甩乾等等。都是一些常用的功能。
每個功能方法裏面其實調用了很多洗衣機封裝好的流程和方法。這樣人使用洗衣機根本不需要知道這些功能是怎麼實現的,只要知道自己要幹嘛,洗衣機有這個模式,直接用就好了。

(✧ᗜ✧)👍哇! 介麼人性化的麼!這種洗衣機給我來一打謝謝!
思路我們整理清楚了,那麼可以開始看看用這種思維模式寫成代碼是怎麼樣的。來上機械鍵盤,開始快樂滴敲代碼了!


服務寫法

因爲本人是用PHP做開發比較多,我這裏就用PHP來做服務的一個例子,其實其他語言都是大同小異。只要你懂得服務的定義。其實都通用的。

Controller 控制器

首先我們寫一個人控制器PersonController.php,作爲一個優秀的人類,我們天生就會洗衣服,但是人嘛天生就是懶惰的。所以我們買了一臺洗衣機(實現洗衣機服務)並且我們學會了使用洗衣機來洗衣服。(實現wash方法)٩(◦`꒳´◦)۶

一個人PersonController,有一個洗衣服方法wash,需要洗衣服的時候實例洗衣服務new WashingMachineServer(),然後只要把衣服傳入洗衣機服務的快洗方法,洗衣機服務就會開始快速quickWash($cloth)清洗了。

// 人控制器
class PersonController
{
    /**
    * 洗衣服方法
    * 
    * @param object $cloth 衣服對象
    */
    public function wash($cloth)
    {
        $washingMachine = new WashingMachineService();
        $washingMachine->quickWash($cloth); // 調用洗衣機的快速清洗功能
    }
}

我們好奇的童鞋們,肯定會好奇,那這個洗衣機(WashingMachineService.php服務) 到底是怎麼實現的呢?它的快洗功能是怎麼做的呢?那我們就來自己建一部洗衣機,自然就懂了。

Service 服務

動手之前我們要先思考,先分析,養成這樣的好習慣,代碼再也不難寫了。

分析的重點分爲服務的運作流程, 可變動的屬性,最後就是有那些可以提供的模式

  • 洗衣機應該怎麼運作流程的:
    1. 把衣服放入洗衣機 addCloth()
    2. 注入水到洗衣機裏 addWater()
    3. 開始洗衣服(開始旋轉和各種累活)wash()
    4. 把水排除洗衣機 flushWater()
    5. 把衣服取出 fetchClouth()
  • 洗衣機可變動的屬性
    • 要把衣服放入洗衣機,我們就需要有個東西來裝着,然後才能清洗,所以我們應該有一個洗衣桶 $bucket
    • 根據衣服的量,使用的水量是應該可以調節的。(對我們要節約用水嘛)$washDuration
  • 洗衣機最常用的模式
    • 快速洗 quickWash()

⚠️ 需要注意:

  • 所有洗衣機的內部方法都是 private 私有方法,因爲都是給洗衣機使用的,外部的人是不能使用的;
  • 快速清洗取衣服這兩個方法是 public 共有方法,因爲是洗衣機提供出去給人使用的方法;
  • 所有屬性都是 protected 保護屬性,是洗衣機獨有的屬性。

現在我們就要使用程序員的魔法,把以上的邏輯和屬性轉換成代碼。(∩◉ω◉)⊃----★

class WashingMachineService
{
    /**
    * 清洗時長 (分鐘)
    * @var integer
    */
    protected $washDuration = 60;
    
    /**
    * 洗衣機的洗衣桶
    * @var array
    */
    protected $bucket;
    
    /**
    * 改變默認洗衣機的清洗時長
    * @param integer $duration
    */
    public function changeWashDuration($duration)
    {
        $this->washDuration = intval($duration);
        
        return $this;
    }
    
    /**
    * 往洗衣機的桶加入水
    */
    private function addWater()
    {
        array_merge($this->bucket, ['water' => 'cold water']);
        
        return $this;
    }
    
    /**
    * 把衣服加入洗衣機桶內
    */
    private function addCloth($cloth)
    {
        array_merge($this->bucket, ['cloths' => $cloth]);
        
        return $this;
    }
    
    /**
    * 旋轉桶把開始洗衣服
    */
    private function wash()
    {
        // 使用洗衣機的清洗時長來全換清洗衣服
        for ($duration = $this->washDuration; $duration > 0; $duration--) {
            array_rand($this->bucket, 3);
        }
        
        return $this;
    }
    
    /**
    * 把桶裏面的水清除掉
    */
    private function flushWater()
    {
        unset($this->bucket['water']);
        
        return $this;
    }
    
    /**
    * 從洗衣桶裏面把衣服拿回出來
    */
    private function fetchCloths()
    {
        return $this->bucket['cloths']
    }
    
    /**
    * 快速清洗衣服方法
    */
    public function quickWash($cloth)
    {
        return $this->changeWashDuration(10) // 重新設置洗衣服的時長
                    ->addCloth($cloth) // 加入衣服
                    ->addWater() // 加入水
                    ->wash() // 開始清洗
                    ->flushWater() // 清除水
                    ->fetchCloths(); // 最後取出衣服返回
    }
}

以上就是一個最基礎的服務,有獨立的內部方法可以讓服務運作起來,也有提供出去的服務模式方法。

⚠️ 需要注意:
服務的重點特性在最後這個 quickWash 快速清洗方法。實現快速清洗是通過使用特定順序組合方式調用洗衣機內部方法。這種服務的實現方式,可以把一個服務裏面的業務邏輯拆分成多個邏輯塊,然後通過不同的順序和組合來實現某種模式或者功能。這樣的服務就非常有彈性,而且所有邏輯塊複用性極高。這個也是設計模式裏面的模版方法模式(Template Method)

上面的例子只是寫了一個洗衣機10%不到的功能,一個完整的洗衣機還會有很多的邏輯方法。那問題就來了,方法多了這個服務就會開始臃腫。這個時候我們就要想一套解耦封裝服務的方式方法。接下來我們來講解一下怎麼更深度的服務封裝。


服務封裝

在日常開發過程中,我們有各種各樣的封裝和解耦方式。包括內部Trait, 內部服務工廠設計模式。這幾種都是可以用來深度封裝服務的方式方法。找到了方法,下一步就是要找到怎麼封裝纔是最優解耦思路。解耦的原理就是找到共通點公用點。然後把這些方法封裝起來,解耦出去。

封裝思路

在上面寫的洗衣機服務,裏面的洗衣桶是很通用的和獨立的業務邏輯。所以它是可以解耦封裝在一起的。

  • 洗衣機的bucket洗衣桶屬性的方法其實可以封裝起來。單獨做爲一個洗衣桶的服務。
  • 所有涉及洗衣桶操作的功能和流程都封裝到洗衣桶服務裏面給洗衣機調用。

使用上面的邏輯,我們可以把洗衣機服務洗衣桶服務拆分成兩塊。來吧上機械鍵盤!


封裝編寫

  • 洗衣機服務 WashingMachineService.php
class WashingMachineService
{
    /**
    * 清洗時長 (分鐘)
    * @var integer
    */
    protected $washDuration = 60;
    
    /**
    * 改變默認洗衣機的清洗時長
    * @param integer $duration
    */
    public function changeWashDuration($duration)
    {
        $this->washDuration = intval($duration);
        
        return $this;
    }
    
    /**
    * 快速清洗衣服方法
    */
    public function quickWash($cloth)
    {
        $washingBucket = new WashingBucketService();
        
        $this->changeWashDuration(10) // 重新設置洗衣服的時長
        
        // 調用洗衣機的桶去清洗衣服
        return $washingBucket->addCloth($cloth) // 加入衣服
                    ->addWater() // 加入水
                    ->wash($this->washDuration) // 開始清洗
                    ->flushWater() // 清除水
                    ->fetchCloths(); // 最後取出衣服返回
    }
}
  • 洗衣桶服務 - WashingBucketService.php
class WashingBucketService
{
    /**
    * 洗衣機的洗衣桶
    * @var array
    */
    protected $bucket;
    
    /**
    * 往洗衣機的桶加入水
    */
    public function addWater()
    {
        array_merge($this->bucket, ['water' => 'cold water']);
        
        return $this;
    }
    
    /**
    * 把衣服加入洗衣機桶內
    */
    public function addCloth($cloth)
    {
        array_merge($this->bucket, ['cloths' => $cloth]);
        
        return $this;
    }
    
    /**
    * 旋轉桶把開始洗衣服
    */
    public function wash($washDuration)
    {
        // 使用洗衣機的清洗時長來全換清洗衣服
        for ($duration = $washDuration; $duration > 0; $duration--) {
            array_rand($this->bucket, 3);
        }
        
        return $this;
    }
    
    /**
    * 把桶裏面的水清除掉
    */
    public function flushWater()
    {
        unset($this->bucket['water']);
        
        return $this;
    }
    
    /**
    * 從洗衣桶裏面把衣服拿回出來
    */
    public function fetchCloths()
    {
        return $this->bucket['cloths']
    }
}

提供和調用

模塊與模塊或者系統與系統直接都會使用到服務來互相打通業務。這個時候服務就要有一個方式提供出去讓外部的模塊或者系統調用。

⚠️ 需要注意:
這裏說的是外部模塊或者系統調用,這個是要考慮到如果是微服務的話,每個模塊都會在不同的服務器和域名下,這個時候就需要異步調用。這種情況下如果還是用類實例的方式來提供和調用服務後面要改就很麻煩了。

這種情況下目前最優的方式就是服務提供者用Trait給到服務使用者來注入到業務代碼裏面。

  • 洗衣機服務Trait - WashingMachineProvider.php
trait WashingMachineProvider
{
    /**
    * 提供洗衣機服務類
    */
    public washingMachine()
    {
        return new \WashingMachineService();
    }
}

⚠️ 需要注意:
這裏是使用了命名空間來實例洗衣機服務類的。但是如果改成了微服務,那我們只需要改掉所有這些服務提供Trait,把服務類實例改爲服務發現,或者異步服務調用就可以了。再也不用花錢去買霸王洗髮水了。٩(^ᴗ^)۶


總結

經歷了千辛萬苦,無數個失眠的夜晚。終於知道服務到底是什麼,應該怎麼寫,怎麼寫纔是對的。寫好服務可以提高代碼的維護性,編寫的代碼也會有更強的邏輯和條理。好的服務也會有更好的彈性和擴張性。下面我們來總結一下編寫服務的重點。

角色: 服務是系統架構裏面的業務處理層。
作用: 主要是爲了高度解耦和封裝不同場景的業務和功能到對應的服務,然而達到高度中心化的業務代碼。
思路: 邏輯要獨立,分解成邏輯塊,保持複用性高,儘量不要限定邏輯使用的順序和高彈性的組合性。
編寫: 高度封裝,高內聚的原理來編寫服務,細化分解通用性,公用性的業務,然後封裝成一個服務。


#通過技術悟出人生道理# 💭
“大千世界每一件事都有千百萬種做法,
吸收,打磨,專研,總結,進步,
纔會找到最適合的做法。” ~ 三·鑽 TriDiamond

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章