Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Concurrency with Promise Style

Tuenti
April 25, 2015

Concurrency with Promise Style

Rayco Araña (Tuenti Android Engineer) introduced how to manage concurrency using Promises at Droidcon Spain 2015

Tuenti

April 25, 2015
Tweet

More Decks by Tuenti

Other Decks in Programming

Transcript

  1. Concurrency
    with
    Promise Style
    Rayco Araña
    Software Engineer at Tuenti
    [email protected]

    View Slide

  2. ¿De qué vamos a hablar?
    • El patrón Promise
    • Promesas vs Listeners
    • Paralelizar sin morir en el intento
    • Algo de reactive programming… o no

    View Slide

  3. Usando listeners…
    public class FileManager {
    public void read(File jsonFile, OnFileReadListener listener) {
    ...
    }
    }

    View Slide

  4. Usando listeners…
    public void foo() {
    FileManager fileManager = ...; //Initialize
    fileManager.read(new File("config.json"), new OnFileReadListener() {
    public void onFileRead(JSONObject jsonFile) {
    ...
    }
    public void onError(Exception ex) {
    ...
    }
    });
    }

    View Slide

  5. Usando listeners…

    View Slide

  6. Problemas…
    • Sincronizar múltiples llamadas?

    View Slide

  7. Problemas…
    • Sincronizar múltiples llamadas?
    • Implementación síncrona?

    View Slide

  8. Problemas…
    • La pila de ejecución queda contaminada
    public class CacheFileManager implements FileManager {
    private Cache cache;
    public void read(File jsonFile, OnFileReadListener listener) {
    JSONObject object = cache.get(jsonFile);
    if(object != null) {
    listener.onFileRead(object);
    } else {
    ...
    }
    }
    }

    View Slide

  9. Problemas…
    • Sincronizar múltiples llamadas?
    • Implementación síncrona?
    • En qué hilo se ejecuta el Listener?

    View Slide

  10. Problemas…
    • Sincronizar múltiples llamadas?
    • Implementación síncrona?
    • En qué hilo se ejecuta el Listener?
    • Es esto “clean code”?

    View Slide

  11. Problemas…
    Output arguments are counterintuitive. Readers expect
    arguments to be inputs, not outputs. If your function must
    change the state of something, have it change the state of the
    object it is called on.
    Robert C. Martin

    View Slide

  12. Promise style…
    public void foo() {
    FileManager fileManager = ...; //Initialize
    Promise promise = fileManager.read(new File("config.json"));
    }
    public class FileManager {
    public Promise read(File jsonFile) {
    ...
    }
    }

    View Slide

  13. Patrón Promise
    Objeto que hace de proxy con el resultado
    de una operación
    El resultado suele ser desconocido debido a que su cálculo aún no ha
    acabado

    View Slide

  14. Patrón Promise. Características
    • Código más legible
    • Concurrencia más sencilla
    • Tests más sencillos
    • Sin ArgumentCaptors para capturar callbacks que luego estimular.

    View Slide

  15. jDeferred
    • Implementación Java del patrón Promise
    • Inspirado en Deferred Object de jQuery
    • Soporte específico para Android
    • Código muy compacto con Lambdas en Java 8
    • http://jdeferred.org

    View Slide

  16. jDeferred. Claves
    • Promise : interface
    • Promesa que recibes al realizar una operación.
    • DeferredObject : class
    • Objeto con el que controlas el estado de la promesa
    • AndroidDeferredObject
    • DeferredManager : interface
    • Gestor de promesas
    • DefaultDeferredManager implementación por defecto
    • AndroidDefererdManager

    View Slide

  17. jDeferred. Flujo
    Hilo 2
    create
    Cliente API DeferredObject
    init
    attachCallbacks
    resolve()
    executeCallbacks
    read()

    View Slide

  18. • Vamos a implementar una API sencilla
    jDeferred. Caso práctico I
    public class FileManager {
    public Promise read(File jsonFile) {
    ...
    }
    }

    View Slide

  19. • Creamos un DeferredObject
    • Realizamos la operación en background
    • Devolvemos la promesa
    jDeferred. Caso práctico II
    public Promise read(File jsonFile) {
    DeferredObject deferredObject = new DeferredObject<>();
    doReadAsync(jsonFile, deferredObject);
    return deferredObject.promise();
    }

    View Slide

  20. • El trabajo lo dejamos para que lo ejecute un ThreadPool
    jDeferred. Caso práctico III
    private Executor threadPool = Executors.newSingleThreadExecutor(); //Whatever thread pool
    private void doReadAsync(File jsonFile,
    DeferredObject deferredObject) {
    threadPool.execute(new Runnable() {
    @Override
    public void run() {
    doTryRead(jsonFile, deferredObject);
    }
    });
    }

    View Slide

  21. • Tratamos el caso de error e implementamos la lógica
    jDeferred. Caso práctico IV
    private void doTryRead(File jsonFile,
    DeferredObject deferredObject) {
    try {
    deferredObject.resolve(doRead(jsonFile));
    } catch (Exception ex) {
    deferredObject.reject(ex);
    }
    }
    private JSONObject doRead(File jsonFile) throws IOException, ParseException {
    JSONObject jsonObject = ...; //Read the file
    return jsonObject;
    }

    View Slide

  22. • Usando la API que hemos creado
    jDeferred. Caso práctico V
    FileManager fileManager = new FileManager();
    fileManager.read(new File("config.json"))
    .done(new DoneCallback() {
    @Override
    public void onDone(JSONObject result) {
    config.init(result);
    view.showMessage("Config loaded.");
    }
    }).fail(new FailCallback() {
    @Override
    public void onFail(Exception ex) {
    config.initWithDefaults();
    view.showMessage("Default config loaded.");
    }
    });

    View Slide

  23. • Con Java 8 queda aún más compacto
    jDeferred. Caso práctico V
    FileManager fileManager = new FileManager();
    fileManager.read(new File("config.json"))
    .done((result) -> {
    config.init(result);
    view.showMessage("Config loaded.");
    }).fail((ex) -> {
    config.initWithDefaults();
    view.showMessage("Default config loaded.");
    });

    View Slide

  24. jDeferred. Caso práctico VI
    • Mocking de forma sencilla
    private void givenSomeValidConfigFile() {
    FileManager fileManager = mock(FileManager.class);
    JSONObject jsonObject = ...//Create the mock config
    DeferredObject deferredObject = new DeferredObject<>();
    when(fileManager.read(any(File.class))).thenReturn(deferredObject.resolve(jsonObject));
    }

    View Slide

  25. Flexibilidad
    • Implementación síncrona de la API muy sencilla
    private Cache cache = new Cache(); //Some cache
    private FileManager networkFileManager = new NetworkFileManager();
    public Promise read(File jsonFile) {
    JSONObject jsonObject = cache.get(jsonFile);
    if (jsonObject != null) {
    return new DeferredObject().resolve(jsonObject);
    } else {
    return networkFileManager.read(jsonFile); //Delegate
    }
    }

    View Slide

  26. • ¿En qué hilo se ejecuta el Listener? ¿UI o Background?
    • UI
    • Cosas en el hilo de UI que no queremos
    • Background
    • Cuando queremos hacer algo en UI tenemos que recurrir a Handlers
    • Problema subyacente
    • Responsabilidad recae en la API: Bad idea 
    • Es el cliente de la API quien debe decidir esto
    Listeners en Android

    View Slide

  27. • AndroidDeferredObject
    • Wrapper de DeferredObject para añadir soporte para Android
    • Nuevos callbacks
    • AndroidDoneCallback
    • AndroidFailCallback
    • AndroidProgressCallback
    • AndroidAlwaysCallback
    • Método para elegir en que hilo se va a ejecutar
    • A partir de 1.2.5, se puede indicar con una anotación
    jDeferred en Android

    View Slide

  28. jDeferred en Android
    • Consejo: Crear clases abstractas que oculten el método
    public abstract class UIDoneCallback implements AndroidDoneCallback {
    public AndroidExecutionScope getExecutionScope() {
    return AndroidExecutionScope.UI;
    }
    }
    public abstract class BackgroundDoneCallback implements AndroidDoneCallback {
    public AndroidExecutionScope getExecutionScope() {
    return AndroidExecutionScope.BACKGROUND;
    }
    }

    View Slide

  29. jDeferred en Android
    • Adios Handlers!
    FileManager fileManager = new FileManager();
    fileManager.read(new File("config.json"))
    .done(new BackgroundDoneCallback() {
    @Override
    public void onDone(JSONObject result) {
    config.init(result);
    }
    }).done(new UIDoneCallback() {
    @Override
    public void onDone(JSONObject result) {
    view.showMessage("Config loaded.");
    }
    });

    View Slide

  30. • CPUs con muchos cores
    • Algunos se apagan, pero hay un consumo mínimo
    • Usar todos de forma eficiente permite apagar la CPU antes
    • Un ThreadPool no es suficiente
    • Nos ayuda a lanzar trabajos en paralelo
    • ¿Cómo sincronizar trabajos dependientes entre si?
    • Herramientas de bajo nivel… complicadas de depurar.
    Paralelizar trabajo

    View Slide

  31. • DeferredManager. Permite lanzar y/o sincronizar promesas
    • Devuelve otra promesa
    Paralelizar trabajo
    DeferredManager deferredManager = new DefaultDeferredManager();
    Promise configPromise = fileManager.read(new File("config.json"));
    Promise serverPromise = fileManager.read(new File("server.json"));
    Promise userPromise = fileManager.read(new File("user.json"));
    deferredManager.when(configPromise, serverPromise, userPromise)
    .done(new DoneCallback() {
    @Override
    public void onDone(MultipleResults results) {
    for (OneResult result : results) {
    view.showJSON((JSONObject) result.getResult());
    }
    view.showMessage("All files loaded!");
    }
    });

    View Slide

  32. Filters
    • Nos permiten manipular o transformar una promesa
    deferredManager.when(configPromise, serverPromise, userPromise)
    .then(new DoneFilter() {
    @Override
    public Config filterDone(MultipleResults results) {
    JSONObject config = (JSONObject) results.get(0).getResult();
    JSONObject server = (JSONObject) results.get(1).getResult();
    JSONObject user = (JSONObject) results.get(2).getResult();
    return new Config(config, server, user);
    }
    })
    .done(result -> view.showConfig(result))
    .fail(ex -> view.showMessage("Config not loaded :-("));

    View Slide

  33. Filters
    • Se puede aplicar a cualquier tipo de promesa
    fileManager.read(new File("config.json"))
    .then(new DoneFilter() {
    @Override
    public Config filterDone(JSONObject result) {
    return new Config(result);
    }
    })
    .done(result -> view.showConfig(result))
    .fail(ex -> view.showMessage("Config not loaded :-("));

    View Slide

  34. Pipes
    • Más potente que los filtros
    • Transformar con posibilidad de errores
    fileManager.read(new File("config.json"))
    .then(new DonePipe() {
    @Override
    public Promise pipeDone(JSONObject result) {
    DeferredObject deferred = new DeferredObject<>();
    if (Config.isValid(result)) {
    return deferred.resolve(new Config(result));
    } else {
    return deferred.reject(new IllegalConfigException());
    }
    }
    })
    .done(result -> view.showConfig(result))
    .fail(ex -> view.showMessage("Config not loaded :-("));

    View Slide

  35. Pipes
    • Pero también encadenar operaciones
    fileManager.read(new File("user/server.json"))
    .then(new DonePipe() {
    @Override
    public Promise pipeDone(JSONObject result) {
    Server server = new Server(result);
    return fileManager.read(server.getConfigPath());
    }
    })
    .done(result -> view.showConfig(result))
    .fail(ex -> view.showMessage("User config not loaded :-("));

    View Slide


  36. View Slide

  37. jDeferred no es RxJava
    • Promesas tienen estado
    • No se puede reutilizar
    • Solo se puede llamar 1 vez a resolve() o reject()
    • A notify() si podemos llamar muchas veces
    • Para progreso, no para notificar cambios
    • Ni mejor ni peor, depende de nuestros requisitos.

    View Slide

  38. The end!

    View Slide

  39. ¿Preguntas?
    • jDeferred
    • http://jdeferred.org
    • Mi blog personal
    • http://raycoarana.com
    • Tuenti Developers Blog
    • http://corporate.tuenti.com/es/dev/blog
    Rayco Araña
    Software Engineer at Tuenti
    @raycoarana
    [email protected]

    View Slide