¿Qué es un buen lenguaje para flatMaps anidados en Java Reactor?

Phyzz:

Heredé la responsabilidad de un servicio REST escrito en Java usando primavera y librerías asociadas, incluyendo reactor. Para operaciones costosas como REST le llama o las operaciones de bases de datos del código está terminando ampliamente los resultados en el reactor Mono.

Hay todo tipo de cosas que necesitan ser abordados en el código pero que sigue apareciendo está anidado flatMaps más de Monos para las secuencias de operaciones costosas que terminan varios niveles de hasta sangría profundamente en un lío ilegible. Me resulta molesto adicional porque venía de Scala, donde esta forma de usar flatMapno es tan mala debido a la comprensión de mantenimiento de azúcar sintáctico todo más o menos al mismo nivel de alcance en vez de ir más profundo.

hasta ahora no he tenido ningún éxito encontrar una manera de acercarse a este para que sea más fácil de leer, aparte de un refactor masiva (y aún así no estoy seguro de por dónde empezar a perfeccionar por ejemplo).

ejemplo anónimos basado en el código, (todos los errores de sintaxis son de forma anónima):

public Mono<OutputData> userActivation(InputData input) {
    Mono<DataType1> d1 = service.expensiveOp1(input);

    Mono<OutputData> result =
        d1
          .flatMap(
            d1 -> {
              return service
                  .expensiveOp2(d1.foo())
                  .flatMap(
                      d2 -> {
                        if (Status.ACTIVE.equals(d2.getStatus())) {
                          throw new ConflictException("Already active");
                        }

                        return service
                            .expensiveOp3(d1.bar(), d2.baz())
                            .flatMap(
                                d3 -> {
                                  d2.setStatus(Status.ACTIVE);

                                  return service
                                      .expensiveOp5(d1, d2, d3)
                                      .flatMap(
                                          d4 -> {
                                            return service.expensiveOp6(d1, d4.foobar())
                                          });
                                });
                      });
            })

    return result;
}

Michael Berry:

Yuck. Unas pocas cosas que no les gusta de ese fragmento, pero voy a empezar con el grande - el anidamiento.

La única razón para la anidación es que en (por ejemplo) expensiveOp5()que necesita una referencia a d1, d2y d3no sólo d4- por lo que no se puede simplemente asignar a través "normalmente", porque se pierde esas referencias anteriores. A veces es posible refactor estas dependencias de distancia, en un contexto particular, por lo que me examino esa ruta en primer lugar.

Sin embargo, si no es posible o deseable, he tendido a encontrar anidadas flatMap()llamadas como este son mejores reemplazado con objetos intermedios a través de la composición.

Si usted tiene un montón de clases de la siguiente manera, por ejemplo:

@Data
class IntermediateResult1 {
    private DataType1 d1;
    private DataType2 d2;
}

@Data
class IntermediateResult2 {
    public IntermediateResult2(IntermediateResult1 i1, DataType3 d3) {
        this.d1 = i1.getD1();
        this.d2 = i1.getD2();
        this.d3 = d3;
    }
    private DataType1 d1;
    private DataType2 d2;
    private DataType3 d3;
}

... y así sucesivamente, a continuación, puedes hacer algo como:

return d1.flatMap(d1 -> new IntermediateResult1(d1, service.expensiveOp2(d1.foo())))
         .flatMap(i1 -> new IntermediateResult2(i1, service.expensiveOp3(i1.getD1().bar(), i1.getD2().baz())))
         //etc.

Por supuesto, también se puede entonces romper las llamadas en sus propios métodos para hacerlo más claro (que probablemente me aconsejan en este caso):

return d1.flatMap(this::doOp1)
         .flatMap(this::doOp2)
         .flatMap(this::doOp3)
         .flatMap(this::doOp4)
         .flatMap(this::doOp5);

Obviamente, los nombres que he usado anteriormente deben considerarse nada más que los marcadores de posición - usted debe pensar cuidadosamente acerca de estos nombres, tan bueno nombrar aquí hará que el razonamiento sobre la explicación y la corriente reactiva mucho más natural.

Aparte de la anidación, otros dos puntos vale la pena destacar que en el código:

  • Utilizar return Mono.error(new ConflictException("Already active"));en lugar de tirar de forma explícita, ya que hace que sea mucho más claro que usted está tratando con una explícita Mono.erroren la corriente.
  • Nunca utilizar métodos mutables como setStatus()medio camino a través de una cadena reactiva - que está pidiendo problemas más adelante. En su lugar, utilizar algo así como el withpatrón para generar una nueva instancia de d2con un campo actualizada. A continuación, puede llamar expensiveOp5(d1, d2.withStatus(Status.ACTIVE), d3)al mismo tiempo perder esa llamada colocador.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=330820&siteId=1
Recomendado
Clasificación