Core

核心原理

Malagu 的核心原理,帮助您更好的理解 Malagu 框架。

Malagu 框架可以开发前端、后端和其他类型应用(比如没有监听任何端口,比如命令行应用)。不管什么样的应用,都会存在一些共性的地方。Malagu 框架抽象了一个应用程序接口 Application 。这个接口足够简单,只有 startstop方法。不同类型的应用的启动方式可能不同。比如前端应用在启动时,前端 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  命令即可。在一些特殊的情况下,我们也可以独立启动应用。独立启动应用大体分为两步:

  1. 加载模块,构建 IoC 容器
  2. 获取应用程序对象,并启动应用
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._shellApplicationShell的实例,ApplicationShellbrowser/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.'赋值实现了一个前端的渲染动作。

后端的Applictionstart方法中仅调用父类AbstractApplicationdoStart方法处理生命周期的回调。具体的启动逻辑由对应的 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 相关代码目录如下:

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 相关代码文件:

日志

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


Copyright © 2024 Zero (github@groupguanfang) 粤ICP备2023102563号