Created
February 17, 2024 23:50
-
-
Save Ivana-/9dbfca86163955ab37bcb3964fbdf817 to your computer and use it in GitHub Desktop.
Тинькофф курс All to Scala 1 задание
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns tinkoff-1 | |
(:import [java.util Date])) | |
;; У вас есть два эквивалентных сервиса, в которых можно узнать статус заявки | |
;; при помощи функций `getApplicationStatus1` и `getApplicationStatus2`. | |
;; Каждый из сервисов может вернуть один из ответов: | |
;; * `Response.Success` в случае успешно выполненного запроса | |
;; * `Response.RetryAfter` в случае, если сервис не может выполнить запрос, поле `delay` - желательная задержка перед повторным запросом | |
;; * `Response.Failure` в случае, если в ходе обработки запроса произошла ошибка | |
(defn random-order-status [] | |
;; случайная строка статуса ордера, возвращаемая сервисом | |
(apply str (repeatedly 20 #(rand-nth "abcdefghijklmnopqrstuvwxyz0123456789")))) | |
(defn get-application-status-mock [id] | |
;; мокаем ответ сервиса: ожидание по таймауту и 10% успех, 70% фэйл, 20% ретрай с дилеем | |
(Thread/sleep (+ 100 (rand-int 100))) | |
(let [status (rand-int 100)] | |
(cond (< status 10) {:status :success | |
:order-id id | |
:order-status (random-order-status)} | |
(< status 80) {:status :failure} | |
:else {:status :retry-after, :delay 100}))) | |
(defn get-application-status-impl [service-id order-id statistics] | |
;; мучаем в цикле сервис запросами, пока не получим успешный ответ | |
;; параллельно в атоме statistics накапливаем количество неуспешных запросов | |
;; и время последнего неуспешного запроса | |
(loop [{:keys [status delay] :as result} (get-application-status-mock order-id)] | |
(if (= status :success) | |
(assoc result :service-id service-id) | |
(do | |
(swap! statistics #(-> % (assoc :time (Date.)) (update :cnt (fnil inc 0)))) | |
(when delay (Thread/sleep delay)) | |
(recur (get-application-status-mock order-id)))))) | |
(defn get-application-status-1 [order-id statistics] | |
;; запрос до победного к сервису 1 | |
(get-application-status-impl 1 order-id statistics)) | |
(defn get-application-status-2 [order-id statistics] | |
;; запрос до победного к сервису 2 | |
(get-application-status-impl 2 order-id statistics)) | |
;; Информация в сервисах синхронизирована, поэтому источником правды можно считать тот сервис, | |
;; который вернёт ответ первым. | |
;; Ваша задача написать метод для получения статуса заявки `performOperation`, | |
;; который будет делать обращение к двум сервисам и возвращать ответ клиенту как только | |
;; получен ответ хотя бы от одного из них. | |
;; Технические детали | |
;; 1. У `performOperation` есть таймаут -15 секунд. | |
;; 2. `performOperation` должен возвращать ответ клиенту как можно быстрее. | |
;; 3. В теле `performOperation` должны выполняться запросы к сервисам (вызовы методов), а также обработка ответов сервисов и преобразование полученных данных в ответ нового метода. | |
;; 4. Для успешно выполненной операции вернуть `ApplicationStatusResponse.Success`, где: | |
;; * `id` - идентификатор заявки (`Response.applicationId`) | |
;; * `status` - статус заявки (`Response.applicationStatus`) | |
;; 5. В случае возникновения ошибки нужно вернуть `ApplicationStatusResponse.Failure`, где: | |
;; * `lastRequestTime` - время последнего запроса, завершившегося ошибкой (опциональное); | |
;; * `retriesCount` - количество неуспешных запросов к сервисам | |
(defn perform-operation [id timeout] | |
(let [resp (promise) | |
statistics-1 (atom nil) | |
statistics-2 (atom nil) | |
;; отдельный поток запросов к первому сервису | |
f1 (future (deliver resp (get-application-status-1 id statistics-1))) | |
;; отдельный поток запросов ко второму сервису | |
f2 (future (deliver resp (get-application-status-2 id statistics-2))) | |
;; дереф блокирует текущий поток до первого успешного ответа любого сервиса | |
;; или до наступления таймаута | |
result (deref resp timeout :timed-out)] | |
;; прерываем еще не завершившиеся потоки запросов к сервисам | |
(doseq [f [f1 f2]] | |
(when-not (future-cancelled? f) (future-cancel f))) | |
;; формируем результат | |
(if (= result :timed-out) | |
{:status :failure | |
;; последнее время неуспешного запорса из обеих статистик | |
:last-request-time (->> [statistics-1 statistics-2] | |
(keep (comp :time deref)) | |
sort | |
last) | |
;; суммарное количество неуспешных запросов из обеих статистик | |
:retries-count (->> [statistics-1 statistics-2] | |
(keep (comp :cnt deref)) | |
(apply +))} | |
result))) | |
(comment | |
;; прогоним мок-тест 10 раз | |
(for [i (range 10)] | |
{:i i :resp (perform-operation i 1000)}) | |
;; типичный результат прогона | |
[{:i 0, :resp {:status :success, :order-id 0, :order-status "xwb58lkiprzb2ksev7nq", :service-id 1}} | |
{:i 1, :resp {:status :failure, :last-request-time #inst "2024-02-17T22:32:16.115-00:00", :retries-count 11}} | |
{:i 2, :resp {:status :failure, :last-request-time #inst "2024-02-17T22:32:17.170-00:00", :retries-count 12}} | |
{:i 3, :resp {:status :success, :order-id 3, :order-status "2ywl3z2y6pye2j1301oh", :service-id 1}} | |
{:i 4, :resp {:status :success, :order-id 4, :order-status "yxbv4rp16ae2mwdrkg1i", :service-id 2}} | |
{:i 5, :resp {:status :success, :order-id 5, :order-status "lo9zhw8qc7jikw6tu769", :service-id 1}} | |
{:i 6, :resp {:status :failure, :last-request-time #inst "2024-02-17T22:32:19.631-00:00", :retries-count 11}} | |
{:i 7, :resp {:status :success, :order-id 7, :order-status "1ofys2bwiqru1x53jeki", :service-id 1}} | |
{:i 8, :resp {:status :success, :order-id 8, :order-status "24wktti2jz06o4c198ec", :service-id 2}} | |
{:i 9, :resp {:status :success, :order-id 9, :order-status "2kxid6qvssm098r8h3nu", :service-id 2}}] | |
;; | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment