基于astroboy的 DI 版本,TypeScript3.2 信仰加成
- 高性能依赖注入[ 实现:@bonbons/di ]
- 控制器声明式路由[ 实现:astroboy-router ]
- 可扩展的注入式模版引擎
- 可配置容器定义化
- DI 可注入依赖实现多重继承
- Config 体系接入 DI 容器
- 配置容器对接 astroboy 标准 configs 模式
- 自动化生成 astroboy 的 routers 规范
- 命令行工具支持
- 完整兼容 astroboy 原始语义,支持任意扩展
- 支持完整依赖注入能力的高级中间件
- 路由方法返回配置接口化
😨 更多功能正在开发中...
- Demo1:多版本并存 - 最大兼容预览(多重代码风格可以共存)
- Demo2:全量 astroboy.ts+构建 base 项目包 - base 仓库预览(构建继承 astroboy.ts 的 base 项目)
- Demo3:使用 base 仓库构建应用 - 最大预览(使用 base 项目构建应用)
- GitHub Pages - astroboy.ts 文档
npm install astroboy astroboy.ts
yarn add astroboy astroboy.ts
import path from "path";
import { Server, Astroboy } from "astroboy.ts";
// astroboy请手动安装,因为astroboy.ts只将其设置为开发依赖
// 未安装astroboy,会引发npm解析错误
Server.Create(Astroboy, {
ROOT_PATH: path.resolve(__dirname, "..")
}).run({
onStart: () => console.log("hello world!"),
onError: err => console.log(`oh shit : ${String(err)}`)
});
app/middleware/server-init.ts
import { serverInit } from "astroboy.ts";
export = () => serverInit;
config/middleware.default.js
module.exports = {
"server-init": {
priority: 0.1,
enable: true
}
};
config/config.default.js
/**
* 默认配置文件
*/
const path = require("path");
module.exports = {
"@astroboy.ts": {
showTrace: false,
diType: "native"
}
};
# 本地安装astrpboy.ts
npx atc dev --inspect --tsconfig app/tsconfig.json
# 全局装过astrpboy.ts
atc dev --inspect --tsconfig app/tsconfig.json
astroboy.ts 开放了一个配置文件,用来简化 cli 参数的使用,类似 webpack,可以使用--config 参数修改配置文件的名字。
atc.config.js - 一个简单的配置文件
const path = require("path");
module.exports = {
tsconfig: "tsconfig.json",
inspect: true,
typeCheck: true,
transpile: true,
debug: "*",
mock: "http://127.0.0.1:8001",
// atc router 的命令配置
// 编译生成routers,不再需要手动书写routers文件
routers: {
enabled: true,
always: false,
approot: "/v1",
filetype: "ts",
details: true
},
// atc-cli监控的文件修改列表,自动重启node服务
watch: [
path.join(__dirname, "app/**/*.*"),
path.join(__dirname, "config/**/*.*"),
path.join(__dirname, "plugins/**/*.*")
],
// 忽略的文件列表
ignore: [],
// atc config 的命令配置
// 编译ts配置文件,支持DI能力 @1.1.0 引入
configCompiler: {
enabled: true,
force: true,
configroot: "app/config",
outputroot: "config
}
};
控制器方面使用装饰器来定制 Router 的业务层级,确定 Route 的 url 和 method,提供 params 和 body 的注入获取能力,并抽象了响应中 body 的写入能力。
app/controllers/test.ts
import {
Controller,
Configs,
AstroboyContext,
ENV,
JsonResult,
GET,
POST,
FromParams,
FromBody,
Deserialize,
IContext
} from "astroboy.ts";
interface GetQuery {
id: string;
name: string;
type: string;
}
interface PostData {
id: number;
name: string;
}
@Controller("test")
class TestController {
// 构造函数注入能力
constructor(
private configs: Configs,
private base: AstroboyContext<IContext>
) {}
// GET: {项目前缀}/api/test/testGet/:type?id=xxxx&name=xxxx
@GET("testGet/:type")
// 显式进行params参数前提,作为路由方法参数
// 使用接口为了更好的类型描述,不会进行任何运行时类型处理
public methodGet(@FromParams() params: GetQuery) {
const { ctx } = this.base;
const { id, name, type } = params;
ctx.type = "application/json";
// JsonResult实现了IResult接口,提供将json内容编程化写入body的能力,同时提供了Configs容器的配置化支持
// 你可以自己实现自定义逻辑,只要实现IResult接口即可
return new JsonResult({
id,
name,
type,
url: ctx.url
});
}
// POST: {项目前缀}/api/post/:type?id=xxxx&name=xxxx
@POST("post/:type")
// body也能进行相似的流程实现参数前提
// 你仍然可以进行直接解构
public async methodPost(
@FromParams() params: GetQuery,
@FromBody() { id, name }: PostData
) {
const { name, id: id2, type } = params;
return new JsonResult({
id,
name,
type,
id2,
name
});
}
}
export = TestController;
到此一个业务路由层级的构建并没有完成,和原生 astroboy 开发类似,相应的需要一个 router 文件来创建 astroboy 的 router 数组定义。
app/routers/test.ts
import TEST from "../controllers/test";
import { buildRouter } from "astroboy.ts";
// “test”代表controllers内的文件级别
// “/v1”代表应用的路由前缀,这里只作为示例
export = buildRouter(TEST, "test", "/v1");
注:1.0.1-rc.27 版本以后已经支持自动生成 router,不需再要上述步骤,操作如下:
- 使用
astroboy.ts
提供的命令行工具
# 在开发启动或者生产打包前确保执行即可
./node_modules/.bin/atc router --always --filetype ts
到此一个完整的业务级别的 router 构造完成了。
astroboy.ts 按照 IoC 模式的指导思路进行设计,所有的服务都应该按照 DI 的方式(无论是手动还是自动)获取、组装和构造逻辑层级。
app/services/test.ts
import { Injectable } from "astroboy.ts";
@Injectable()
class TestService {
private value = 98765;
public add(v: number) {
this.value += v;
}
public showValue() {
return this.value;
}
}
export = TestService;
astroboy.ts 服务的默认行为是范围单例(scoped),简单的描述来说,一个服务在同一个请求流程中总是保持单例状态,并在请求结束后释放。scoped 服务可以在请求流程中的任意位置获取,并承担数据传输载体的职责。
你当然可以手动改变这一默认行为:
// 请确保你了解type的含义,以免服务出现不符合预期的行为
@Injectable({ type: InjectScope.Singleton })
class Test02Service {
...
}
其他行为:
- new(每次获取总是创建一个全新的对象)
- singleton(在整个应用中保持唯一并积累副作用)
服务还具有其他高级功能(包括依赖注入分离和实现多重继承),这里不一一展开了。
创建接口来描述 astroboy 的配置中的各个业务部分:
config/options/demo.ts
import { createConfig } from "astroboy.ts";
interface DemoOptions {
/** ccccc */
key01: number;
key02: string;
}
export const DEMO_OPTIONS = createConfig<DemoOptions>("demo");
config/config.default.js
/**
* 默认配置文件
*/
const path = require("path");
module.exports = {
"@astroboy.ts": {
showTrace: true,
diType: "proxy"
},
demo: {
key01: 12345,
key02: "woshinidie"
},
strOpt: "test_string_config"
};
这样既可以更好的描述原本混乱的 config 文件,同时可以在对 config 访问的时候提供定义支持。
// 注入Configs服务,然后获取配置
// opts变量将会被正确的绑定上类型信息
const opts = this.configs.get(DEMO_OPTIONS);
过程比较轻量,废话不多,直接上 demo:
app/middlewares/demo.ts
import { injectScope, ENV, Context } from "astroboy.ts";
import DataService from "../services/Data";
export = () =>
injectScope(async ({ injector, configs, ctx, next }) => {
// console.log(configs.get(ENV).showTrace);
const data = injector.get(DataService);
data.setStamp(new Date().getTime());
await next();
// console.log("fuck");
});
在 1.1.3-rc.16
版本引入中间件编译能力,支持使用 DI 语法来直接书写中间件。
配置文件:
const path = require("path");
// 不相关的配置信息已经隐藏
module.exports = {
tsconfig: "tsconfig.json",
// atc middleware 的命令配置
// 编译ts配置文件,支持DI能力 @1.1.03-rc.16 引入
middlewareCompiler: {
enabled: true, // 默认:false
force: true, // 默认:false
root: "middlewares", // 默认位置:middlewares/*.ts
output: "app/middlewares" // 默认输出位置:app/middlewares/*.js
}
};
middlewares/test.ts
import { AstroboyContext } from "astroboy.ts";
import * as atc from "astroboy.ts";
import { testA } from "../app/utils/testA";
export default async function testMiddleware(
context: AstroboyContext,
injector: atc.InjectService
) {
console.log(new Date().getTime());
console.log(context.ctx.url);
await testA();
await this.next();
}
执行命令: atc middleware --force
得到结果:
app/middlewares/test.js
// [astroboy.ts] 自动生成的代码
import { injectScope, IMiddlewaresScope } from "astroboy.ts";
import astroboy_ts_1 = require("astroboy.ts");
import * as astroboy_ts_2 from "astroboy.ts";
import testA_1 = require("../utils/testA");
async function testMiddleware(context, injector) {
console.log(new Date().getTime());
console.log(context.ctx.url);
await testA_1.testA();
await this.next();
}
export = () => injectScope(async ({ injector, next }: IMiddlewaresScope) => {
const _p0 = injector.get(astroboy_ts_1.AstroboyContext);
const _p1 = injector.get(astroboy_ts_2.InjectService);
await testMiddleware.call({ next }, _p0, _p1);
});
在 1.1.0
版本引入配置文件编译能力,支持使用 ts 来书写 config,同时可以将类型友好的服务化配置引入 DI。
使用 atc config --force
,强制使用 ts 配置文件夹,覆盖原始的 config
const path = require("path");
// 不相关的配置信息已经隐藏
module.exports = {
tsconfig: "tsconfig.json",
// atc config 的命令配置
// 编译ts配置文件,支持DI能力 @1.1.0 引入
configCompiler: {
enabled: true, // 默认:false
force: true, // 默认:false
configroot: "app/config", // 默认位置:app/config/**.ts
outputroot: "config" // 默认输出位置:config/**.js
}
};
下面是一个 config.default.ts
的例子:
app/config/config.default.ts
import { IStrictConfigsCompiler, ConfigReader } from "astroboy.ts";
type DIType = "proxy" | "native";
// 定义整个应用的config结构
export interface IConfigs {
"@astroboy.ts": {
showTrace: boolean;
diType: DIType;
};
demo: {
key01: number;
key02: string;
};
strOpt: string;
a: number;
b: string;
c: {
d: boolean;
e?: string;
};
f: {
v: string;
};
}
// 创建应用级别的DI项,可以在controller、service等地方注入使用
export class MyConfigsReader extends ConfigReader<IConfigs> {}
// 只要加上export标识,就可以输出到编译后的文件中去
export function woshinidie() {
xFunc();
return 123456;
}
export function xFunc() {
console.log({
a: 124,
b: "sdfad"
});
}
// 默认导出实现接口约定的类
export default function DefaultCOnfigs(): IConfigs {
const path = require("path");
return {
"@astroboy.ts": {
showTrace: true,
diType: <DIType>"proxy"
},
demo: {
key01: 12345,
key02: "woshinidie"
},
strOpt: "test_string_config",
a: woshinidie(),
b: "default",
c: {
d: false,
e: "352424"
}
};
}
书写一个环境 config 配置,比如 config.dev.ts
:
app/config/config.dev.ts
import { IConfigs } from "./config.default";
// 使用非严格接口,只提供一部分的参数,用于覆盖
export default () => (<Partial<IConfigs>{
b: "dev"
});
完成以后,在应用启动时执行:atc config --force
:
config/config.default.js
// [astroboy.ts] 自动生成的代码
const tslib_1 = require("tslib");
const astroboy_ts_1 = require("astroboy.ts");
class MyConfigsReader extends astroboy_ts_1.ConfigReader {}
function woshinidie() {
xFunc();
return 123456;
}
function xFunc() {
console.log({
a: 124,
b: "sdfad"
});
}
module.exports = (function DefaultCOnfigs() {
const path = require("path");
return {
"@astroboy.ts": {
showTrace: true,
diType: "proxy"
},
demo: {
key01: 12345,
key02: "woshinidie"
},
strOpt: "test_string_config",
a: woshinidie(),
b: "default",
c: {
d: false,
e: "352424"
}
};
})();
config/config.dev.js
// [astroboy.ts] 自动生成的代码
const tslib_1 = require("tslib");
const config_default_1 = require("./config.default");
module.exports = (() => ({
b: "dev"
}))();
文档完善中...
Copyright (c) 2018 NODE.Mogician <bigmogician@outlook.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.