Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Usando listeners…

Slide 6

Slide 6 text

Problemas… • Sincronizar múltiples llamadas?

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

• 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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

• 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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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 :-("));

Slide 33

Slide 33 text

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 :-("));

Slide 34

Slide 34 text

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 :-("));

Slide 35

Slide 35 text

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 :-("));

Slide 36

Slide 36 text

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

The end!

Slide 39

Slide 39 text

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