Project Reactor: реактивное преобразование состояния изменяемого объекта с другим издателем в той же последовательности - PullRequest
1 голос
/ 29 октября 2019

Я пытаюсь научиться реактивному программированию путем рефакторинга некоторого в настоящее время блокирующего кода. Я неоднократно сталкивался с проблемой установки состояния некоторого изменяемого объекта данных из последовательности Mono без подписки на нее. В старом коде значения полей объекта вычислялись неким блокирующим сервисом, который я сейчас также делаю внутри Mono s.

До сих пор я обычно (ab) использовал flatMap для получения ожидаемогоПоведение:

initExpensiveObject().flatMap(expObj -> initExpensiveField(expObj).map(expField -> {
    expObj.setExpensiveField(expField);
    return expObj;
})).subscribe(expObj -> System.out.println("expensiveField: " + expObj.getExpensiveField()));
import reactor.core.publisher.Mono;

public class Main {

    /**
     * Expensive, lazy object instantiation
     */
    public static Mono<ExpensiveObject> initExpensiveObject() {
        return Mono.fromCallable(ExpensiveObject::new);
    }

    /**
     * Expensive, async mapping (i.e. database access, network request):
     * ExpensiveObject -> int
     */
    public static Mono<Integer> initExpensiveField(ExpensiveObject expObj) {
        return Mono.just(1);
    }

    public static class ExpensiveObject {
        private int expensiveField = -1;

        public int getExpensiveField() {
            return expensiveField;
        }

        public void setExpensiveField(int expensiveField) {
            this.expensiveField = expensiveField;
        }
    }
}

Хотя этот flatMap -паттерн работает, я чувствую, что должно быть более реактивное решение. Учитывая, что в одном Mono столько операторов, интуитивно кажется неправильным «отображать» один объект на один и тот же, чтобы изменить его состояние. Операторы «побочного эффекта» (doOn*), однако, не позволяют легко трансформировать другого издателя без подписки на него.

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

1 Ответ

1 голос
/ 29 октября 2019

Пока работает этот шаблон flatMap, я чувствую, что должно быть более реактивное решение.

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

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

"Наименьшие изменения"«Подход, который я выбрал бы, будет следующим:

  • Сделать ExpensiveObject неизменным. Удалите метод setter и предоставьте другой конструктор, который принимает явное значение для expensiveField.
  • Предоставьте «реактивный» withExpensiveField() (или ofExpensiveField(), или что-то еще полностью, выбирайте!)на ExpensiveObject, который принимает Mono<Integer> для expensiveField и возвращает Mono<ExpensiveObject>.
  • Это позволяет вам построить вашу реактивную цепочку с помощью одного flatMap() вызова, и не будет видно никаких изменяемых объектов:
    initExpensiveObject()
            .flatMap(expObj -> expObj.withExpensiveField(initExpensiveField(expObj)))
            .subscribe(expObj -> System.out.println("expensiveField: " + expObj.getExpensiveField()));
    

Приведенный выше код с изменениями:

public class Main {

    /**
     * Expensive, lazy object instantiation
     */
    public static Mono<ExpensiveObject> initExpensiveObject() {
        return Mono.fromCallable(ExpensiveObject::new);
    }

    /**
     * Expensive, async mapping (i.e. database access, network request):
     * ExpensiveObject -> int
     */
    public static Mono<Integer> initExpensiveField(ExpensiveObject expObj) {
        return Mono.just(1);
    }

    public static final class ExpensiveObject {

        private final int expensiveField;

        public ExpensiveObject() {
            expensiveField = -1;
        }

        private ExpensiveObject(int expensiveField) {
            this.expensiveField = expensiveField;
        }

        public int getExpensiveField() {
            return expensiveField;
        }

        public Mono<ExpensiveObject> withExpensiveField(Mono<Integer> expensiveField) {
            return expensiveField.map(ExpensiveObject::new);
        }
    }
}

Возможно, вы захотите изменить вышеупомянутое в зависимости от окончательного дизайна (методы with мало что делаютнапример, на один объект поля), но это выдвигает на первый план основные идеи.

...