Nano Hash - криптовалюты, майнинг, программирование

Reactive Spring Boot: возврат оценок до завершения карты

Я использую реактивный WebClient для создания API, который взаимодействует с двумя другими API. API2 необходимо получить информацию от API1, а затем мой сервис объединяет и возвращает обе информации. Ресурс:

@GetMapping("monoMedication/{medID}")
    public  Mono<Object> getMonoMedication(@PathVariable String medID) throws SSLException {
           Mono<Login> Login =createWebClient()
                .post()
                .uri("URI_LOGIN_API1" )
                .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromObject(body))
                .retrieve()
                .bodyToMono(Login.class);

        return Login.map(login-> {
                       Mono<String> medicationBundles = null;
            try {
                 medicationBundles = createWebClient()
                        .post()
                        .uri("URI_API1_GET_DATA")
                        .contentType(MediaType.APPLICATION_JSON)
                        .accept(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromObject("Information"))
                        .header("Authorization", login.getSessionId())
                        .retrieve()
                       .bodyToMono(String.class);

            } catch (SSLException e) {
                e.printStackTrace();
            }

            return  medicationBundles.map(bundles_string -> {
                try {
                    List<Object> bundle_list = mapper.readValue(bundles_string, new TypeReference<List<Object>>(){});
                    bundle_list.forEach(bundle-> processBundle(bundle,medicationList));

                    return medicationList;
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
                    return null;
                })
        })
    } 

Функция:

List<String> medicationList = new ArrayList<>();
    private void processBundle(Object bundle, List<String> medicationlist) {
       //do something to get id from bundle
        String ID = bundle.getID();
// if i add something to medicationList.add(ID) here, it is in the required return of my API
        Mono<String> Medication = 
            webClientBuilder.build()
            .get()
            .uri("URI_API2_GET_DATA"+ID)
            .retrieve()
            .bodyToMono(String.class);

         Medication.map(medication_ID -> {
        //do something to get information from medication_ID
            String info = medication_ID.getInfo();

            //this Information comes after the required return
            return medicationList.add(info+ID);
        }).subscribe();
    }

Моя проблема в том, что возврат происходит до завершения требуемой последней карты. мне как-то чего-то не хватает. Я пробовал разные подходы, например. then(), thenMany(), thenReturn() в разных позициях. Есть ли способ сделать это? Если есть, возможно, уже готовый простой пример, это тоже поможет!


Ответы:


1

Трудно следить за вашим кодом, потому что вы смешиваете и сопоставляете реактивное программирование с императивным программированием не лучшим образом.

Ваш код не компилируется, и у вас есть несколько странных вещей, таких как medID, которые никогда не используются, и переменные, которые никогда не объявлялись, например body. Поэтому я взял ваш код только как есть, я не создал полностью рабочий пример, а только руководство.

Вы должны выбрать либо реактивный маршрут, либо императивный маршрут. Некоторые части моего ответа будут самоуверенными, если кто-то позже пожалуется, так что это отказ от ответственности.

Во-первых, вы по каждому запросу создаете несколько WebClients, это, на мой взгляд, считается плохой практикой. Создание WebClient является своего рода дорогостоящей ненужной операцией, поскольку вы можете использовать их повторно, поэтому вам следует объявить свои веб-клиенты во время запуска и @Autowire их.

@Configuration
public class WebClientConfig {


    @Bean
    @Qualifier("WebClient1")
    public WebClient createWebClient1() {
        return WebClient.create( ... );
    }

    @Bean
    @Qualifier("WebClient2")
    public WebClient createWebClient2() {
        return WebClient.create( ... );
    }

    @Bean
    @Qualifier("WebClient3")
    public WebClient createWebClient3() {
        return WebClient.create( ... );
    }
}

А затем используйте их, автоматически подключив их к вашему классу.

После очистки вашего кода и разделения его на функции с правильным возвратом, я надеюсь, это даст вам некоторое представление о том, как бы я его структурировал. Ваша проблема в том, что вы не возвращаетесь должным образом из своих функций и не выполняете цепочку возвратов. Как только вам нужно использовать subscribe, вы обычно понимаете, что сделали что-то не так.

@RestController
public class FooBar {

    private WebClient webClient1;
    private WebClient webClient2;
    private WebClient webClient3;

    @Autowire
    public Foobar(@Qualifier("WebClient1") WebClient webclient1, @Qualifier("WebClient2") WebClient webclient2, @Qualifier("WebClient3") WebClient webclient3) {
        this.webClient1 = webClient1;
        this.webClient2 = webClient2;
        this.webClient3 = webClient3;
    }

    @GetMapping("monoMedication/{medID}")
    public Mono<List<MedicationData>> getMonoMedication(@PathVariable String medID) {
        return doLogin()
            .flatMap(login -> {
                return getMedicationBundles(login.getSessionId());
            }).flatMap(medicationBundles -> {
                return getMedicationData(medicationBundle.getId());
            }).collectList();
    }

    private Mono<Login> doLogin() {
         return webClient1
                .post()
                .uri("URI_LOGIN_API1")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .bodyValue(body)
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> ...)
                .onStatus(HttpStatus::is5xxServerError, response -> ...)
                .bodyToMono(Login.class);
    }

    private Flux<MedicationBundle> getMedicationBundles(String sessionId) {
        return webClient2
                .post()
                .uri("URI_API1_GET_DATA")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .bodyValue("Information")
                .header("Authorization", sessionId)
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, response -> ...)
                .onStatus(HttpStatus::is5xxServerError, response -> ...)
                .bodyToFlux(MedicationBundle.class);
    }

    private Mono<String> getMedicationData(String medicationId) {
        return webClient3.get()
            .uri(uriBuilder - > uriBuilder
                    .path("/URI_API2_GET_DATA/{medicationId}")
                    .build(medicationId))
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, response -> ...)
            .onStatus(HttpStatus::is5xxServerError, response -> ...)
            .bodyToMono(MedicationData.class);
    }
}

Я написал это от руки без какой-либо IDE, это больше для того, чтобы показать вам, как вы должны структурировать свой код, надеюсь, что это покажет вам некоторые рекомендации.

Что можно и чего нельзя делать в реактивном программировании:

  • Избегайте использования блоков try/catch, в реактивном программировании вы обычно либо возвращаете Mono.empty(), чтобы игнорировать ошибку, либо возвращаете Mono.error(), содержащее исключение.

  • Используйте flatMap для асинхронной обработки, map для синхронной обработки, есть несколько других операторов, таких как concatMap и flatMapSequential, которые сохраняют порядок, объясняя, что это отдельный ответ.

  • по возможности избегайте void методов, всегда старайтесь использовать чистые функции (избегайте манипулирования списками в пустых функциях , вы делаете это с указателями в C++, а не в Java), вы можете вернуть Mono<Void> из реактивной функции, объединив оператор .then().

  • Всегда используйте преимущества системы типов, если это возможно, старайтесь не сериализовать в Object, если это возможно, создавайте объектное представление данных и сериализуйте в него.

  • Почти никогда не подписывайтесь в своем приложении, если вы не являетесь потребителем данных. Тот, кто инициирует вызов, обычно имеет номер subscriber, ваше приложение обычно имеет номер producer, а вызывающий клиент (веб-сервис или другой сервис) — номер subscriber. Несколько subscribers обычно являются запахом кода.

  • Всегда старайтесь возвращаться и цепляться за возвраты. Все функции, которые я написал выше, что-то возвращают, и цепочка возвращается, это то, что называется construction belt analogy. Я лично всегда начинаю каждую функцию с написания инструкции return в первой строке, затем я начинаю писать свои реактивные вещи. Никогда не покидайте Mono или Flux, не вернувшись к ним и не привязавшись к ним, потому что Mono или Flux никогда не запустятся, если на них никто не подпишется.

  • структурируйте свой код в функции, написание функции бесплатно :)

Немного полезной литературы и видео:

Начало работы с Reactor

официальная документация Webflux (я предлагаю сначала просмотреть официальную документацию по реактору, эту документацию может быть трудно понять, если вы не знаете реактор достаточно хорошо)

Хорошее весеннее видео, в нем почти все, что я написал выше. Что можно и чего нельзя делать в реактивном программировании

Как было сказано ранее, это немного основано на мнении, но, надеюсь, это даст вам некоторые рекомендации.

18.07.2020
  • Можно ли добиться того же с помощью одного экземпляра WebClient? 20.07.2020
  • так как я не знаю какие API вы вызываете и как они должны быть настроены, только вы можете ответить на этот вопрос, но если ничего особенного, то наверное. 20.07.2020
  • Спасибо за подробный ответ, буду признателен! Как вы правильно заметили, возникла проблема с цепочкой. Я сделал карту внутри карты, поэтому я думаю, что это была проблема. Я очистил код и выполнил одну операцию за другой, и это сработало. Спасибо также за другие советы по программированию с помощью Spring, я их запомню. 24.07.2020
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..