SuperAPI AI 工具集

typed-service-contracts

编程与开发

查看项目

更新于 10-15 12:28

技能介绍

用途

Typed Service Contracts(Spec 与 Handler 模式)是一套架构标准,用于构建健壮、类型安全的 TypeScript 服务。它定义了一种由契约式设计(Design by Contract,DbC)原则支撑的垂直切片架构,把应用逻辑视为严格定义的工作单元:输入被解析(parse)而非仅仅校验(validate),错误被当作值(Result 模式)处理,而不是抛出异常。

适用场景

  • 构建 CLI 或库:需要在用户输入与系统逻辑之间建立严格边界时。
  • 复杂校验:当输入在被使用前需要转换(解析)时,例如确保某个字符串是有效的文件路径。
  • 高可靠性要求:当无法承受未处理的运行时异常、需要穷尽的错误处理时。
  • 测试聚焦:当希望将数据校验测试与业务逻辑测试分离时。

架构组件

1. Spec(spec.ts)

即“契约”或“端口”,定义“做什么”。它必须包含:

  • 输入 Schema:一个 Zod schema,将原始输入解析为有效的 DTO。
  • 输出 Schema:一个 Zod schema,定义成功时的数据结构。
  • 错误 Schema:具体失败模式的可辨识联合(discriminated union),而非泛化错误。
  • Result 类型:Success | Failure 的可辨识联合。
  • 接口:能力定义,例如 interface ConfigureSpec。

2. Handler(handler.ts)

即“实现”或“适配器”,定义“怎么做”。它必须:

  • 实现 Spec 中定义的接口。
  • 是一个处理副作用(文件系统、API 调用)的“非纯”类。
  • 绝不抛出异常。它必须捕获内部错误并将其映射为 Result 类型。

使用方式

第 1 步:定义契约(spec.ts)

按照以下模板定义边界。

import { z } from 'zod';

// 1. VALIDATION HELPERS (Reusable Refinements)
export const SafePathSchema = z.string()
  .min(1)
  .refine(p => !p.includes('..'), "No traversal allowed");

// 2. INPUT (The Command) - "Parse, don't validate"
export const MyTaskInputSchema = z.object({
  path: SafePathSchema,
  force: z.boolean().default(false),
});
export type MyTaskInput = z.infer<typeof MyTaskInputSchema>;

// 3. ERROR CODES (Exhaustive)
export const MyTaskErrorCode = z.enum([
  'FILE_NOT_FOUND',
  'PERMISSION_DENIED', 
  'UNKNOWN_ERROR'
]);

// 4. RESULT (The Monad)
export const MyTaskSuccess = z.object({
  success: z.literal(true),
  data: z.string(), // The output payload
});

export const MyTaskFailure = z.object({
  success: z.literal(false),
  error: z.object({
    code: MyTaskErrorCode,
    message: z.string(),
    suggestion: z.string().optional(),
    recoverable: z.boolean(),
  })
});

export type MyTaskResult = 
  | z.infer<typeof MyTaskSuccess> 
  | z.infer<typeof MyTaskFailure>;

// 5. INTERFACE (The Capability)
export interface MyTaskSpec {
  execute(input: MyTaskInput): Promise<MyTaskResult>;
}

第 2 步:实现 Handler(handler.ts)

按照以下模板实现逻辑。

import { MyTaskSpec, MyTaskInput, MyTaskResult } from './spec.js';
import * as fs from 'fs';

export class MyTaskHandler implements MyTaskSpec {
  async execute(input: MyTaskInput): Promise<MyTaskResult> {
    try {
      // 1. Business Logic
      if (!fs.existsSync(input.path)) {
        // 2. Explicit Error Return (No Throwing)
        return {
          success: false,
          error: {
            code: 'FILE_NOT_FOUND',
            message: `Path does not exist: ${input.path}`,
            recoverable: true
          }
        };
      }

      // 3. Success Return
      return {
        success: true,
        data: 'Operation complete'
      };

    } catch (error) {
      // 4. Safety Net: Catch unknown runtime errors
      return {
        success: false,
        error: {
          code: 'UNKNOWN_ERROR',
          message: error instanceof Error ? error.message : String(error),
          recoverable: false
        }
      };
    }
  }
}

第 3 步:测试策略

不要编写单体测试,而应将其拆分为契约测试与逻辑测试。

A. 契约测试(Schema)

测试“门卫”,确保无效数据在到达 handler 之前被拒绝。

  • 聚焦:边界情况、校验规则、Zod refinements。
  • 风格:数据驱动(表驱动测试)。
// spec.test.ts
import { MyTaskInputSchema } from './spec';

const invalidCases = [
  { val: '../etc/passwd', err: 'No traversal allowed' },
  { val: '', err: 'min(1)' },
];

test.each(invalidCases)('validates paths', ({ val, err }) => {
  const result = MyTaskInputSchema.safeParse({ path: val });
  expect(result.success).toBe(false);
});

B. 逻辑测试(Handler)

测试“厨师”,模拟外部依赖(fs、网络)并断言 Result 对象。

  • 聚焦:业务逻辑流程、错误映射、成功状态。
  • 风格:模拟单元测试或场景运行器。
// handler.test.ts
import { MyTaskHandler } from './handler';
import { vi } from 'vitest'; // or jest

test('returns FILE_NOT_FOUND if path missing', async () => {
  // MOCK
  vi.mocked(fs.existsSync).mockReturnValue(false);
  
  // EXECUTE
  const handler = new MyTaskHandler();
  const result = await handler.execute({ path: '/fake' });

  // ASSERT (Check the Result Object)
  expect(result.success).toBe(false);
  if (!result.success) {
    expect(result.error.code).toBe('FILE_NOT_FOUND');
  }
});

限制

资料未提供关于该技能的具体安装命令、依赖版本要求、平台限制或收费信息,暂未提供。