Межсервисные транзакции Mikro-orm в NestJS
Я оцениваю Mikro-Orm для будущего проекта. Есть несколько вопросов, на которые я либо не нашел ответа в документации, либо не до конца их понял.
Позвольте мне описать минимально сложный пример (NestJS): у меня есть система обработки заказов с двумя объектами: Orders
а также Invoices
а также таблица счетчиков для последовательных номеров счетов (юридическое требование). Важно отметить, что метод create OrderService не всегда вызывается контроллером, но также и через систему crobjob/queue. Мои вопросы касаются варианта использования создания нового заказа:
class OrderService {
async createNewOrder(orderDto) {
const order = new Order();
order.customer = orderDto.customer;
order.items = orderDto.items;
const invoice = await this.InvoiceService.createInvoice(orderDto.items);
order.invoice = invoice;
await order.persistAndFlush();
return order
}
}
class InvoiceService {
async create(items): Invoice {
const invoice = new Invoice();
invoice.number = await this.InvoiceNumberService.getNextInSequence();
// the next two lines are external apis, if they throw, the whole transaction should roll back
const pdf = await this.PdfCreator.createPdf(invoice);
const upload = await s3Api.uplpad(pdf);
return invoice;
}
}
class InvoiceNumberService {
async getNextInSequence(): number {
return await db.collection("counter").findOneAndUpdate({ type: "INVOICE" }, { $inc: { value: 1 } });
}
}
Весь вариант использования создания нового заказа со всеми последующими вызовами службы должен происходить в одной транзакции Mikro-Orm. Поэтому, если что-то вызывает OrderService.createNewOrder() или один из вызываемых впоследствии методов, необходимо откат всей транзакции.
Mikro-Orm не допускает атомарного приращения обновления, показанного в InvoiceNumberService. Могу вернуться к родному драйверу mongo. Но как мне убедиться, что вызов collection.findOneAndUpdate() использует ту же транзакцию, что и объекты, управляемые Mikro-Orm?
Mikro-Orm нужен уникальный контекст запроса. В примерах для NestJS этот уникальный контекст создается на уровне контроллера. В приведенном выше примере методы обслуживания не обязательно вызываются контроллером. Поэтому мне понадобится новый контекст для каждого вызова OrderService.createNewOrder(), срок действия которого привязан к вызову функции, правильно? Как я могу этого добиться?
Как я могу использовать один и тот же контекст запроса между службами? В приведенном выше примере InvoiceService и InvoiceNumberService потребуется тот же контекст, что и OrderService, для правильной работы Mikro-Orm.
1 ответ
Я начну с плохих новостей, транзакции mongodb еще не поддерживаются в MikroORM (хотя они появятся в течение нескольких недель, вероятно, уже реализовали PoC). Вы можете подписаться здесь на обновления: https://github.com/mikro-orm/mikro-orm/issues/34
Но позвольте мне ответить на остальное, поскольку оно будет применяться:
Ты можешь использовать const collection = (em as EntityManager<MongoDriver>).getConnection().getCollection('counter');
чтобы получить коллекцию из экземпляра внутреннего соединения mongo. Вы также можете использоватьorm.em.getTransactionContext()
чтобы получить текущий контекст транзакции (в настоящее время реализован только в драйверах sql, но в будущем это, вероятно, вернет session
объект в монго).
Также обратите внимание, что в драйвере mongo неявные транзакции не будут включены по умолчанию (хотя его можно будет настроить), поэтому вам нужно будет использовать явное разграничение транзакций через em.transactional(...)
.
В RequestContext
помощник работает автоматически. Вы просто регистрируете его как промежуточное ПО (выполняется автоматически в адаптере nestjs orm), а затем ваш обработчик запросов (метод маршрута / конечной точки / контроллера) запускается внутри домена, который разделяет контекст. Благодаря этому все службы в DI могут совместно использовать одноэлементные экземпляры репозиториев, но они автоматически выбирают правильный контекст из домена.
В основном у вас есть этот автоматический контекст запроса, а затем вы можете создавать новые (вложенные) контексты вручную с помощью em.transactional(...)
.