clean-architecture-ts

📁 toilahuongg/google-antigravity-kit 📅 Jan 25, 2026
4
总安装量
2
周安装量
#50105
全站排名
安装命令
npx skills add https://github.com/toilahuongg/google-antigravity-kit --skill clean-architecture-ts

Agent 安装分布

windsurf 1
opencode 1
weavefox 1
claude-code 1
antigravity 1

Skill 文档

Clean Architecture for Remix/TypeScript Apps

As Remix apps grow, loader and action functions can become bloated “God Functions”. This skill emphasizes separation of concerns.

1. The Layers

A. The Web Layer (Loaders/Actions)

Responsibility: Parsing requests, input validation (Zod), and returning Responses (JSON/Redirect). Rule: NO business logic here. Only orchestration.

// app/routes/app.products.update.ts
export const action = async ({ request }: ActionFunctionArgs) => {
  const { shop } = await authenticate.admin(request);
  const formData = await request.formData();
  
  // 1. Validate Input
  const input = validateProductUpdate(formData);

  // 2. Call Service
  const updatedProduct = await ProductService.updateProduct(shop, input);

  // 3. Return Response
  return json({ product: updatedProduct });
};

B. The Service Layer (Business Logic)

Responsibility: The “What”. Rules, calculations, error handling, complex flows. Rule: Framework agnostic. Should not know about “Request” or “Response” objects.

// app/services/product.service.ts
export class ProductService {
  static async updateProduct(shop: string, input: ProductUpdateInput) {
    // Business Rule: Can't update archived products
    const existing = await ProductRepository.findByShopAndId(shop, input.id);
    if (existing.status === 'ARCHIVED') {
      throw new BusinessError("Cannot update archived product");
    }

    // Business Logic
    const result = await ProductRepository.save({
      ...existing,
      ...input,
      updatedAt: new Date()
    });

    return result;
  }
}

C. The Repository Layer (Data Access)

Responsibility: The “How”. interaction with Database (Prisma), APIs (Shopify Admin), or File System. Rule: Only this layer touches the DB/API.

// app/repositories/product.repository.ts
export class ProductRepository {
  static async findByShopAndId(shop: string, id: string) {
    return prisma.product.findFirstOrThrow({
      where: { shop, id: BigInt(id) }
    });
  }
}

2. Directory Structure

app/
  routes/         # Web Layer
  services/       # Business Logic
  repositories/   # Data Access (DB/API)
  models/         # Domain Types / Interfaces
  utils/          # Pure functions (math, string manipulation)

3. Dependency Injection (Optional but Recommended)

For complex apps, use a container like tsyringe to manage dependencies, especially for testing (mocking Repositories).

// app/services/order.service.ts
@injectable()
export class OrderService {
  constructor(
    @inject(OrderRepository) private orderRepo: OrderRepository,
    @inject(ShopifyClient) private shopify: ShopifyClient
  ) {}
}

4. Error Handling

Create custom Error classes to differentiate between “Bad Request” (User error) and “Server Error” (System error).

// app/errors/index.ts
export class BusinessError extends Error {
  public code = 422;
}

export class NotFoundError extends Error {
  public code = 404;
}

Refactor your loader/action to catch these errors and return appropriate HTTP status codes.