code review java – Divisor de Java 8 para resultados paginados

Pregunta:

Resumen: estoy usando una API que devuelve resultados paginados. Quiero tener estos resultados como Java 8 Stream e implementé un Spliterator para este propósito.


Estoy usando la API de AWS S3 Java para enumerar objetos en el depósito S3. La API devuelve resultados paginados : cuando llamo a client.listObjects(bucketName, rootKey) por primera vez, obtengo una instancia de ObjectListing que puede estar completa o truncada, es decir, devolver solo una "página" de resultados.

Si ObjectListing está truncado, tengo que solicitar más "página" a través de client.listNextBatchOfObjects(objectListing) (proporcionando la "página" actual como marcador) y así sucesivamente hasta que obtengo un ObjectListing que no está truncado.

Quiero usar las API de Java 8 Stream para trabajar con ObjectListing s. Idealmente, quiero ocultar las páginas de consulta de ObjectListing s detrás de alguna instalación que solo me daría un Stream<ObjectListing> . Para esto, he implementado un Spliterator<ObjectListing> :

import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Consumer;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectListing;

public class ObjectListingSpliterator implements Spliterator<ObjectListing> {

    private final AmazonS3 client;
    private ObjectListing objectListing;
    private volatile boolean split = false;
    private volatile boolean currentObjectListingWasConsumed = false;

    public ObjectListingSpliterator(AmazonS3 client, ObjectListing objectListing) {
        Objects.requireNonNull(client, "client must not be null.");
        Objects.requireNonNull(objectListing, "objectListing must not be null.");
        this.client = client;
        this.objectListing = objectListing;
    }

    @Override
    public boolean tryAdvance(Consumer<? super ObjectListing> action) {
        if (!currentObjectListingWasConsumed) {
            action.accept(objectListing);
            currentObjectListingWasConsumed = true;
            if (!split && objectListing.isTruncated()) {
                objectListing = client.listNextBatchOfObjects(objectListing);
                currentObjectListingWasConsumed = false;
            }
            return true;
        } else {
            return false;
        }
    }

    @Override
    public Spliterator<ObjectListing> trySplit() {
        if (split) {
            // Already split, cannot be split once again
            return null;
        } else if (objectListing.isTruncated()) {
            split = true;
            final ObjectListing nextObjectListing = client
                    .listNextBatchOfObjects(objectListing);
            return new ObjectListingSpliterator(client, nextObjectListing);
        } else {
            return null;
        }
    }

    @Override
    public long estimateSize() {
        if (objectListing.isTruncated()) {
            return Long.MAX_VALUE;
        } else {
            return currentObjectListingWasConsumed ? 0 : 1;
        }
    }

    @Override
    public int characteristics() {
        return Spliterator.ORDERED | Spliterator.IMMUTABLE;
    }
}

Ejemplo de uso: enumere todas las claves del depósito y la clave raíz:

// Get the original objectListing
ObjectListing objectListing = client.listObjects(bucketName,
    rootKey);
// Create a stream of objectListings
Stream<ObjectListing> objectListings = StreamSupport.stream(
    new ObjectListingSpliterator(client, objectListing), false);

List<String> keys = objectListings
    .map(ObjectListing::getObjectSummaries)
    .flatMap(Collection::stream)
    .map(S3ObjectSummary::getKey);

Lo que me interesa:

  • ¿Está bien mi spliterator?
  • No estoy seguro de si podría haber un problema de concurrencia entre tryAdvance y trySplit .
  • No estoy seguro de qué characteristics deberían tener.

La pregunta en realidad no es específica de la API de AWS, generalmente se trata de un resultado paginado de transmisión donde:

  • hay alguna forma de recuperar la primera página;
  • con cada página sabes si está completa o no;
  • puede obtener la página siguiente usando la página anterior.

Respuesta:

En primer lugar, dividir el spliterator solo tiene sentido si tanto el resto del spliterator actual como el devuelto todavía tienen trabajo pendiente. En su caso, esto (casi) no es cierto, ya que opera en los lotes completos y el spliterator actual como máximo devuelve su lote actual después de la división. Por lo tanto, reemplazaría trySplit () con un simple retorno nulo. Esto también aborda cualquier problema potencial de concurrencia (que no he analizado en profundidad).

Básicamente, las características le dicen a la persona que llama las, a falta de una palabra mejor, las características de su separador. 🙂

Creo que para el enfoque por lotes que toma, ORDERED, NONNULL, INMUTABLE debería estar bien.

Aparte de eso, para una utilidad más directa, prefiero adoptar el enfoque de no iterar sobre los lotes, sino sobre su contenido, es decir, crear un Spliterator que se inicializa con el primer lote de ObjectListing y luego pasa de forma transparente a través del elemento de colecciones subyacente. sabio y busca el siguiente lote según sea necesario. Esto eliminaría la necesidad de flatMap en el flujo de resultados y se sentiría más natural para un enfoque de transmisión. (De hecho, esto suena tan útil que me gustaría tenerlo :-))

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top

web tasarım