核心原理
Malagu 的核心原理,帮助您更好的理解 Malagu 框架。
Malagu 框架可以开发前端、后端和其他类型应用(比如没有监听任何端口,比如命令行应用)。不管什么样的应用,都会存在一些共性的地方。Malagu 框架抽象了一个应用程序接口 Application
。这个接口足够简单,只有 start
和 stop
方法。不同类型的应用的启动方式可能不同。比如前端应用在启动时,前端 Dom 对象会挂载到 Document
上。
// 应用程序接口
export interface Application {
start(): Promise<void>;
stop(): Promise<void>;
}
应用生命周期
应用启动的过程中,会触发不同阶段的生命周期方法的执行。ApplicationLifecycle 接口主要是应用生命周期的抽象。当您需要监听应用生命周期的某个阶段时,您可以通过实现 ApplicationLifecycle 全部或部分接口,并以 id 为ApplicationLifecycle
注入到 IoC 容器即可。例如,我们在应用启动的时候初始化数据库连接池就可以实现该接口。
export interface ApplicationLifecycle<T extends Application> {
initialize?(): void;
onStart?(app: T): MaybePromise<void>;
onStop?(app: T): void;
}
独立启动应用
Malagu 框架通过命令行工具,外加框架本身的渐进式能力,使我们不用关心框架底层是如何启动的,框架会根据组件依赖情况,自动加载模块,并采用适合的方式启动应用程序,我们只需要使用 malagu serve
命令即可。在一些特殊的情况下,我们也可以独立启动应用。独立启动应用大体分为两步:
- 加载模块,构建 IoC 容器
- 获取应用程序对象,并启动应用
import "reflect-metadata";
import { ApplicationFactory } from "@malagu/core/lib/common/application/application-factory";
import { ContainerUtil } from "@malagu/core";
import { AppService } from "./app.service";
import AppModule from "./module";
async function bootstrap() {
const app = await ApplicationFactory.create({}, AppModule); // 根据应用属性配置和相关的应用模块创建应用
await app.start(); // 启动应用
const appService = ContainerUtil.get<AppService>(AppService); // 从 IoC 容器中获取指定的对象
console.log(appService.getHello());
}
bootstrap();
核心配置参考
下面是关于@malagu/core
的核心配置项。
malagu.hostDomId
用来定义前端的根元素 id,默认值:malagu-root,示例:
# malagu.yml
malagu:
hostDomId: app
malagu.annotation.Component.proxy
是否启用代理(可在组件的参数中单独启用,AOP 组件需要开启),默认值:false
# malagu.yml
malagu:
annotation:
Component:
proxy: true
malagu.aop
是否开启 aop,默认值true
# malagu.yml
malagu:
aop: false
malagu.logger.level
日志级别,默认值:info
# malagu.yml
malagu:
logger:
level: debug # 'verbose' | 'debug' | 'info' | 'warn' | 'error'
frontend.entry
指定前端应用的入口文件,默认值:lib/common/application/application-entry
# malagu.yml
frontend:
entry: lib/common/application/application-entry
backend.entry
指定 node 应用的入口文件,默认值:lib/common/application/application-entry
# malagu.yml
backend:
entry: lib/common/application/application-entry
Application 原理
Application 是 Malagu 应用的入口,以common/application-entry.ts为例:
import { container } from "../container/dynamic-container";
import { Application } from "./application-protocol";
import { ContainerProvider } from "../container";
container.then((c) => {
ContainerProvider.set(c);
const application = c.get<Application>(Application);
application.start().catch((reason) => {
console.error(`Failed to start the ${application.constructor.name}.`);
if (reason) {
console.error(reason);
}
});
});
在第六行,获取 Application 的实例,并在第七行调用了start
方法启动应用。Application
在核心包中的前后端实现如下:
start 方法
async start(): Promise<void> {
await this.doStart();
this.stateService.state = 'started';
const host = await this.getHost();
this._shell.attach(host);
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
this.stateService.state = 'attached_shell';
await this.revealShell(host);
this.registerEventListeners();
this.stateService.state = 'ready';
}
start 方法中调用了this._shell.attach(host)
来处理前端的渲染,this._shell
是ApplicationShell
的实例,ApplicationShell
在browser/shell/application-shell.ts文件中的实现如下:
// browser/shell/application-shell.ts
import { Component } from "../../common";
import { ApplicationShell } from "./shell-protocol";
@Component(ApplicationShell)
export class ApplicationShellImpl implements ApplicationShell {
attach(host: HTMLElement): void {
host.textContent = "Hello, Malagu.";
}
}
我们可以看到 attch 方法接为一个类型为HTMLElement
的参数 host,并调用host.textContent = 'Hello, Malagu.'
赋值实现了一个前端的渲染动作。
后端的Appliction
的start
方法中仅调用父类AbstractApplication
的doStart
方法处理生命周期的回调。具体的启动逻辑由对应的 package 处理。
Appliction
在日常应用开发基本接触不多,前端开发时可能需要自定义ApplicationShell
来实现渲染。
Application 相关代码文件:
IOC 容器
IoC 是面向对象编程的一种设计原则,Malagu 中主要依靠一系列的注解器(Annotation)来组织代码使之匹配这种设计原则。来看一段示例代码:
import { Component, Autowired } from "@malagu/core";
@Component("a")
export class A {}
@Component()
export class B {
@Autowired("a")
protected a: A;
}
常用的注解器如下:
Component
/Service
定义功能Autowired
挂载功能Constant
定义常量Value
挂载配置
IOC 相关代码目录如下:
- 容器实现:common/container
- IOC 相关的注解: common/annotation
AOP
AOP 面向切面编程是一种编程方式,可以作为 OOP 的补充,针对特定方法做前置后置处理。下面展示一个针对 Http 请求处理的示例:
// http-service-protocol.ts
export const HttpService = Symbol('HttpService');
export interface HttpService {
get<T>: (url: string, data: any) => Promise<T>;
post<T>: (url: string, data: any) => Promise<T>;
}
// http-service.ts
import { Service } from "@malagu/core";
import { HttpService } from "http-service-protocol";
@Service(HttpService)
export class HttpServiceImpl implements HttpService {
get<T>(url: string, data: any) {
// 发送get请求
}
post<T>(url: string, data: any) {
// 发送post请求
}
}
// http-service-before.ts
import { Aspect, MethodBeforeAdvice } from "@malagu/core";
@Aspect(MethodBeforeAdvice)
export class MethodBeforeAdviceImpl implements MethodBeforeAdvice {
async before(method: string | number | symbol, args: any[], target: any): Promise<void> {
if (method === "get" || method === "post") {
console.log(`method: ${method} url: ${args[0]} data${args[1]}`);
}
}
}
AOP 相关代码文件:
- 定义 AOP 的 interface 和相关实现: common/aop
- AOP 相关的注解: common/annotation
- autoBind 中对 AOP 类进行包装: common/container/auto-bind.ts
日志
Malagu 核心提供了对日志的封装,代码在common/logger/logger.ts:
// http-service-before.ts
import { Logger, Aspect, MethodBeforeAdvice } from "@malagu/core";
@Aspect(MethodBeforeAdvice)
export class MethodBeforeAdviceImpl implements MethodBeforeAdvice {
@Autowired(Logger)
loger: Logger;
async before(method: string | number | symbol, args: any[], target: any): Promise<void> {
if (method === "get" || method === "post") {
this.logger.info(`method: ${method} url: ${args[0]} data${args[1]}`);
}
}
}
核心包内默认的日志功能较为精简,如果有更复杂的需求可以自行定义 Logger 来扩展,或者使用@malagu/logger
包。
Error
TODO
Config
TODO
Utils
TODO