Чтобы добиться независимости бизнес-логики от фреймворка и протокола, нужно следовать принципам чистой архитектуры и разделения ответственностей. Вот ключевые подходы:
Применяйте паттерн "Многослойная архитектура" (Layered Architecture), где бизнес-логика находится в ядре, а фреймворки и протоколы — во внешних слоях:
+-------------------+
| Presentation | <-- Express, Fastify, gRPC, GraphQL
+-------------------+
| Application | <-- Маршрутизация, валидация
+-------------------+
| Domain | <-- Чистая бизнес-логика
+-------------------+
| Infrastructure | <-- БД, кэш, внешние сервисы
+-------------------+
Определите интерфейсы (абстрактные классы или TypeScript interfaces) для всех внешних взаимодействий:
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
interface PaymentService {
processPayment(amount: number): Promise<PaymentResult>;
}
Преобразуйте входящие/исходящие данные в нейтральные структуры:
class CreateOrderDto {
constructor(
public readonly productId: string,
public readonly quantity: number,
public readonly userId: string
) {}
}
Внедряйте зависимости через конструкторы, а не импортируйте фреймворки напрямую:
class OrderService {
constructor(
private orderRepository: OrderRepository,
private paymentService: PaymentService
) {}
async createOrder(dto: CreateOrderDto) {
// Чистая бизнес-логика
}
}
Создавайте адаптеры, которые преобразуют специфичные запросы в нейтральные DTO:
// Express адаптер
app.post('/orders', async (req, res) => {
const dto = new CreateOrderDto(req.body.productId, req.body.quantity, req.user.id);
const result = await orderService.createOrder(dto);
res.json(result);
});
// gRPC адаптер
service OrderService {
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse) {
const dto = new CreateOrderDto(request.getProductId(), request.getQuantity(), request.getUserId());
const result = await orderService.createOrder(dto);
return new CreateOrderResponse(result);
}
}
Реализуйте архитектуру, где:
Не используйте в бизнес-логике:
Бизнес-логика должна тестироваться без моков фреймворков:
test('Order creation', async () => {
const mockRepo = { save: jest.fn() };
const service = new OrderService(mockRepo, mockPayment);
await service.createOrder(new CreateOrderDto('prod1', 2, 'user1'));
expect(mockRepo.save).toHaveBeenCalled();
});
Правильно организованная архитектура позволяет: