@imean/microservice@1.5.1
Microservice Framework for Deno
一个轻量级的 TypeScript 微服务框架,专为 Deno 设计。提供了类型安全、自动客户端生成、请求重试等特性。
特性
- 🦕 专为 Deno 设计
- 📝 完全的 TypeScript 支持
- 🔄 自动生成类型安全的客户端代码
- 🛡️ 使用 Zod 进行运行时类型验证
- 🔁 内置智能重试机制
- 🎯 支持幂等操作
- 🌟 优雅的装饰器 API
- 🚦 优雅停机支持
- 📡 生成基于 fetch 的客户端代码,可以在 Deno 、Node.js、Bun 以及浏览器中使用
- 🌟 服务间调用可以利用 Deno 的分布式模块引入快速集成生成的客户端
TODOs
- 示例项目
- 微服务高级功能,熔断器、负载均衡等
- 服务指标统计
安装
import { Action, Microservice, Module } from "jsr:@imean/microservice";
快速开始
1. 定义数据模型
使用 Zod 定义你的数据模型:
import { z } from "zod"; const UserSchema = z.object({ id: z.string(), name: z.string(), age: z.number().min(0).max(150), }); type User = z.infer<typeof UserSchema>;
2. 创建服务模块
使用装饰器定义你的服务模块和方法:
@Module("users", { description: "用户服务模块", version: "1.0.0", }) class UserService { private users = new Map<string, User>(); @Action({ description: "获取用户信息", params: [z.string()], returns: UserSchema, }) async getUser(id: string): Promise<User> { const user = this.users.get(id); if (!user) { throw new Error("用户不存在"); } return user; } @Action({ description: "创建新用户", params: [z.string(), z.number()], returns: UserSchema, }) async createUser(name: string, age: number): Promise<User> { const id = crypto.randomUUID(); const user = { id, name, age }; this.users.set(id, user); return user; } @Action({ description: "更新用户信息", params: [z.string(), z.string(), z.number()], returns: UserSchema, // 标记为幂等操作,支持自动重试 idempotence: true, }) async updateUser(id: string, name: string, age: number): Promise<User> { const user = this.users.get(id); if (!user) { throw new Error("用户不存在"); } const updatedUser = { ...user, name, age }; this.users.set(id, updatedUser); return updatedUser; } }
3. 启动服务
const service = new Microservice({ modules: [UserService], prefix: "/api", }); await service.init(); // 启动在 3000 端口 service.start(3000);
4. 使用生成的客户端
访问服务根路径(如 http://localhost:3000/client.ts)会自动下载生成的
TypeScript 客户端代码。
使用生成的客户端:
const client = new MicroserviceClient({ baseUrl: "http://localhost:3000/client.ts?cache=v1", }); // 创建用户 const user = await client.users.createUser("张三", 25); // 更新用户(支持自动重试) const updated = await client.users.updateUser(user.id, "张三丰", 30); // 获取用户 const found = await client.users.getUser(user.id);
高级特性
幂等性和重试机制
框架提供了智能的重试机制,但仅对标记为幂等的操作生效:
重试策略:
- 仅对标记为
idempotence: true的方法进行重试 - 重试间隔:500ms、1000ms、3000ms、5000ms
- 最多重试 4 次
优雅停机
在需要停止服务时,可以等待所有重试请求完成:
API 参考
装饰器
@Module(name: string, options: ModuleOptions)
定义一个服务模块。
interface ModuleOptions { description?: string; version?: string; }
@Action(options: ActionOptions)
定义一个模块方法。
interface ActionOptions { description?: string; params: z.ZodType<any>[]; // 参数类型定义 returns: z.ZodType<any>; // 返回值类型定义 idempotence?: boolean; // 是否是幂等操作 }
Microservice
constructor(options: MicroserviceOptions)
创建微服务实例。
interface MicroserviceOptions { modules: (new () => any)[]; // 模块类数组 prefix?: string; // API 前缀,默认为 "/api" }
start(port?: number): void
启动服务器,默认端口为 3000。
clientCode(): Promise
获取生成的客户端代码。
MicroserviceClient
constructor(options: ClientOptions)
创建客户端实例。
interface ClientOptions { baseUrl: string; // 服务器地址 prefix?: string; // API 前缀,默认为 "/api" headers?: Record<string, string>; // 自定义请求头 }
错误处理
服务端抛出的错误会被自动转换为标准的错误响应:
interface ServiceResponse<T> { success: boolean; data?: T; error?: string; }
类型安全
框架使用 Zod 进行运行时类型验证,确保:
- 请求参数类型正确
- 返回值类型符合预期
- 自动生成的客户端代码类型完整
最佳实践
服务启动前检查
框架提供了 startCheck 方法用于在服务正式启动前进行必要的检查和初始化。这对于确保依赖服务(如数据库)可用非常有用。
// main.ts import { startCheck } from "jsr:@imean/microservice"; // 数据库连接检查 async function checkDatabase() { try { const db = await connectDB({ host: "localhost", port: 5432, // ...其他配置 }); await db.ping(); console.log("✅ 数据库连接成功"); } catch (error) { throw new Error(`数据库连接失败: ${error.message}`); } } // Redis 连接检查 async function checkRedis() { try { const redis = await connectRedis(); await redis.ping(); console.log("✅ Redis 连接成功"); } catch (error) { throw new Error(`Redis 连接失败: ${error.message}`); } } // 启动检查 startCheck( // 前置检查项 [checkDatabase, checkRedis], // 服务启动回调 async () => { // 使用动态导入载入服务模块 const { UserService } = await import("./services/user.ts"); const { OrderService } = await import("./services/order.ts"); const service = new Microservice({ modules: [UserService, OrderService], prefix: "/api", }); service.start(3000); } );
这种方式的优点:
-
依赖检查
- 确保所有必要的外部服务都可用
- 避免服务启动后才发现依赖问题
- 提供清晰的错误信息
-
按需加载
- 使用动态导入延迟加载服务模块
- 避免在检查失败时不必要的资源初始化
- 提高启动性能
-
优雅失败
- 如果检查失败,服务不会启动
- 适合在容器环境中使用
- 便于问题诊断
目录结构建议
your-service/ ├── main.ts # 入口文件,包含启动检查 ├── config/ │ └── index.ts # 配置文件 ├── services/ │ ├── user.ts # 用户服务模块 │ └── order.ts # 订单服务模块 ├── models/ │ ├── user.ts # 用户数据模型 │ └── order.ts # 订单数据模型 ├── utils/ │ └── db.ts # 数据库连接工具 └── tests/ └── services/ ├── user.test.ts └── order.test.ts
配置管理
建议将配置和服务逻辑分离:
// config/index.ts export const config = { database: { host: Deno.env.get("DB_HOST") || "localhost", port: parseInt(Deno.env.get("DB_PORT") || "5432"), // ... }, redis: { url: Deno.env.get("REDIS_URL") || "redis://localhost:6379", // ... }, service: { port: parseInt(Deno.env.get("PORT") || "3000"), prefix: Deno.env.get("API_PREFIX") || "/api", }, }; // main.ts import { config } from "./config/index.ts"; startCheck( [ /* ... */ ], async () => { const service = new Microservice({ modules: [ /* ... */ ], prefix: config.service.prefix, }); service.start(config.service.port); } );
文件上传/二进制数据
框架传输采用 ejson 进行序列化,支持二进制数据传输。只需要在模型中接受 Uint8Array 类型即可,并且 Zod 类型需要设置为 z.instanceof(Uint8Array)。
import * as z from "zod"; @Module("files") export class FileService { @Action({ params: [z.instanceof(Uint8Array)], returns: z.instanceof(Uint8Array), }) reverseBinary(data: Uint8Array): Uint8Array { return data.reverse(); } }
定时任务
框架提供了 @Schedule 装饰器用于定义定时任务。在分布式环境中,同一个定时任务只会在一个服务实例上执行。
基本用法
@Module("tasks") class TaskService { @Schedule({ interval: 5000, // 执行间隔(毫秒) mode: ScheduleMode.FIXED_RATE, // 执行模式 }) async cleanupTask() { // 定时执行的任务代码 } }
执行模式
框架支持两种执行模式:
-
FIXED_RATE: 固定频率执行,不考虑任务执行时间@Schedule({ interval: 5000, mode: ScheduleMode.FIXED_RATE, }) async quickTask() { // 每 5 秒执行一次 } -
FIXED_DELAY: 固定延迟执行,等待任务完成后再计时@Schedule({ interval: 5000, mode: ScheduleMode.FIXED_DELAY, }) async longRunningTask() { // 任务完成后等待 5 秒再执行下一次 }
分布式调度
定时任务基于 etcd 实现分布式调度:
- 自动选主:多个服务实例中只有一个会执行定时任务
- 故障转移:当执行任务的实例故障时,其他实例会自动接管
- 服务发现:新加入的实例会自动参与选主
const service = new Microservice({ name: "user-service", // 服务名称 modules: [TaskService], etcd: { hosts: ["localhost:2379"], // etcd 服务地址 auth: { // 可选的认证信息 username: "root", password: "password", }, ttl: 10, // 租约 TTL(秒) namespace: "services", // 可选的命名空间 }, });
选举 Key
每个定时任务都有唯一的选举 key,格式为:
{service-name}/{module-name}/schedules/{method-name}
优雅停机
服务停止时会自动清理定时任务和选举信息:
// 在 k8s 停机信号处理中 await service.stop();
注意事项
- 使用定时任务需要配置 etcd
- 建议使用
FIXED_DELAY模式执行耗时任务 - 任务执行时间不应超过执行间隔
- 确保不同任务的选举 key 不重复
License
MIT
Add Package
deno add jsr:@imean/microservice
Import symbol
import * as microservice from "@imean/microservice";
Import directly with a jsr specifier
import * as microservice from "jsr:@imean/microservice";
Add Package
pnpm i jsr:@imean/microservice
pnpm dlx jsr add @imean/microservice
Import symbol
import * as microservice from "@imean/microservice";
Add Package
yarn add jsr:@imean/microservice
yarn dlx jsr add @imean/microservice
Import symbol
import * as microservice from "@imean/microservice";
Add Package
vlt install jsr:@imean/microservice
Import symbol
import * as microservice from "@imean/microservice";
Add Package
npx jsr add @imean/microservice
Import symbol
import * as microservice from "@imean/microservice";
Add Package
bunx jsr add @imean/microservice
Import symbol
import * as microservice from "@imean/microservice";