你真的懂怎么写`服务层`吗?

真正的服务层是怎么写的?
其实不少系统架构里面都有服务层,可是服务对不少开发人员来讲都有不少不一样的定义和写法。甚至在我待过的公司里都有不一样的写法和编写模式。每一个人每一个团队每一个项目都有对服务不一样的理解。那到底什么是服务,怎么理解才是对的呢?php

大家有没有过无数个夜晚里严重怀疑人生,琢磨着到底哪种服务才是对的?哪种才是最好的写法,哪种才能达到服务的真正意义?由于这种执着,我开始在国外的各类网站,大神们写过的开源大项目里面和文章里面总结出一个大多数研发伙伴们承认的理解方式和编写方式。程序员

要理解什么是服务,咱们先来给服务一个定义,在系统架构里面处于什么角色做用是什么。web


服务定义

角色:服务是系统架构里面的业务处理层。
做用:主要是为了高度解耦和封装不一样场景的业务和功能到对应的服务,然而达到高度中心化的业务代码。设计模式

这个定义没毛病吧?赞同的童鞋在评论里举个手哈 👋。
好,有了一个优雅高尚的服务定义,咱们来用一个通俗易懂的例子来理解服务。服务器


理解服务

  • 假设是一个控制器,如今拿到了一个衣服对象参数,而后人拥有一个洗衣服方法
  • 如今人须要洗衣服,可是手洗效率过低了,因此咱们写了一个多功能的洗衣机服务给到人去使用
  • 洗衣机这个服务里面有不少不一样洗衣服的方法,可是其实具体洗衣机里面的每个清洗方法人是不知道怎么实现的,人都是直接按照提供的功能直接使用。
  • 因此全部服务里面的方法都是解耦在服务里面,服务要提供的方法是能够方便人使用的。

这样说是否是很好理解了?因此最简单的理解就是:架构

服务是用来封装业务逻辑代码,是一个独立的逻辑层,高度封装解耦后提供给控制器或者其余须要用到这个服务的地方使用的。异步


编写思路

错误例子ide

把全部洗衣机的方法提供给人使用,那就等同于让人来决定全部洗衣机的参数和清洗步骤。那人放衣服到洗衣机后,要选择先加水,加多少水,而后清洗开始,清洗多久,再甩干等等。svg

就想一想这个洗衣机就不想用了,洗个衣服那么多选项,还要想那个设置顺序才是对的! 我太难了!洗个鸡腿哦!(ノ`□ ´)ノ⌒┻━┻微服务

⭕️ 正确例子

洗衣机服务实现了不少不一样的经常使用洗衣服的模式, 好比快速清洗,毛衣清洗,地毯清洗,风干,甩干等等。都是一些经常使用的功能。
每一个功能方法里面其实调用了不少洗衣机封装好的流程和方法。这样人使用洗衣机根本不须要知道这些功能是怎么实现的,只要知道本身要干吗,洗衣机有这个模式,直接用就行了。

(✧ᗜ✧)👍哇! 介么人性化的么!这种洗衣机给我来一打谢谢!
思路咱们整理清楚了,那么能够开始看看用这种思惟模式写成代码是怎么样的。来上机械键盘,开始快乐滴敲代码了!


服务写法

由于本人是用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