Loading...
Loading...
Best practices for implementing Clean Architecture in Remix/TypeScript apps. Covers Service Layer, Repository Pattern, and Dependency Injection.
npx skill4agent add toilahuongg/shopify-agents-kit clean-architecture-tsloaderaction// 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 });
};// 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;
}
}// app/repositories/product.repository.ts
export class ProductRepository {
static async findByShopAndId(shop: string, id: string) {
return prisma.product.findFirstOrThrow({
where: { shop, id: BigInt(id) }
});
}
}app/
routes/ # Web Layer
services/ # Business Logic
repositories/ # Data Access (DB/API)
models/ # Domain Types / Interfaces
utils/ # Pure functions (math, string manipulation)tsyringe// app/services/order.service.ts
@injectable()
export class OrderService {
constructor(
@inject(OrderRepository) private orderRepo: OrderRepository,
@inject(ShopifyClient) private shopify: ShopifyClient
) {}
}// app/errors/index.ts
export class BusinessError extends Error {
public code = 422;
}
export class NotFoundError extends Error {
public code = 404;
}loaderaction