mcpField
T
typed-service-contracts

typed-service-contracts

github.com/google-labs-code开发28.6k707 次安装2 周前更新
代码仓库

关于 typed-service-contracts

此 TypeScript 架构技能以契约和处理器定义服务,并采用解析后的输入及 Result 错误处理模式。适用于重视边界清晰、错误穷尽处理以及验证与业务逻辑分开测试的 CLI 和库开发。

主要功能

  1. 01Zod 输入与输出模式
  2. 02区分成功与失败的 Result
  3. 03处理器映射错误而不抛出异常
  4. 04契约测试与逻辑测试分离

适用场景

  1. 01为 CLI 定义严格的输入边界
  2. 02解析并转换复杂输入
  3. 03将模式测试与业务逻辑测试分开

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/google-labs-code/design.md --skill typed-service-contracts

安装后在 Claude Code 中输入/typed-service-contracts确认已加载。

详细介绍

类型化服务契约(Spec 与 Handler 模式)

本 Skill 定义了一种以契约式设计(Design by Contract,DbC)原则为基础的垂直切片架构(Vertical Slice Architecture)。它将应用逻辑视为严格定义的工作单元(Units of Work):输入需要被解析(而不仅仅是校验),错误则作为值处理(Result 模式),而不是以异常的形式抛出。

何时使用本 Skill

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

架构组件

1. Spec(spec.ts)

"契约"或"端口"。它定义了做什么(What)。它必须包含:

  • 输入 Schema: 一个 Zod Schema,用于将原始输入解析为有效的 DTO。
  • 输出 Schema: 一个 Zod Schema,定义成功时的数据结构。
  • 错误 Schema: 具体失败模式的判别联合(discriminated union),而非通用错误。
  • Result 类型: Success | Failure 的 DiscriminatedUnion。
  • 接口: 能力定义(例如 interface ConfigureSpec)。
2. Handler(handler.ts)

"实现"或"适配器"。它定义了如何做(How)。它必须:

  • 实现 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 步:测试策略

不要编写单体式测试。将其拆分为契约测试(Contract Tests)与逻辑测试(Logic Tests)。

A. 契约测试(Schema)

测试守门人(Bouncer)。确保无效数据在到达 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)

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

  • 关注点: 业务逻辑流程、错误映射、成功状态。
  • 风格: 模拟单元测试或场景运行器(Scenario Runners)。
// 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');
  }
});

评论

支持 Markdown