{"id":316478,"date":"2021-01-17T15:00:17","date_gmt":"2021-01-17T15:00:17","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=316478"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=316478","title":{"rendered":"\u041b\u0435\u0447\u0438\u043c Java Reactor \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 Kotlin Coroutines"},"content":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<p>\u041d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0430\u0431\u043e\u0442\u0435 \u043f\u0438\u0448\u0435\u043c \u043d\u0430 Reactor. \u0422\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u043a\u043b\u0430\u0441\u0441\u043d\u0430\u044f, \u043d\u043e \u043a\u0430\u043a \u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u041d\u041e. \u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0435\u0449\u0438 \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0430\u044e\u0442, \u043a\u043e\u0434 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0438 \u0447\u0438\u0442\u0430\u0442\u044c, \u0441 ThreadLocal \u0441\u043e\u0432\u0441\u0435\u043c \u0431\u0435\u0434\u0430. \u0420\u0435\u0448\u0438\u043b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0443\u0439\u0434\u0443\u0442, \u0435\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 Kotlin Coroutines, \u0430 \u043a\u0430\u043a\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u0434\u043e\u0431\u0430\u0432\u044f\u0442\u0441\u044f.<\/p>\n<h3>\u041a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430<\/h3>\n<p>\u0414\u043b\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b <a href=\"https:\/\/github.com\/gnefedev\/coroutines_vs_reactor\" rel=\"noopener noreferrer nofollow\"><u>\u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442<\/u><\/a>, \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0435. \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u0434 <a href=\"https:\/\/github.com\/gnefedev\/coroutines_vs_reactor\/blob\/master\/src\/main\/java\/com\/gnefedev\/coroutines\/vs\/reactor\/services\/Ledger.java#L31\" rel=\"noopener noreferrer nofollow\"><u>\u0437\u0434\u0435\u0441\u044c<\/u><\/a>. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u0441\u0442\u0430\u043b \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0442\u044c \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b, \u0442\u0430\u043a \u043b\u0443\u0447\u0448\u0435 \u0432\u0438\u0434\u043d\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b.<\/p>\n<p>\u0412 \u0434\u0432\u0443\u0445 \u0441\u043b\u043e\u0432\u0430\u0445 \u043e\u0431 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0435:&nbsp;<\/p>\n<p>\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u0434\u0435\u043d\u044c\u0433\u0438 \u0441 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0447\u0451\u0442\u0430 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439, \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043e \u0444\u0430\u043a\u0442\u0435 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430.&nbsp;<\/p>\n<p>\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u0435\u043d, \u0442\u0430\u043a \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 \u0411\u0414, \u0442\u043e \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443, \u0447\u0442\u043e \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e. \u041f\u0440\u0438 \u0432\u0441\u0442\u0430\u0432\u043a\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u043b\u0435\u0442\u0435\u0442\u044c DataIntegrityViolationException, \u044d\u0442\u043e \u0442\u043e\u0436\u0435 \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u0443\u0436\u0435 \u0435\u0441\u0442\u044c.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0443\u0439\u0442\u0438 \u0432 \u043c\u0438\u043d\u0443\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432 \u043a\u043e\u0434\u0435 + Optimistic lock, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0441\u0447\u0435\u0442\u0430. \u0427\u0442\u043e\u0431\u044b \u043e\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u043d\u0443\u0436\u0435\u043d retry \u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<details class=\"spoiler\">\n<summary>\u0414\u043b\u044f \u0442\u0435\u0445 \u043a\u043e\u043c\u0443 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0441\u0430\u043c \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u044b\u0431\u0438\u0440\u0430\u043b \u0442\u0430\u043a\u043e\u0439, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0430 \u043d\u0435 \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0431\u044b\u043b \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c. \u0412\u043c\u0435\u0441\u0442\u043e \u043e\u0434\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043d\u0430\u0434\u043e \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u0443\u043f\u0440\u043e\u0432\u043e\u0434\u043a\u0438, optimistic lock \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d (\u0432\u043c\u0435\u0441\u0442\u043e \u043d\u0435\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0441\u0447\u0435\u0442\u0430 \u0432 sql), select + insert \u043d\u0430\u0434\u043e \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 upsert.<\/p>\n<\/div>\n<\/details>\n<h3>\u0416\u0430\u043b\u043e\u0431\u044b \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430<\/h3>\n<ol>\n<li>\n<p>Stacktrace \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043f\u043e\u043f\u0430\u043b\u0438 \u0432 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043d\u043e\u0435 \u043c\u0435\u0441\u0442\u043e.<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u0434 \u044f\u0432\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0447\u0435\u043c \u0431\u044b\u043b \u0431\u044b \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f\u0445.&nbsp;<\/p>\n<\/li>\n<li>\n<p>\u041c\u043d\u043e\u0433\u043e\u0441\u0442\u0443\u043f\u0435\u043d\u0447\u0430\u0442\u0430\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u0438\u0437-\u0437\u0430 flatMap.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0443\u0434\u043e\u0431\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0438 \u0438\u0445 \u0432\u044b\u0431\u0440\u043e\u0441.<\/p>\n<\/li>\n<li>\n<p>\u0421\u043b\u043e\u0436\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0434\u043b\u044f Mono.empty().<\/p>\n<\/li>\n<li>\n<p>\u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0441 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c, \u0435\u0441\u043b\u0438 \u043d\u0430\u0434\u043e \u0432 \u043b\u043e\u0433 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 traceId. (\u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e, \u043d\u043e \u0442\u0435 \u0436\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 ThreadLocal \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 SpringSecurity)<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0443\u0434\u043e\u0431\u043d\u043e \u0434\u0435\u0431\u0430\u0436\u0438\u0442\u044c.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u044f\u0432\u043d\u043e\u0435 api \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<\/ol>\n<h3>\u0425\u043e\u0434 \u043b\u0435\u0447\u0435\u043d\u0438\u044f<\/h3>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u043b \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 <a href=\"https:\/\/github.com\/gnefedev\/coroutines_vs_reactor\/pull\/4\" rel=\"noopener noreferrer nofollow\"><u>PR<\/u><\/a> \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0441 Java \u043d\u0430 Kotlin.&nbsp;<\/p>\n<p>\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0447\u0442\u0438 \u0432\u0435\u0437\u0434\u0435 \u0433\u043b\u0430\u0434\u043a\u0430\u044f.&nbsp;<\/p>\n<p>\u041f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u043e\u0441\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c com.fasterxml.jackson.module:jackson-module-kotlin \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f data \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0438 org.jetbrains.kotlin.plugin.spring \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0432\u0435\u0437\u0434\u0435 open \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b.<\/p>\n<p>\u0412 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u044b\u043b\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c <code>suspend fun transfer(@RequestBody request: TransferRequest)<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>public Mono&lt;Void&gt; transfer(@RequestBody TransferRequest request)<\/code><\/p>\n<p>\u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b <code>suspend fun save(account: Account): Account<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>Mono&lt;Account&gt; save(Account account);<\/code> \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435, \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0432 \u043d\u0438\u0445 \u0442\u043e\u043b\u044c\u043a\u043e suspend \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043d\u0430\u0434\u043e, \u0447\u0442\u043e\u0431\u044b \u0445\u043e\u0442\u044c \u043e\u0434\u0438\u043d \u043c\u0435\u0442\u043e\u0434 \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0441 Reactor \u0442\u0438\u043f\u0430\u043c\u0438.<\/p>\n<p>\u0422\u0435\u0441\u0442\u044b \u043e\u0431\u0435\u0440\u043d\u0443\u043b \u0432 <code>runBlocking { \u2026 }<\/code>, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c suspend \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<p>\u0414\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 Retry \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/michaelbull\/kotlin-retry\" rel=\"noopener noreferrer nofollow\"><u>kotlin-retry<\/u><\/a>. \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435, \u0432 \u043d\u0435\u0439 \u043d\u0435 \u0431\u044b\u043b\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e \u043a\u043b\u0430\u0441\u0441\u0443 \u043e\u0448\u0438\u0431\u043a\u0438, \u043d\u043e \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u043b\u0435\u0433\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c (\u0437\u0430\u0432\u0451\u043b <a href=\"https:\/\/github.com\/michaelbull\/kotlin-retry\/pull\/14\" rel=\"noopener noreferrer nofollow\"><u>PR<\/u><\/a>).<\/p>\n<p>\u041d\u0443 \u0438, \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u043b \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c. \u0412\u0441\u0435 \u0434\u0435\u0442\u0430\u043b\u0438 \u043e\u043f\u0438\u0448\u0443 \u043d\u0438\u0436\u0435 \u043f\u043e-\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code class=\"java\">public Mono&lt;Void&gt; transfer(String transactionKey, long fromAccountId,                            long toAccountId, BigDecimal amount) {   return transactionRepository.findByUniqueKey(transactionKey)     .map(Optional::of)     .defaultIfEmpty(Optional.empty())     .flatMap(withMDC(foundTransaction -&gt; {       if (foundTransaction.isPresent()) {         log.warn(\"retry of transaction \" + transactionKey);         return Mono.empty();       }       return accountRepository.findById(fromAccountId)         .switchIfEmpty(Mono.error(new AccountNotFound()))         .flatMap(fromAccount -&gt; accountRepository.findById(toAccountId)           .switchIfEmpty(Mono.error(new AccountNotFound()))           .flatMap(toAccount -&gt; {             var transactionToInsert = Transaction.builder()               .amount(amount)               .fromAccountId(fromAccountId)               .toAccountId(toAccountId)               .uniqueKey(transactionKey)               .build();             var amountAfter = fromAccount.getAmount().subtract(amount);             if (amountAfter.compareTo(BigDecimal.ZERO) &lt; 0) {               return Mono.error(new NotEnoghtMoney());             }             return transactionalOperator.transactional(               transactionRepository.save(transactionToInsert)                 .onErrorResume(error -&gt; {                   \/\/transaction was inserted on parallel transaction,                   \/\/we may return success response                   if (error instanceof DataIntegrityViolationException              &amp;&amp; error.getMessage().contains(\"TRANSACTION_UNIQUE_KEY\")) {                     return Mono.empty();                   } else {                     return Mono.error(error);                   }                 })                 .then(accountRepository.transferAmount(                   fromAccount.getId(), fromAccount.getVersion(),                    amount.negate()                 ))                 .then(accountRepository.transferAmount(                   toAccount.getId(), toAccount.getVersion(), amount                 ))             );           }));     }))     .retryWhen(Retry.backoff(3, Duration.ofMillis(1))       .filter(OptimisticLockException.class::isInstance)       .onRetryExhaustedThrow((__, retrySignal) -&gt; retrySignal.failure())     )     .onErrorMap(       OptimisticLockException.class,       e -&gt; new ResponseStatusException(         BANDWIDTH_LIMIT_EXCEEDED,         \"limit of OptimisticLockException exceeded\", e       )     )     .onErrorResume(withMDC(e -&gt; {       log.error(\"error on transfer\", e);       return Mono.error(e);     })); }<\/code><\/pre>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code class=\"kotlin\">suspend fun transfer(transactionKey: String, fromAccountId: Long,                      toAccountId: Long, amount: BigDecimal) {   try {     try {       retry(limitAttempts(3) + filter { it is OptimisticLockException }) {         val foundTransaction = transactionRepository           .findByUniqueKey(transactionKey)         if (foundTransaction != null) {           logger.warn(\"retry of transaction $transactionKey\")           return@retry         }          val fromAccount = accountRepository.findById(fromAccountId)           ?: throw AccountNotFound()         val toAccount = accountRepository.findById(toAccountId)           ?: throw AccountNotFound()          if (fromAccount.amount - amount &lt; BigDecimal.ZERO) {           throw NotEnoghtMoney()         }         val transactionToInsert = Transaction(           amount = amount,           fromAccountId = fromAccountId,           toAccountId = toAccountId,           uniqueKey = transactionKey         )         transactionalOperator.executeAndAwait {           try {             transactionRepository.save(transactionToInsert)           } catch (e: DataIntegrityViolationException) {             if (e.message?.contains(\"TRANSACTION_UNIQUE_KEY\") != true) {               throw e;             }           }            accountRepository.transferAmount(             fromAccount.id!!, fromAccount.version, amount.negate()           )           accountRepository.transferAmount(             toAccount.id!!, toAccount.version, amount           )         }       }     } catch (e: OptimisticLockException) {       throw ResponseStatusException(         BANDWIDTH_LIMIT_EXCEEDED,          \"limit of OptimisticLockException exceeded\", e       )     }   } catch (e: Exception) {     logger.error(e) { \"error on transfer\" }     throw e;   } }<\/code><\/pre>\n<h4>Stacktraces<\/h4>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0433\u043b\u0430\u0432\u043d\u043e\u0435.&nbsp;<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code>o.s.w.s.ResponseStatusException: 509 BANDWIDTH_LIMIT_EXCEEDED \"limit of OptimisticLockException exceeded\"; nested exception is c.g.c.v.r.OptimisticLockException \tat c.g.c.v.r.services.Ledger.lambda$transfer$5(Ledger.java:75) \t... Caused by: c.g.c.v.r.OptimisticLockException: null \tat c.g.c.v.r.repos.AccountRepositoryImpl.lambda$transferAmount$0(AccountRepositoryImpl.java:27) \tat r.c.p.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)   ...<\/code><\/pre>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code>error on transfer o.s.w.s.ResponseStatusException: 509 BANDWIDTH_LIMIT_EXCEEDED \"limit of OptimisticLockException exceeded\"; nested exception is c.g.c.v.r.OptimisticLockException \tat c.g.c.v.r.services.Ledger.transfer$suspendImpl(Ledger.kt:70) \tat c.g.c.v.r.services.Ledger$transfer$1.invokeSuspend(Ledger.kt) \t... Caused by: c.g.c.v.r.OptimisticLockException: null \tat c.g.c.v.r.repos.AccountRepositoryImpl.transferAmount(AccountRepositoryImpl.kt:24) \t... \tat c.g.c.v.r.services.Ledger$transfer$3$1.invokeSuspend(Ledger.kt:65) \tat c.g.c.v.r.services.Ledger$transfer$3$1.invoke(Ledger.kt) \tat o.s.t.r.TransactionalOperatorExtensionsKt$executeAndAwait$2$1.invokeSuspend(TransactionalOperatorExtensions.kt:30) \t(Coroutine boundary) \tat o.s.t.r.TransactionalOperatorExtensionsKt.executeAndAwait(TransactionalOperatorExtensions.kt:31) \tat c.g.c.v.r.services.Ledger$transfer$3.invokeSuspend(Ledger.kt:56) \tat com.github.michaelbull.retry.RetryKt$retry$3.invokeSuspend(Retry.kt:38) \tat c.g.c.v.r.services.Ledger.transfer$suspendImpl(Ledger.kt:35) \tat c.g.c.v.r.controllers.LedgerController$transfer$2$1.invokeSuspend(LedgerController.kt:20) \tat c.g.c.v.r.controllers.LedgerController$transfer$2.invokeSuspend(LedgerController.kt:19) \tat kotlin.reflect.full.KCallables.callSuspend(KCallables.kt:55) \tat o.s.c.CoroutinesUtils$invokeSuspendingFunction$mono$1.invokeSuspend(CoroutinesUtils.kt:64) \t(Coroutine creation stacktrace) \tat k.c.i.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:122) \tat k.c.i.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) \t... Caused by: c.g.c.v.r.OptimisticLockException: null \tat c.g.c.v.r.repos.AccountRepositoryImpl.transferAmount(AccountRepositoryImpl.kt:24) \t... \tat c.g.c.v.r.services.Ledger$transfer$3$1.invokeSuspend(Ledger.kt:65) \tat c.g.c.v.r.services.Ledger$transfer$3$1.invoke(Ledger.kt) \tat o.s.t.r.TransactionalOperatorExtensionsKt$executeAndAwait$2$1.invokeSuspend(TransactionalOperatorExtensions.kt:30) \t... <\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0421\u043a\u0443\u0447\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0435\u043a\u0442\u0440\u0435\u0439\u0441\u043e\u0432 \u044f \u0432\u044b\u0440\u0435\u0437\u0430\u043b, \u043f\u0430\u043a\u0435\u0442\u044b \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u043b (\u0437\u0430\u0431\u043e\u0447\u0443\u0441\u044c \u043e \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435, \u0438 \u0431\u0435\u0437 \u0442\u043e\u0433\u043e \u0434\u043b\u0438\u043d\u043d\u043e).<\/p>\n<p>\u0412 Java \u043e\u0447\u0435\u043d\u044c \u043a\u0443\u0446\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f. \u0414\u0430, \u043e\u0448\u0438\u0431\u043a\u0430 \u0435\u0441\u0442\u044c. \u0414\u0430\u0436\u0435 \u0432\u0438\u0434\u043d\u043e \u043d\u0430 \u043a\u0430\u043a\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0435 \u043e\u043d\u0430 \u0432\u044b\u043b\u0435\u0442\u0435\u043b\u0430. \u0422\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043f\u043e\u043d\u044f\u0442\u043d\u043e \u0430 \u043a\u0430\u043a \u043c\u044b \u0432 \u044d\u0442\u0443 \u0441\u0442\u0440\u043e\u0447\u043a\u0443 \u043a\u043e\u0434\u0430 \u043f\u043e\u043f\u0430\u043b\u0438. \u0412 Kotlin \u0432\u0435\u0440\u0441\u0438\u0438 \u0432\u0438\u0434\u0435\u043d \u0432\u0435\u0441\u044c \u0442\u0440\u0435\u0439\u0441 \u043e\u0442 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430.<\/p>\n<p>\u0412\u043e\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0441\u0435\u0431\u0435, \u0447\u0442\u043e \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435 \u043e\u0448\u0438\u0431\u043a\u0443 \u0432 \u043b\u043e\u0433\u0435 \u0433\u0434\u0435-\u0442\u043e \u043d\u0430 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438 \u0432 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0439 \u043c\u0435\u0442\u043e\u0434. \u0410 \u043a\u0442\u043e \u0435\u0433\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u043b? \u041f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u043f\u043e \u043b\u043e\u0433\u0430\u043c \u0440\u044f\u0434\u043e\u043c \u0438\u0441\u043a\u0430\u0442\u044c. \u042d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u043e, \u0435\u0441\u043b\u0438 \u043b\u043e\u0433\u0438 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u044b \u0447\u0435\u0440\u0435\u0437 \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u0432\u0440\u043e\u0434\u0435 traceId (thread name \u043d\u0430\u043c \u043d\u0435 \u043f\u043e\u043c\u043e\u0436\u0435\u0442) \u0438 \u0432\u043e\u043e\u0431\u0449\u0435 \u043b\u043e\u0433\u0438 \u0435\u0441\u0442\u044c.<\/p>\n<h4>\u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430<\/h4>\n<p>Kotlin \u0432\u0435\u0440\u0441\u0438\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u0440\u043e\u0449\u0435, \u0442\u043e\u0447\u043d\u043e \u0442\u0430\u043a \u0436\u0435, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b \u0431\u044b \u043a\u043e\u0434 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f\u0445. \u041f\u043e \u043a\u0440\u0430\u0439\u043d\u0435 \u043c\u0435\u0440\u0435 \u043f\u043e\u043a\u0430 \u043c\u044b \u043d\u0435 \u0432\u0432\u043e\u0434\u0438\u043c \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 (\u0442\u0443\u0442 \u0435\u0449\u0451 \u0432\u043e\u043f\u0440\u043e\u0441 \u043a\u0430\u043a\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u043f\u0438\u0441\u0430\u0442\u044c: \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f\u0445 \u0438\u043b\u0438 \u0441 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c\u0438).<\/p>\n<h4>\u041c\u043d\u043e\u0433\u043e\u0441\u0442\u0443\u043f\u0435\u043d\u0447\u0430\u0442\u0430\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430<\/h4>\n<p>\u041d\u0438\u043a\u0430\u043a\u0438\u0445 flatMap. \u0414\u043e\u0431\u0430\u0432\u0438\u043b\u0438\u0441\u044c \u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0437-\u0437\u0430 \u044f\u0432\u043d\u044b\u0445 try catch, \u043d\u043e \u0441\u0445\u043e\u0436\u0430\u044f \u043b\u043e\u0433\u0438\u043a\u0430 \u0432\u0441\u044f \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0430 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435.<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code class=\"java\">return accountRepository.findById(fromAccountId)   .switchIfEmpty(Mono.error(new AccountNotFound()))   .flatMap(fromAccount -&gt; accountRepository.findById(toAccountId)     .switchIfEmpty(Mono.error(new AccountNotFound()))     .flatMap(toAccount -&gt; {       ...     })<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code class=\"kotlin\">val fromAccount = accountRepository.findById(fromAccountId)   ?: throw AccountNotFound() val toAccount = accountRepository.findById(toAccountId)   ?: throw AccountNotFound() ...<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<h4>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0438 \u0438\u0445 \u0432\u044b\u0431\u0440\u043e\u0441<\/h4>\n<p>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0442\u0435\u043f\u0435\u0440\u044c \u0447\u0435\u0440\u0435\u0437 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 try catch, \u043b\u0435\u0433\u043a\u043e \u043f\u043e\u043d\u044f\u0442\u044c \u043a\u0430\u043a\u043e\u0439 \u043a\u0443\u0441\u043e\u043a \u043a\u043e\u0434\u0430 \u043c\u044b \u043e\u0431\u0435\u0440\u043d\u0443\u043b\u0438.<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code class=\"java\">return transactionRepository.findByUniqueKey(transactionKey)   ...   .onErrorMap(     OptimisticLockException.class,     e -&gt; new ResponseStatusException(       BANDWIDTH_LIMIT_EXCEEDED,        \"limit of OptimisticLockException exceeded\", e     )   )<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code class=\"kotlin\">try {   val foundTransaction = transactionRepository     .findByUniqueKey(transactionKey)   ... } catch (e: OptimisticLockException) {   throw ResponseStatusException(     BANDWIDTH_LIMIT_EXCEEDED,      \"limit of OptimisticLockException exceeded\", e   ) }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u041e\u0448\u0438\u0431\u043a\u0438 \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0447\u0435\u0440\u0435\u0437 throw, \u0430 \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044f \u043e\u0431\u044a\u0435\u043a\u0442 \u043e\u0448\u0438\u0431\u043a\u0438. \u0412 Reactor \u043c\u0435\u043d\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0430\u044e\u0442 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u0432\u0438\u0434\u0430:<\/p>\n<pre><code class=\"java\">.flatMap(foo -&gt; {   if (foo.isEmpty()) {      return Mono.error(new IllegalStateException());   } else {     return Mono.just(foo);   } })<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u041c\u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u0440\u0430\u0437\u0438\u0442\u044c, \u0447\u0442\u043e \u044d\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u0442\u0438\u043b\u044c, \u0442\u0430\u043a \u0438 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c. \u041d\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0438\u0437-\u0437\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u0442\u0438\u043b\u044f \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430.<\/p>\n<h4>Mono.empty()<\/h4>\n<p>\u042d\u0442\u043e \u0437\u0430\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u044f. \u0412 \u0440\u0435\u0430\u043a\u0442\u043e\u0440 \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c null \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043d\u0435\u043b\u044c\u0437\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u00a8C5C.&nbsp;<\/p>\n<p>Ide \u043d\u0438\u043a\u0430\u043a \u0442\u0435\u0431\u0435 \u043d\u0435 \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u043c\u043e\u0436\u0435\u0442 \u043b\u0438 \u044d\u0442\u043e\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 mono \u0431\u044b\u0442\u044c \u043f\u0443\u0441\u0442\u044b\u043c \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u043b\u0438 \u0442\u044b \u044d\u0442\u043e. \u0412 \u0440\u0430\u0431\u043e\u0447\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043b\u044f \u043c\u0435\u043d\u044f \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c \u0442\u0443\u043f\u044b\u0445 \u043e\u0448\u0438\u0431\u043e\u043a. \u0422\u043e \u0437\u0430\u0431\u0443\u0434\u0435\u0448\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c, \u0442\u043e \u043f\u043e\u0441\u043b\u0435 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u043a\u0430\u043a-\u0442\u043e \u043d\u0435 \u0442\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<p>\u0412 Kotlin \u0431\u0443\u0434\u0435\u0442 not null \u0442\u0438\u043f, \u0435\u0441\u043b\u0438 \u0442\u044b \u0442\u043e\u0447\u043d\u043e \u0437\u043d\u0430\u0435\u0448\u044c, \u0447\u0442\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0431\u0443\u0434\u0435\u0442. \u0418\u043b\u0438 \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 nullable \u0442\u0438\u043f \u0438 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 \u043e\u0431\u044f\u0436\u0435\u0442 \u0442\u0435\u0431\u044f \u0447\u0442\u043e-\u0442\u043e \u0441 \u044d\u0442\u0438\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c.<\/p>\n<p>\u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043d\u0430 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435:<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code class=\"java\">return transactionRepository.findByUniqueKey(transactionKey)   .map(Optional::of)   .defaultIfEmpty(Optional.empty())   .flatMap(foundTransaction -&gt; {     if (foundTransaction.isPresent()) {       log.warn(\"retry of transaction \" + transactionKey);       return Mono.empty();     } ...<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code class=\"kotlin\">val foundTransaction = transactionRepository   .findByUniqueKey(transactionKey) if (foundTransaction != null) {   logger.warn(\"retry of transaction $transactionKey\")   return@retry } ...<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u041c\u043e\u0436\u0435\u0442, \u043a\u0430\u043a-\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u044d\u0442\u0443 \u043b\u043e\u0433\u0438\u043a\u0443 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0430\u0434\u0435\u043a\u0432\u0430\u0442\u043d\u0435\u0435 \u043d\u0430 Reactor, \u043d\u043e \u0442\u043e \u0447\u0442\u043e \u044f \u043d\u0430\u0433\u0443\u0433\u043b\u0438\u043b \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0435\u0449\u0451 \u0445\u0443\u0436\u0435.<\/p>\n<h4>\u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442<\/h4>\n<p>\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0432\u0441\u0435\u0433\u0434\u0430 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c traceId \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430. ThreadLocal \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 <a href=\"http:\/\/logback.qos.ch\/manual\/mdc.html\" rel=\"noopener noreferrer nofollow\"><u>MDC<\/u><\/a> (\u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f). \u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c?<\/p>\n<p>\u0415\u0441\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442. \u0418 \u0432 Reactor \u0438 \u0432 Coroutines \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 immutable, \u0442\u0430\u043a \u0447\u0442\u043e \u043d\u043e\u0432\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 MDC \u043f\u043e\u0434\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u0442 \u043d\u0435 \u0442\u0430\u043a \u043f\u0440\u043e\u0441\u0442\u043e (\u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442).<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u043e \u0432 Java \u043d\u0430\u0434\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442 traceId \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442:<\/p>\n<pre><code class=\"java\">@Component public class TraceIdFilter implements WebFilter {   @Override   public Mono&lt;Void&gt; filter(     ServerWebExchange exchange, WebFilterChain chain   ) {     var traceId = Optional.ofNullable(       exchange.getRequest().getHeaders().get(\"X-B3-TRACEID\")     )       .orElse(Collections.emptyList())       .stream().findAny().orElse(UUID.randomUUID().toString());     return chain.filter(exchange)       .contextWrite(context -&gt;         LoggerHelper.addEntryToMDCContext(context, \"traceId\", traceId)       );   } }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0418 \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0447\u0442\u043e-\u0442\u043e \u0437\u0430\u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043d\u0430\u0434\u043e \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u0442\u044c traceId \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430 \u0432 MDC:<\/p>\n<pre><code class=\"java\">public static &lt;T, R&gt; Function&lt;T, Mono&lt;R&gt;&gt; withMDC(   Function&lt;T, Mono&lt;R&gt;&gt; block ) {   return value -&gt; Mono.deferContextual(context -&gt; {     Optional&lt;Map&lt;String, String&gt;&gt; mdcContext = context       .getOrEmpty(MDC_ID_KEY);     if (mdcContext.isPresent()) {       try {         MDC.setContextMap(mdcContext.get());         return block.apply(value);       } finally {         MDC.clear();       }     } else {       return block.apply(value);     }   }); }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0414\u0430, \u044d\u0442\u043e \u043e\u043f\u044f\u0442\u044c Mono. \u0422.\u0435. \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u0433\u0434\u0430, \u043a\u043e\u0433\u0434\u0430 \u043a\u043e\u0434 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u0435\u0440\u043d\u0443\u0442\u044c Mono. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"java\">.onErrorResume(withMDC(e -&gt; {   log.error(\"error on transfer\", e);   return Mono.error(e); }))<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0412 Kotlin \u043f\u0440\u043e\u0449\u0435. \u041d\u0443\u0436\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c traceId \u0441\u0440\u0430\u0437\u0443 \u0432 MDC:<\/p>\n<pre><code class=\"kotlin\">@Component class TraceIdFilter : WebFilter {   override fun filter(     exchange: ServerWebExchange, chain: WebFilterChain   ): Mono&lt;Void&gt; {     val traceId = exchange.request.headers[\"X-B3-TRACEID\"]?.first()      MDC.put(\"traceId\", traceId ?: UUID.randomUUID().toString())     return chain.filter(exchange)   } }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0418 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c <code>withContext(MDCContext()) { \u2026 }<\/code><\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043d\u0430 \u0432\u0440\u0435\u043c\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b, \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f MDC \u0438 \u0432 \u043b\u043e\u0433\u0430\u0445 \u0431\u0443\u0434\u0435\u0442 traceId. \u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u043b\u043e\u0433 \u043d\u0438 \u043e \u0447\u0435\u043c \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<p>\u0422\u0443\u0442 \u0435\u0441\u0442\u044c \u043e\u0434\u043d\u043e \u041d\u041e, \u043e\u0431 \u044d\u0442\u043e\u043c \u043f\u043e\u0437\u0436\u0435.<\/p>\n<h4>\u0414\u0435\u0431\u0430\u0433<\/h4>\n<p>\u0412 Java Reactor \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u0435\u0431\u0430\u0433\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0435 \u0436\u0435, \u0447\u0442\u043e \u0438 \u0441\u043e \u0441\u0442\u0435\u043a\u0442\u0440\u0435\u0439\u0441\u043e\u043c: \u043d\u0443\u0436\u043d\u043e \u0434\u0443\u043c\u0430\u0442\u044c \u043e \u0442\u043e\u043c, \u043a\u0442\u043e \u0432 \u043a\u0430\u043a\u043e\u043c \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u043a\u043e\u0433\u043e \u0432\u044b\u0437\u0432\u0430\u043b, \u0441\u0442\u0430\u0432\u0438\u0442\u044c breakpoints \u0432 \u043b\u044f\u043c\u0431\u0434\u0430\u0445 \u0438 \u0442.\u0434..<\/p>\n<p>\u0421 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c\u0438 \u043d\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u0435\u0448\u044c \u0440\u0430\u0437\u043d\u0438\u0446\u044b \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c: \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 stepOver, \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u044e\u0442\u0441\u044f, \u0432\u0438\u0434\u043d\u043e \u0441\u0442\u0435\u043a\u0442\u0440\u0435\u0439\u0441 \u0438 \u043f\u043e \u043d\u0435\u043c\u0443 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0430\u0442\u044c\u0441\u044f (\u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0432\u043f\u043b\u043e\u0442\u044c \u0434\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430).&nbsp;<\/p>\n<p>\u0412\u0441\u0451 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u043e, \u043a\u0440\u043e\u043c\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c suspend \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0434\u0435\u0431\u0430\u0433\u0430. \u041d\u0430 \u044d\u0442\u043e \u0443\u0436\u0435 \u0435\u0441\u0442\u044c <a href=\"https:\/\/youtrack.jetbrains.com\/issue\/KT-27974\" rel=\"noopener noreferrer nofollow\"><u>issue<\/u><\/a>. \u041f\u0440\u0430\u0432\u0434\u0430, \u043d\u0430\u0434\u043e \u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u0447\u0442\u043e \u0438 \u0432 Java Reactor \u043e\u0441\u043e\u0431\u043e \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0432 evaluate \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0445\u043e\u0447\u0435\u0442\u0441\u044f.<\/p>\n<h4>\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h4>\n<p>\u041f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0438\u043c, \u044f \u0445\u043e\u0447\u0443 \u0443\u0441\u043a\u043e\u0440\u0438\u0442\u044c \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0438\u0437 \u0431\u0430\u0437\u044b \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u044b \u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e, \u0430 \u043d\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043a\u0430\u043a \u0441\u0435\u0439\u0447\u0430\u0441.<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code class=\"java\">return Mono.zip(   transactionRepository.findByUniqueKey(transactionKey)     .map(Optional::of)     .defaultIfEmpty(Optional.empty()),   accountRepository.findById(fromAccountId)     .switchIfEmpty(Mono.error(new AccountNotFound())),   accountRepository.findById(toAccountId)     .switchIfEmpty(Mono.error(new AccountNotFound())), ).flatMap(withMDC(fetched -&gt; {   var foundTransaction = fetched.getT1();   var fromAccount = fetched.getT2();   var toAccount = fetched.getT3();   if (foundTransaction.isPresent()) {     log.warn(\"retry of transaction \" + transactionKey);     return Mono.empty();   }   ... }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code class=\"kotlin\">val foundTransactionAsync = GlobalScope.async(coroutineContext) {   logger.info(\"async fetch of transaction $transactionKey\")   transactionRepository.findByUniqueKey(transactionKey) } val fromAccountAsync = GlobalScope.async(coroutineContext) {    accountRepository.findById(fromAccountId)  } val toAccountAsync = GlobalScope.async(coroutineContext) {    accountRepository.findById(toAccountId)  }  if (foundTransactionAsync.await() != null) {   logger.warn(\"retry of transaction $transactionKey\")   return@retry }  val fromAccount = fromAccountAsync.await() ?: throw AccountNotFound() val toAccount = toAccountAsync.await() ?: throw AccountNotFound()<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0412 Kotlin \u0432\u0435\u0440\u0441\u0438\u0438 \u0435\u0441\u0442\u044c \u044f\u0432\u043d\u043e\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435 \u201c\u0432\u043e\u0442 \u044d\u0442\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u201d, \u0432\u043c\u0435\u0441\u0442\u043e \u201c\u0432\u044b\u043f\u043e\u043b\u043d\u0438 \u0432\u0441\u0451 \u044d\u0442\u043e \u0432 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u201d \u0432 Reactor.<\/p>\n<p>\u0427\u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0432\u0430\u0436\u043d\u043e\u0435, \u043a\u043e\u0434 \u0432\u0435\u0434\u0451\u0442 \u0441\u0435\u0431\u044f \u043f\u043e-\u0440\u0430\u0437\u043d\u043e\u043c\u0443. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 Reactor \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u0440\u0438 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0443 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u0432\u0441\u0435 \u0442\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0442\u0441\u044f. \u0421 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c\u0438 \u043c\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0432\u0441\u0435 \u0442\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0438 \u0436\u0434\u0430\u0442\u044c \u0447\u0435\u0433\u043e-\u0442\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 foundTransactionAsync.await(). \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0435\u0441\u043b\u0438 transactionRepository.findByUniqueKey() \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0441\u044f \u0431\u044b\u0441\u0442\u0440\u0435\u0435, \u0442\u043e \u043c\u044b \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443, \u0431\u0435\u0437 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f accountRepository.findById() (\u044d\u0442\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u043e\u0442\u043c\u0435\u043d\u044f\u0442\u0441\u044f).<\/p>\n<p>\u0412 \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0430\u0445 \u0440\u0430\u0437\u043d\u0438\u0446\u0430 \u0431\u0443\u0434\u0435\u0442 \u043e\u0449\u0443\u0442\u0438\u043c\u0435\u0439. \u041d\u0435 \u0443\u0432\u0435\u0440\u0435\u043d, \u0447\u0442\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u0432 Reactor \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0430:<\/p>\n<pre><code class=\"kotlin\">val foundTransactionAsync = GlobalScope.async(coroutineContext) {   logger.info(\"async fetch of transaction $transactionKey\")   transactionRepository.findByUniqueKey(transactionKey) } val fromAccountAsync = GlobalScope.async(coroutineContext) {   accountRepository.findById(fromAccountId) } val toAccountAsync = GlobalScope.async(coroutineContext) {   accountRepository.findById(toAccountId) }  if (foundTransactionAsync.await() != null) {   logger.warn(\"retry of transaction $transactionKey\")   return@retry }  val transactionToInsert = Transaction(   amount = amount,   fromAccountId = fromAccountId,   toAccountId = toAccountId,   uniqueKey = transactionKey ) transactionalOperator.executeAndAwait {   try {     transactionRepository.save(transactionToInsert)   } catch (e: DataIntegrityViolationException) {     if (e.message?.contains(\"TRANSACTION_UNIQUE_KEY\") != true) {       throw e;     }   }   val fromAccount = fromAccountAsync.await() ?: throw AccountNotFound()   val toAccount = toAccountAsync.await() ?: throw AccountNotFound()   if (fromAccount.amount - amount &lt; BigDecimal.ZERO) {     throw NotEnoghtMoney()   }    accountRepository.transferAmount(     fromAccount.id!!, fromAccount.version, amount.negate()   )   accountRepository.transferAmount(     toAccount.id!!, toAccount.version, amount   ) }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0417\u0434\u0435\u0441\u044c \u044f \u043e\u0436\u0438\u0434\u0430\u044e \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0443\u0436\u0435 \u0432\u043d\u0443\u0442\u0440\u0438 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 \u0411\u0414 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438. \u0422.\u0435. \u043c\u044b \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u043c, \u0436\u0434\u0451\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u043c \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u0435. \u0422\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u0441\u0442\u043e\u0438\u0442, \u0441\u043a\u043e\u0440\u0435\u0435 \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430, \u0445\u043e\u0442\u044c \u043e\u043d\u043e \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 (\u043f\u043e\u043a\u0430 \u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0432 \u0445\u0432\u0430\u0442\u0430\u0435\u0442).<\/p>\n<h3>\u041f\u043e\u0431\u043e\u0447\u043d\u044b\u0435 \u044d\u0444\u0444\u0435\u043a\u0442\u044b<\/h3>\n<p>\u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043a\u0443\u0434\u0430 \u0431\u0435\u0437 \u043d\u0438\u0445.<\/p>\n<h4>\u041d\u0430\u0434\u043e \u044f\u0432\u043d\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c context \u0438 scope<\/h4>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u043a\u0430\u043a \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f, \u043d\u0430\u0434\u043e:<\/p>\n<ol>\n<li>\n<p>\u041a\u0430\u0436\u0434\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443 \u043d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c scope. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0432\u0441\u0435 \u043f\u043e\u0440\u043e\u0436\u0434\u0430\u0435\u043c\u044b\u0435 \u043f\u0440\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u044b \u0432\u0441\u0435 \u0432\u043c\u0435\u0441\u0442\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0448\u0438\u0431\u043a\u0438.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043f\u0440\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c context. \u0417\u0430\u0447\u0435\u043c \u043d\u0430\u043c \u043d\u0443\u0436\u0435\u043d \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u043b \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u043f\u0440\u043e \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>Spring \u043d\u0435 \u0431\u0435\u0440\u0435\u0442 \u043d\u0430 \u0441\u0435\u0431\u044f \u0437\u0430\u0431\u043e\u0442\u0443 \u043e\u0431 \u044d\u0442\u043e\u043c \u0432\u043e\u043f\u0440\u043e\u0441\u0435, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u044f\u0432\u043d\u043e:<\/p>\n<pre><code class=\"kotlin\">@PutMapping(\"\/transfer\") suspend fun transfer(@RequestBody request: TransferRequest) {   coroutineScope {     withContext(MDCContext()) {       ledger.transfer(request.transactionKey, request.fromAccountId,                        request.toAccountId, request.amount)     }   } }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0415\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0438\u0442\u044c \u0432 \u043e\u0434\u043d\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438 \u043f\u043e\u0442\u043e\u043c \u0447\u0435\u0440\u0435\u0437 regexp \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0432\u043e \u0432\u0441\u0435\u0445 \u043c\u0435\u0442\u043e\u0434\u0430\u0445 \u0435\u0441\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0439 \u0432\u044b\u0437\u043e\u0432. \u041d\u043e \u043b\u0443\u0447\u0448\u0435 \u0431\u044b\u043b\u0430 \u0431\u044b \u043a\u0430\u043a\u0430\u044f-\u0442\u043e \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e.<\/p>\n<h4>\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 context<\/h4>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u043c\u044b \u043f\u043e\u0440\u043e\u0436\u0434\u0430\u0435\u043c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u044f\u0432\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442. \u0412\u043e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"kotlin\">val foundTransactionAsync = GlobalScope.async(coroutineContext) {   logger.info(\"async fetch of transaction $transactionKey\")   transactionRepository.findByUniqueKey(transactionKey) }<\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u041f\u043e-\u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 async() \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f (\u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u0443\u0441\u0442). \u0412\u0438\u0434\u0438\u043c\u043e, \u0447\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u043c\u0435\u043d\u044c\u0448\u0435 \u043d\u0435\u044f\u0432\u043d\u044b\u0445 \u0432\u0435\u0449\u0435\u0439. \u041d\u043e \u043a\u0430\u043a \u0438\u0442\u043e\u0433, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u0437\u0430\u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442, \u0432 \u043b\u043e\u0433\u0435 \u043d\u0435 \u043e\u043a\u0430\u0436\u0435\u0442\u0441\u044f traceId. \u041f\u043e\u043c\u043d\u0438\u0442\u0435 \u044f \u043f\u0438\u0441\u0430\u043b, \u0447\u0442\u043e \u043d\u0435 \u043d\u0430\u0434\u043e \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f? \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u0434\u043e \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b (\u0447\u0442\u043e, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043b\u0443\u0447\u0448\u0435).<\/p>\n<h4>AOP \u0438 suspend<\/h4>\n<p>\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u044f \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043b \u0432 \u043f\u0435\u0440\u0432\u043e\u043c \u043f\u0443\u043d\u043a\u0442\u0435, \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u043c\u0443 \u0441\u043b\u043e\u0436\u043d\u043e. \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043f\u043e\u043a\u0430 \u043d\u0435\u043b\u044c\u0437\u044f \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c aspect \u0434\u043b\u044f suspend \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<p>\u042f \u0432 \u0438\u0442\u043e\u0433\u0435 \u0441\u0443\u043c\u0435\u043b \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0430\u043a\u043e\u0439 \u0430\u0441\u043f\u0435\u043a\u0442. \u041d\u043e \u0434\u043b\u044f \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f.<\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u0435\u0435 \u0430\u0434\u0435\u043a\u0432\u0430\u0442\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u0438\u0441\u0430\u0442\u044c \u0430\u0441\u043f\u0435\u043a\u0442\u044b (\u043f\u043e\u043f\u0440\u043e\u0431\u0443\u044e \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0441\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c).<\/p>\n<h3>\u041e\u0446\u0435\u043d\u043a\u0430 \u043b\u0435\u0447\u0435\u043d\u0438\u044f<\/h3>\n<p>\u0412\u0441\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0438\u0441\u0447\u0435\u0437\u043b\u0438. \u0414\u043e\u0431\u0430\u0432\u0438\u043b\u0430\u0441\u044c \u043f\u0430\u0440\u0430 \u043d\u043e\u0432\u044b\u0445, \u043d\u043e \u043e\u043d\u043e \u0442\u0435\u0440\u043f\u0438\u043c\u043e.<\/p>\n<p>\u041d\u0430\u0434\u043e \u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u0447\u0442\u043e \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u044b \u0431\u044b\u0441\u0442\u0440\u043e \u0440\u0430\u0437\u0432\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0438 \u044f \u043e\u0436\u0438\u0434\u0430\u044e \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043d\u0438\u043c\u0438.<\/p>\n<p>\u0412\u0438\u0434\u043d\u043e, \u0447\u0442\u043e \u043a\u043e\u043c\u0430\u043d\u0434\u0430 JetBrains \u0432\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0442\u043d\u0435\u0441\u043b\u0430\u0441\u044c \u043a \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432. \u041d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044f \u0437\u043d\u0430\u044e, \u0433\u0434\u0435-\u0442\u043e \u0433\u043e\u0434 \u043d\u0430\u0437\u0430\u0434 \u0432\u0441\u0451 \u0435\u0449\u0451 \u0431\u044b\u043b\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u0435\u0431\u0430\u0433\u043e\u043c \u0438 \u0441\u0442\u0430\u043a\u0442\u0440\u0435\u0439\u0441\u043e\u043c, \u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443.<\/p>\n<p>\u0421\u0430\u043c\u043e\u0435 \u0433\u043b\u0430\u0432\u043d\u043e\u0435, \u0441 \u043a\u043e\u0440\u0443\u0442\u0438\u043d\u0430\u043c\u0438 \u043d\u0435 \u043d\u0430\u0434\u043e \u0432 \u0433\u043e\u043b\u043e\u0432\u0435 \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432\u0441\u0435 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0440\u0430\u0431\u043e\u0442\u044b Reactor \u0438 \u0435\u0433\u043e \u043c\u043e\u0433\u0443\u0447\u0438\u0439 API. \u0422\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0438\u0448\u0435\u0448\u044c \u043a\u043e\u0434.<\/p>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/537716\/\"> https:\/\/habr.com\/ru\/post\/537716\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<p>\u041d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0430\u0431\u043e\u0442\u0435 \u043f\u0438\u0448\u0435\u043c \u043d\u0430 Reactor. \u0422\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f \u043a\u043b\u0430\u0441\u0441\u043d\u0430\u044f, \u043d\u043e \u043a\u0430\u043a \u0432\u0441\u0435\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u043c\u043d\u043e\u0433\u043e \u041d\u041e. \u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0435\u0449\u0438 \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0430\u044e\u0442, \u043a\u043e\u0434 \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0438 \u0447\u0438\u0442\u0430\u0442\u044c, \u0441 ThreadLocal \u0441\u043e\u0432\u0441\u0435\u043c \u0431\u0435\u0434\u0430. \u0420\u0435\u0448\u0438\u043b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0443\u0439\u0434\u0443\u0442, \u0435\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043d\u0430 Kotlin Coroutines, \u0430 \u043a\u0430\u043a\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442, \u0434\u043e\u0431\u0430\u0432\u044f\u0442\u0441\u044f.<\/p>\n<h3>\u041a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430<\/h3>\n<p>\u0414\u043b\u044f \u0441\u0442\u0430\u0442\u044c\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b <a href=\"https:\/\/github.com\/gnefedev\/coroutines_vs_reactor\" rel=\"noopener noreferrer nofollow\"><u>\u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442<\/u><\/a>, \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c\u0438 \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u043d\u0430 \u0440\u0430\u0431\u043e\u0442\u0435. \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u0434 <a href=\"https:\/\/github.com\/gnefedev\/coroutines_vs_reactor\/blob\/master\/src\/main\/java\/com\/gnefedev\/coroutines\/vs\/reactor\/services\/Ledger.java#L31\" rel=\"noopener noreferrer nofollow\"><u>\u0437\u0434\u0435\u0441\u044c<\/u><\/a>. \u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e \u043d\u0435 \u0441\u0442\u0430\u043b \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0442\u044c \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b, \u0442\u0430\u043a \u043b\u0443\u0447\u0448\u0435 \u0432\u0438\u0434\u043d\u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b.<\/p>\n<p>\u0412 \u0434\u0432\u0443\u0445 \u0441\u043b\u043e\u0432\u0430\u0445 \u043e\u0431 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0435:&nbsp;<\/p>\n<p>\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u0434\u0435\u043d\u044c\u0433\u0438 \u0441 \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u0447\u0451\u0442\u0430 \u043d\u0430 \u0434\u0440\u0443\u0433\u043e\u0439, \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044f \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043e \u0444\u0430\u043a\u0442\u0435 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430.&nbsp;<\/p>\n<p>\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u0438\u0434\u0435\u043c\u043f\u043e\u0442\u0435\u043d\u0442\u0435\u043d, \u0442\u0430\u043a \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 \u0411\u0414, \u0442\u043e \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0443, \u0447\u0442\u043e \u0432\u0441\u0451 \u0445\u043e\u0440\u043e\u0448\u043e. \u041f\u0440\u0438 \u0432\u0441\u0442\u0430\u0432\u043a\u0435 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u043b\u0435\u0442\u0435\u0442\u044c DataIntegrityViolationException, \u044d\u0442\u043e \u0442\u043e\u0436\u0435 \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f \u0443\u0436\u0435 \u0435\u0441\u0442\u044c.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0443\u0439\u0442\u0438 \u0432 \u043c\u0438\u043d\u0443\u0441 \u0435\u0441\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432 \u043a\u043e\u0434\u0435 + Optimistic lock, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043a\u043e\u043d\u043a\u0443\u0440\u0435\u043d\u0442\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0441\u0447\u0435\u0442\u0430. \u0427\u0442\u043e\u0431\u044b \u043e\u043d \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u043d\u0443\u0436\u0435\u043d retry \u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<details class=\"spoiler\">\n<summary>\u0414\u043b\u044f \u0442\u0435\u0445 \u043a\u043e\u043c\u0443 \u043d\u0435 \u043d\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u0441\u0430\u043c \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u044b\u0431\u0438\u0440\u0430\u043b \u0442\u0430\u043a\u043e\u0439, \u0447\u0442\u043e\u0431\u044b \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0430 \u043d\u0435 \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u0431\u044b\u043b \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u043c \u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c. \u0412\u043c\u0435\u0441\u0442\u043e \u043e\u0434\u043d\u043e\u0439 \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0438 \u043d\u0430\u0434\u043e \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u0443\u043f\u0440\u043e\u0432\u043e\u0434\u043a\u0438, optimistic lock \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d (\u0432\u043c\u0435\u0441\u0442\u043e \u043d\u0435\u0433\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0441\u0447\u0435\u0442\u0430 \u0432 sql), select + insert \u043d\u0430\u0434\u043e \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0430 upsert.<\/p>\n<\/div>\n<\/details>\n<h3>\u0416\u0430\u043b\u043e\u0431\u044b \u043f\u0430\u0446\u0438\u0435\u043d\u0442\u0430<\/h3>\n<ol>\n<li>\n<p>Stacktrace \u043d\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043a\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u043f\u043e\u043f\u0430\u043b\u0438 \u0432 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043d\u043e\u0435 \u043c\u0435\u0441\u0442\u043e.<\/p>\n<\/li>\n<li>\n<p>\u041a\u043e\u0434 \u044f\u0432\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435, \u0447\u0435\u043c \u0431\u044b\u043b \u0431\u044b \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044f\u0445.&nbsp;<\/p>\n<\/li>\n<li>\n<p>\u041c\u043d\u043e\u0433\u043e\u0441\u0442\u0443\u043f\u0435\u043d\u0447\u0430\u0442\u0430\u044f \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u0438\u0437-\u0437\u0430 flatMap.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0443\u0434\u043e\u0431\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a \u0438 \u0438\u0445 \u0432\u044b\u0431\u0440\u043e\u0441.<\/p>\n<\/li>\n<li>\n<p>\u0421\u043b\u043e\u0436\u043d\u0430\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0434\u043b\u044f Mono.empty().<\/p>\n<\/li>\n<li>\n<p>\u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0441 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c, \u0435\u0441\u043b\u0438 \u043d\u0430\u0434\u043e \u0432 \u043b\u043e\u0433 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 traceId. (\u0432 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0435 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e, \u043d\u043e \u0442\u0435 \u0436\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 ThreadLocal \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 SpringSecurity)<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u0443\u0434\u043e\u0431\u043d\u043e \u0434\u0435\u0431\u0430\u0436\u0438\u0442\u044c.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u044f\u0432\u043d\u043e\u0435 api \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<\/ol>\n<h3>\u0425\u043e\u0434 \u043b\u0435\u0447\u0435\u043d\u0438\u044f<\/h3>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u043b \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 <a href=\"https:\/\/github.com\/gnefedev\/coroutines_vs_reactor\/pull\/4\" rel=\"noopener noreferrer nofollow\"><u>PR<\/u><\/a> \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u0441 Java \u043d\u0430 Kotlin.&nbsp;<\/p>\n<p>\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0447\u0442\u0438 \u0432\u0435\u0437\u0434\u0435 \u0433\u043b\u0430\u0434\u043a\u0430\u044f.&nbsp;<\/p>\n<p>\u041f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u043b\u043e\u0441\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c com.fasterxml.jackson.module:jackson-module-kotlin \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0430 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f data \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0438 org.jetbrains.kotlin.plugin.spring \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0432\u0435\u0437\u0434\u0435 open \u043c\u043e\u0434\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u044b.<\/p>\n<p>\u0412 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u044b\u043b\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c <code>suspend fun transfer(@RequestBody request: TransferRequest)<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>public Mono&lt;Void&gt; transfer(@RequestBody TransferRequest request)<\/code><\/p>\n<p>\u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b <code>suspend fun save(account: Account): Account<\/code> \u0432\u043c\u0435\u0441\u0442\u043e <code>Mono&lt;Account&gt; save(Account account);<\/code> \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435, \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0432 \u043d\u0438\u0445 \u0442\u043e\u043b\u044c\u043a\u043e suspend \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043d\u0430\u0434\u043e, \u0447\u0442\u043e\u0431\u044b \u0445\u043e\u0442\u044c \u043e\u0434\u0438\u043d \u043c\u0435\u0442\u043e\u0434 \u0440\u0430\u0431\u043e\u0442\u0430\u043b \u0441 Reactor \u0442\u0438\u043f\u0430\u043c\u0438.<\/p>\n<p>\u0422\u0435\u0441\u0442\u044b \u043e\u0431\u0435\u0440\u043d\u0443\u043b \u0432 <code>runBlocking { \u2026 }<\/code>, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c suspend \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<p>\u0414\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 Retry \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 <a href=\"https:\/\/github.com\/michaelbull\/kotlin-retry\" rel=\"noopener noreferrer nofollow\"><u>kotlin-retry<\/u><\/a>. \u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435, \u0432 \u043d\u0435\u0439 \u043d\u0435 \u0431\u044b\u043b\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e \u043a\u043b\u0430\u0441\u0441\u0443 \u043e\u0448\u0438\u0431\u043a\u0438, \u043d\u043e \u044d\u0442\u043e \u0431\u044b\u043b\u043e \u043b\u0435\u0433\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c (\u0437\u0430\u0432\u0451\u043b <a href=\"https:\/\/github.com\/michaelbull\/kotlin-retry\/pull\/14\" rel=\"noopener noreferrer nofollow\"><u>PR<\/u><\/a>).<\/p>\n<p>\u041d\u0443 \u0438, \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u043b \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c. \u0412\u0441\u0435 \u0434\u0435\u0442\u0430\u043b\u0438 \u043e\u043f\u0438\u0448\u0443 \u043d\u0438\u0436\u0435 \u043f\u043e-\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code class=\"java\">public Mono&lt;Void&gt; transfer(String transactionKey, long fromAccountId,                            long toAccountId, BigDecimal amount) {   return transactionRepository.findByUniqueKey(transactionKey)     .map(Optional::of)     .defaultIfEmpty(Optional.empty())     .flatMap(withMDC(foundTransaction -&gt; {       if (foundTransaction.isPresent()) {         log.warn(\"retry of transaction \" + transactionKey);         return Mono.empty();       }       return accountRepository.findById(fromAccountId)         .switchIfEmpty(Mono.error(new AccountNotFound()))         .flatMap(fromAccount -&gt; accountRepository.findById(toAccountId)           .switchIfEmpty(Mono.error(new AccountNotFound()))           .flatMap(toAccount -&gt; {             var transactionToInsert = Transaction.builder()               .amount(amount)               .fromAccountId(fromAccountId)               .toAccountId(toAccountId)               .uniqueKey(transactionKey)               .build();             var amountAfter = fromAccount.getAmount().subtract(amount);             if (amountAfter.compareTo(BigDecimal.ZERO) &lt; 0) {               return Mono.error(new NotEnoghtMoney());             }             return transactionalOperator.transactional(               transactionRepository.save(transactionToInsert)                 .onErrorResume(error -&gt; {                   \/\/transaction was inserted on parallel transaction,                   \/\/we may return success response                   if (error instanceof DataIntegrityViolationException              &amp;&amp; error.getMessage().contains(\"TRANSACTION_UNIQUE_KEY\")) {                     return Mono.empty();                   } else {                     return Mono.error(error);                   }                 })                 .then(accountRepository.transferAmount(                   fromAccount.getId(), fromAccount.getVersion(),                    amount.negate()                 ))                 .then(accountRepository.transferAmount(                   toAccount.getId(), toAccount.getVersion(), amount                 ))             );           }));     }))     .retryWhen(Retry.backoff(3, Duration.ofMillis(1))       .filter(OptimisticLockException.class::isInstance)       .onRetryExhaustedThrow((__, retrySignal) -&gt; retrySignal.failure())     )     .onErrorMap(       OptimisticLockException.class,       e -&gt; new ResponseStatusException(         BANDWIDTH_LIMIT_EXCEEDED,         \"limit of OptimisticLockException exceeded\", e       )     )     .onErrorResume(withMDC(e -&gt; {       log.error(\"error on transfer\", e);       return Mono.error(e);     })); }<\/code><\/pre>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code class=\"kotlin\">suspend fun transfer(transactionKey: String, fromAccountId: Long,                      toAccountId: Long, amount: BigDecimal) {   try {     try {       retry(limitAttempts(3) + filter { it is OptimisticLockException }) {         val foundTransaction = transactionRepository           .findByUniqueKey(transactionKey)         if (foundTransaction != null) {           logger.warn(\"retry of transaction $transactionKey\")           return@retry         }          val fromAccount = accountRepository.findById(fromAccountId)           ?: throw AccountNotFound()         val toAccount = accountRepository.findById(toAccountId)           ?: throw AccountNotFound()          if (fromAccount.amount - amount &lt; BigDecimal.ZERO) {           throw NotEnoghtMoney()         }         val transactionToInsert = Transaction(           amount = amount,           fromAccountId = fromAccountId,           toAccountId = toAccountId,           uniqueKey = transactionKey         )         transactionalOperator.executeAndAwait {           try {             transactionRepository.save(transactionToInsert)           } catch (e: DataIntegrityViolationException) {             if (e.message?.contains(\"TRANSACTION_UNIQUE_KEY\") != true) {               throw e;             }           }            accountRepository.transferAmount(             fromAccount.id!!, fromAccount.version, amount.negate()           )           accountRepository.transferAmount(             toAccount.id!!, toAccount.version, amount           )         }       }     } catch (e: OptimisticLockException) {       throw ResponseStatusException(         BANDWIDTH_LIMIT_EXCEEDED,          \"limit of OptimisticLockException exceeded\", e       )     }   } catch (e: Exception) {     logger.error(e) { \"error on transfer\" }     throw e;   } }<\/code><\/pre>\n<h4>Stacktraces<\/h4>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0441\u0430\u043c\u043e\u0435 \u0433\u043b\u0430\u0432\u043d\u043e\u0435.&nbsp;<\/p>\n<p>\u0411\u044b\u043b\u043e:<\/p>\n<pre><code>o.s.w.s.ResponseStatusException: 509 BANDWIDTH_LIMIT_EXCEEDED \"limit of OptimisticLockException exceeded\"; nested exception is c.g.c.v.r.OptimisticLockException \tat c.g.c.v.r.services.Ledger.lambda$transfer$5(Ledger.java:75) \t... Caused by: c.g.c.v.r.OptimisticLockException: null \tat c.g.c.v.r.repos.AccountRepositoryImpl.lambda$transferAmount$0(AccountRepositoryImpl.java:27) \tat r.c.p.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)   ...<\/code><\/pre>\n<p>\u0421\u0442\u0430\u043b\u043e:<\/p>\n<pre><code>error on transfer o.s.w.s.ResponseStatusException: 509 BANDWIDTH_LIMIT_EXCEEDED \"limit of OptimisticLockException exceeded\"; nested exception is c.g.c.v.r.OptimisticLockException \tat c.g.c.v.r.services.Ledger.transfer$suspendImpl(Ledger.kt:70) \tat c.g.c.v.r.services.Ledger$transfer$1.invokeSuspend(Ledger.kt) \t... Caused by: c.g.c.v.r.OptimisticLockException: null \tat c.g.c.v.r.repos.AccountRepositoryImpl.transferAmount(AccountRepositoryImpl.kt:24) \t... \tat c.g.c.v.r.services.Ledger$transfer$3$1.invokeSuspend(Ledger.kt:65) \tat c.g.c.v.r.services.Ledger$transfer$3$1.invoke(Ledger.kt) \tat o.s.t.r.TransactionalOperatorExtensionsKt$executeAndAwait$2$1.invokeSuspend(TransactionalOperatorExtensions.kt:30) \t(Coroutine boundary) \tat o.s.t.r.TransactionalOperatorExtensionsKt.executeAndAwait(TransactionalOperatorExtensions.kt:31) \tat c.g.c.v.r.services.Ledger$transfer$3.invokeSuspend(Ledger.kt:56) \tat com.github.michaelbull.retry.RetryKt$retry$3.invokeSuspend(Retry.kt:38) \tat c.g.c.v.r.services.Ledger.transfer$suspendImpl(Ledger.kt:35) \tat c.g.c.v.r.controllers.LedgerController$transfer$2$1.invokeSuspend(LedgerController.kt:20) \tat c.g.c.v.r.controllers.LedgerController$transfer$2.invokeSuspend(LedgerController.kt:19) \tat kotlin.reflect.full.KCallables.callSuspend(KCallables.kt:55) \tat o.s.c.CoroutinesUtils$invokeSuspendingFunction$mono$1.invokeSuspend(CoroutinesUtils.kt:64) \t(Coroutine creation stacktrace) \tat k.c.i.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:122) \tat k.c.i.CancellableKt.startCoroutineCancellable(Cancellable.kt:30) \t... Caused by: c.g.c.v.r.OptimisticLockException: null \tat c.g.c.v.r.repos.AccountRepositoryImpl.transferAmount(AccountRepositoryImpl.kt:24) \t... \tat c.g.c.v.r.services.Ledger$transfer$3$1.invokeSuspend(Ledger.kt:65) \tat c.g.c.v.r.services.Ledger$transfer$3$1.invoke(Ledger.kt) \tat o.s.t.r.TransactionalOperatorExtensionsKt$executeAndAwait$2$1.invokeSuspend(TransactionalOperatorExtensions.kt:30) \t... <\/code><\/pre>\n<div class=\"table\">\n<table>\n<tbody>\n<tr><\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>\u0421\u043a\u0443\u0447\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0435\u043a\u0442\u0440\u0435\u0439\u0441\u043e\u0432 \u044f \u0432\u044b\u0440\u0435\u0437\u0430\u043b, \u043f\u0430\u043a\u0435\u0442\u044b \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u043b (\u0437\u0430\u0431\u043e\u0447\u0443\u0441\u044c \u043e \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0435, \u0438 \u0431\u0435\u0437 \u0442\u043e\u0433\u043e \u0434\u043b\u0438\u043d\u043d\u043e).<\/p>\n<p>\u0412 Java \u043e\u0447\u0435\u043d\u044c \u043a\u0443\u0446\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f. \u0414\u0430, \u043e\u0448\u0438\u0431\u043a\u0430 \u0435\u0441\u0442\u044c. \u0414\u0430\u0436\u0435 \u0432\u0438\u0434\u043d\u043e \u043d\u0430 \u043a\u0430\u043a\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0435 \u043e\u043d\u0430 \u0432\u044b\u043b\u0435\u0442\u0435\u043b\u0430. \u0422\u043e\u043b\u044c\u043a\u043e <\/p>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-316478","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/316478","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=316478"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/316478\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=316478"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=316478"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=316478"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}