Skip to content

Instantly share code, notes, and snippets.

@Ivana-
Created February 17, 2024 23:50
Show Gist options
  • Save Ivana-/9dbfca86163955ab37bcb3964fbdf817 to your computer and use it in GitHub Desktop.
Save Ivana-/9dbfca86163955ab37bcb3964fbdf817 to your computer and use it in GitHub Desktop.
Тинькофф курс All to Scala 1 задание
(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