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

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

Embed Size (px)

Citation preview

Page 1: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Concurrency with

Promise Style

Rayco ArañaSoftware Engineer at [email protected]

Page 2: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

¿De qué vamos a hablar?

• El patrón Promise• Promesas vs Listeners• Paralelizar sin morir en el intento• Algo de reactive programming… o no

Page 3: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Usando listeners…

public class FileManager {

public void read(File jsonFile, OnFileReadListener listener) { ... }

}

Page 4: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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) { ... }

});

}

Page 5: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Usando listeners…

Page 6: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Problemas…

• Sincronizar múltiples llamadas?

Page 7: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Problemas…

• Sincronizar múltiples llamadas?• Implementación síncrona?

Page 8: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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 { ... } }

}

Page 9: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Problemas…

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

Page 10: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Problemas…

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

Page 11: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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

Page 12: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Promise style…

public void foo() {

FileManager fileManager = ...; //Initialize Promise promise = fileManager.read(new File("config.json"));

}

public class FileManager {

public Promise read(File jsonFile) { ... }

}

Page 13: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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

Page 14: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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.

Page 15: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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

Page 16: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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

Page 17: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

jDeferred. Flujo

Hilo 2

create

Cliente API DeferredObject

init

attachCallbacks

resolve()

executeCallbacks

read()

Page 18: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• Vamos a implementar una API sencilla

jDeferred. Caso práctico I

public class FileManager {

public Promise<JSONObject, Exception, Void> read(File jsonFile) { ... }

}

Page 19: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• Creamos un DeferredObject• Realizamos la operación en background• Devolvemos la promesa

jDeferred. Caso práctico II

public Promise<JSONObject, Exception, Void> read(File jsonFile) { DeferredObject<JSONObject, Exception, Void> deferredObject = new DeferredObject<>(); doReadAsync(jsonFile, deferredObject); return deferredObject.promise();}

Page 20: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• 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<JSONObject, Exception, Void> deferredObject) { threadPool.execute(new Runnable() { @Override public void run() { doTryRead(jsonFile, deferredObject); } });}

Page 21: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• Tratamos el caso de error e implementamos la lógica

jDeferred. Caso práctico IV

private void doTryRead(File jsonFile, DeferredObject<JSONObject, Exception, Void> 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;}

Page 22: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• Usando la API que hemos creado

jDeferred. Caso práctico V

FileManager fileManager = new FileManager();fileManager.read(new File("config.json")) .done(new DoneCallback<JSONObject>() { @Override public void onDone(JSONObject result) { config.init(result); view.showMessage("Config loaded."); } }).fail(new FailCallback<Exception>() { @Override public void onFail(Exception ex) { config.initWithDefaults(); view.showMessage("Default config loaded."); } });

Page 23: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• 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."); });

Page 24: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

jDeferred. Caso práctico VI

• Mocking de forma sencilla

private void givenSomeValidConfigFile() { FileManager fileManager = mock(FileManager.class);

JSONObject jsonObject = ...//Create the mock config DeferredObject<JSONObject, Exception, Void> deferredObject = new DeferredObject<>();

when(fileManager.read(any(File.class))).thenReturn(deferredObject.resolve(jsonObject));}

Page 25: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Flexibilidad

• Implementación síncrona de la API muy sencilla

private Cache cache = new Cache(); //Some cacheprivate FileManager networkFileManager = new NetworkFileManager();

public Promise<JSONObject, Exception, Void> read(File jsonFile) { JSONObject jsonObject = cache.get(jsonFile); if (jsonObject != null) { return new DeferredObject().resolve(jsonObject); } else { return networkFileManager.read(jsonFile); //Delegate }}

Page 26: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• ¿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

Page 27: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• 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

Page 28: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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; }

}

Page 29: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

jDeferred en Android

• Adios Handlers!

FileManager fileManager = new FileManager();fileManager.read(new File("config.json")) .done(new BackgroundDoneCallback<JSONObject>() { @Override public void onDone(JSONObject result) { config.init(result); } }).done(new UIDoneCallback<JSONObject>() { @Override public void onDone(JSONObject result) { view.showMessage("Config loaded."); } });

Page 30: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• 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

Page 31: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

• 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<MultipleResults>() { @Override public void onDone(MultipleResults results) { for (OneResult result : results) { view.showJSON((JSONObject) result.getResult()); } view.showMessage("All files loaded!"); } });

Page 32: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Filters

• Nos permiten manipular o transformar una promesa

deferredManager.when(configPromise, serverPromise, userPromise) .then(new DoneFilter<MultipleResults, Config>() { @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 :-("));

Page 33: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Filters

• Se puede aplicar a cualquier tipo de promesa

fileManager.read(new File("config.json")) .then(new DoneFilter<JSONObject, Config>() { @Override public Config filterDone(JSONObject result) { return new Config(result); } }) .done(result -> view.showConfig(result)) .fail(ex -> view.showMessage("Config not loaded :-("));

Page 34: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Pipes

• Más potente que los filtros• Transformar con posibilidad de errores

fileManager.read(new File("config.json")) .then(new DonePipe<JSONObject, Config, Exception, Void>() { @Override public Promise<Config, Exception, Void> pipeDone(JSONObject result) { DeferredObject<Config, Exception, Void> 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 :-("));

Page 35: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Pipes

• Pero también encadenar operaciones

fileManager.read(new File("user/server.json")) .then(new DonePipe<JSONObject, JSONObject, Exception, Void>() { @Override public Promise<JSONObject, Exception, Void> 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 :-("));

Page 36: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Page 37: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

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.

Page 38: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

The end!

Page 39: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

We are hiring!

• ¿Quieres unirte a nosotros?

• Buscas un ambiente creativo

• Grandes desafíos

• Desarrollar productos que cambienla vida de millones de personas

Envíanos tu CV!

Page 40: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

Tuenti challenge 5

Page 41: Concurrency with Promise Style Rayco Araña Software Engineer at Tuenti rayco@tuenti.com

¿Preguntas?

• jDeferred• http://jdeferred.org

• Mi blog personal• http://raycoarana.com

• Tuenti Developers Blog• http://corporate.tuenti.com/es/dev/blog

Rayco ArañaSoftware Engineer at Tuenti@[email protected]