实现Service
====
引入Service的概念就是为了让控制器和业务逻辑完全分离，方便做测试和逻辑的复用，极大的提高我们的维护效率。

同样的，我们引入一个规范，我们的service必须写在service文件夹中，里面的每一个文件，就是一个或者一组相关的service.

在src根目录下，新建一个service文件夹:
新增一个service文件就叫它userService吧！
```ts
//  service/userService.ts

module.exports = {
    async storeInfo() {
        //doing something
    }
};
```
好了，我们可以在任意时候使用这个函数了，非常简单。

有一些小问题
=====
我们在写controller业务控制的时候，我们不得不在使用的时候，就去引入一下这个文件
```js
const serviceA = require('./service/serviceA')，
```
这种代码是重复的，不必要的，非常影响我们的开发速度。

我们来回顾一下controller中的一些逻辑。

```ts
import { Controller } from "./base";

//user.ts

export default class User extends Controller {
    async user() {
        this.ctx.body = 'hello user';
    }

    async userInfo() {
        this.ctx.body = 'hello userinfo';
    }
}

```
我们观察到ctx的是我们每次都会使用到的一个属性，我们其实可以把service绑定到ctx中去，我们的使用可以变成
```ts
async user() {
        this.ctx.body = 'hello user';
        this.ctx.service.xxxxxService();
    }

```
我们还需要注意到的是，无论是service还是controller的生命周期都必须是和ctx **一起创建，一起销毁的，这样才不会冲突**。

还记得我们刚刚说的吗？**每一个请求都有一个独立的ctx这个概念极其重要，在之后的旅途中，我们将频繁的使用到。**

我们最好的做法就是在koa生成context的同时，生成service，然后在context销毁的同时，也要销毁service。要做到这一点，我们必须对context这个对象进行拓展。


拓展koa的context
======
要拓展一个对象，最好的办法就是修改它的**原型**。

好就好在，koa给我们提供了context的原型。

```ts
//app.js
import * as Koa from 'koa';
import { Loader } from './loader';

const app = new Koa;

const loader = new Loader;

app.context.extends = 1; //context的原型

app.use(loader.loadRouter());

app.listen(3000, '127.0.0.1', () => {
    console.log('服务器在运行');
})

```
在koa的实例中，我们可以拿到app.context，也就是context的原型。对其添加属性，koa就可以在每个请求中一并帮我们生成这个属性。

```ts
import { Controller } from "./base";
//user.ts
export default class User extends Controller {
    async user() {
        this.ctx.body = 'hello user' + this.ctx.extends;//启动服务器后你会在这里拿到 hello user1;
    }

    async userInfo() {
        this.ctx.body = 'hello userinfo';
    }
}
```
了解到这个特性，我们就可以很方便的给context进行拓展了。

第一步：实现service
=====

首先我们创建一个service文件夹，创建一个check.ts，做为我们第一个service.

多想一步，我们的业务逻辑(service)非常依赖每一次请求的ctx，因此，我们的service中也必须要有ctx。

```ts
import { BaseContext } from "koa";

export class Service {
    ctx: BaseContext;
    constructor(ctx: BaseContext) {
        this.ctx = ctx;
    }
}

```
因此我们做一个基类。

在check.ts中写:
```ts
import { BaseContext } from "koa";

class Service {
    ctx: BaseContext;
    constructor(ctx: BaseContext) {
        this.ctx = ctx;
    }
}

class check extends Service {
    index() {
        return 2 + 3;
    }
}

module.exports = check;

```

第二步：自动扫描service，挂载于context上
=====

首先修改我们的app.ts，注意这里将app实例，传给loader
```ts
//app.js
import * as Koa from 'koa';
import { Loader } from './loader';

const app = new Koa;

const loader = new Loader(app);//注意这里将app实例，传给loader

app.use(loader.loadRouter());

app.listen(3000, '127.0.0.1', () => {
    console.log('服务器在运行');
})
```

在Loader类中，添加一个loadService方法和一个app属性。

```ts
export class Loader {
    router: Router = new Router;
    controller: any = {};
    app: Koa;
    constructor(app: Koa) {
        this.app = app;
    }
    loadService() {
        const service = fs.readdirSync(__dirname + '/service');

        Object.defineProperty(this.app.context, 'service', {
            get() {
                if (!(<any>this)['cache']) {
                    (<any>this)['cache'] = {};
                }
                const loaded = (<any>this)['cache'];
                if (!loaded['service']) {
                    loaded['service'] = {};
                    service.forEach((d) => {
                        const name = d.split('.')[0];
                        const mod = require(__dirname + '/service/' + d);

                        loaded['service'][name] = new mod(this);
                    });
                    return loaded.service;
                }
                return loaded.service;
            }
        });
    }
    ......
}
```

loadService函数是相对复杂的，因为涉及到几个思想：

- 我们想要在controller中使用```this.ctx.service.check.index();```这样的形式，那我们必须要对ctx.service的getter进行重写。
- 我们在controller中可能会多次使用到this.ctx.service对象，所以我们必须要对其进行缓存，不然每次使用，我们都遍历service文件夹，导入模块，重新生成所有service对象，再挂载，我们的性能会急剧下降。

明白了这两个思想之后，我们就可以很容易的理解loadService函数:
- 重写this.ctx.service的getter
- 第一次访问getter的时候，扫描模块，导入，并且缓存
- 判断是否有缓存，有缓存了直接返回缓存

由此，一个高性能的service就实现了.

曙光第三步：开心的使用service
=======

```ts
//service/check.ts

import { BaseContext } from "koa";

class Service {
    ctx: BaseContext;
    constructor(ctx: BaseContext) {
        this.ctx = ctx;
    }
}

class check extends Service {
    index() {
        return 2 + 3;
    }
}

module.exports = check;

```

我们在controller中轻松愉快的使用：

```ts
import { Controller } from "./base";

//user.ts

export default class User extends Controller {
    async user() {
        this.ctx.body = this.ctx.service.check.index();//注意看这里
        
    }

    async userInfo() {
        this.ctx.body = 'hello userinfo';
    }
}
```
启动服务器之后，我们调用接口，就可以看到输出了。

