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

ROSIE: CLEAN USE CASE FRAMEWORK

Karumi
August 11, 2016

ROSIE: CLEAN USE CASE FRAMEWORK

During the past years we have been discussing and iterating over our mobile application architecture at Karumi, and the result of all that work is called Rosie.

Rosie is a framework that helps application development by focusing on three pillars that we consider very important: testability, code scalability y code readability.

This talk is not about promoting Rosie but rather the creation process of this framework, the needs that we needed to cover and how we materialized all the literature present in tons of books and internet articles. This is about problems and solutions

Karumi

August 11, 2016
Tweet

More Decks by Karumi

Other Decks in Technology

Transcript

  1. ROSIE: CLEAN USE CASE FRAMEWORK Jorge Juan Barroso Carmona [email protected]

    @flipper83 +JorgeJBarroso Pedro Gomez Sergio Gutierrez
  2. Sergio Gutierrez Senior Mobile Engineer Pedro Gomez Senior Mobile Engineer

    Alberto Gragera Technical Director Davide Mendolia Senior Full Stack Engineer
  3. Adam Tornhill “If Clean Architecture is about running away from

    frameworks, does a clean architecture framework make sense?”
  4. Why

  5. Activity Fragment Service View … Presenter View Use Case Rich

    Model Repository DataSource Api Retrofit SharedPref Api Sync Execution
  6. Adam Tornhill public class SampleActivity extends RosieActivity implements SamplePresenter.View {

    @Inject @Presenter SamplePresenter presenter; @Override protected void onPreparePresenter() {/*...*/} }
  7. Adam Tornhill public class SampleActivity extends RosieActivity implements SamplePresenter.View {

    @Inject @Presenter SamplePresenter presenter; @Override protected void onPreparePresenter() {/*...*/} }
  8. Adam Tornhill public class SampleActivity extends RosieActivity implements SamplePresenter.View {

    @Inject @Presenter SamplePresenter presenter; @Override protected void onPreparePresenter() {/*...*/} }
  9. Adam Tornhill public class SamplePresenter extends RosiePresenter<SamplePresenter.View> { protected void

    initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }
  10. Adam Tornhill public class SamplePresenter extends RosiePresenter<SamplePresenter.View> { protected void

    initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }
  11. Adam Tornhill public class SamplePresenter extends RosiePresenter<SamplePresenter.View> { protected void

    initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }
  12. Adam Tornhill public class SamplePresenter extends RosiePresenter<SamplePresenter.View> { protected void

    initialize() {/*...*/} protected void update() {/*...*/} protected void pause() {/*...*/} protected void destroy() {/*...*/} private void sampleMethod() { View view = getView(); // Get the view linked to the presenter view.foo(); } public interface View extends RosiePresenter.View { void foo(); }
  13. Adam Tornhill public final class PresenterLifeCycleLinker { public void initializeLifeCycle(Object

    source, RosiePresenter.View view) {
 if (source == null) {
 throw new IllegalArgumentException(
 "The source instance used to initialize the presenters can't be null");
 }
 if (view == null) {
 throw new IllegalArgumentException(
 "The view instance used to initialize the presenters can't be null");
 }
 
 addAnnotatedPresenter(source);
 setView(view);
 initializePresenters();
 }
  14. Adam Tornhill public final class PresenterLifeCycleLinker { public void initializeLifeCycle(Object

    source, RosiePresenter.View view) {
 if (source == null) {
 throw new IllegalArgumentException(
 "The source instance used to initialize the presenters can't be null");
 }
 if (view == null) {
 throw new IllegalArgumentException(
 "The view instance used to initialize the presenters can't be null");
 }
 
 addAnnotatedPresenter(source);
 setView(view);
 initializePresenters();
 }
  15. Adam Tornhill void resetView() {
 final Class<?> viewClass = getViewInterfaceClass();


    InvocationHandler emptyHandler = new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 return null;
 }
 };
 ClassLoader classLoader = viewClass.getClassLoader();
 Class[] interfaces = new Class[1];
 interfaces[0] = viewClass;
 this.view = (T) Proxy.newProxyInstance(classLoader, interfaces, emptyHandler); }
  16. Adam Tornhill void resetView() {
 final Class<?> viewClass = getViewInterfaceClass();


    InvocationHandler emptyHandler = new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 return null;
 }
 };
 ClassLoader classLoader = viewClass.getClassLoader();
 Class[] interfaces = new Class[1];
 interfaces[0] = viewClass;
 this.view = (T) Proxy.newProxyInstance(classLoader, interfaces, emptyHandler); }
  17. Adam Tornhill void resetView() {
 final Class<?> viewClass = getViewInterfaceClass();


    InvocationHandler emptyHandler = new InvocationHandler() {
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 return null;
 }
 };
 ClassLoader classLoader = viewClass.getClassLoader();
 Class[] interfaces = new Class[1];
 interfaces[0] = viewClass;
 this.view = (T) Proxy.newProxyInstance(classLoader, interfaces, emptyHandler); }
  18. Adam Tornhill public class ComicsPresenter extends RosiePresenter<ComicsPresenter.View> { public void

    getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }
  19. Adam Tornhill public class ComicsPresenter extends RosiePresenter<ComicsPresenter.View> { public void

    getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .name(“sortByDate”) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }
  20. Adam Tornhill public class ComicsPresenter extends RosiePresenter<ComicsPresenter.View> { public void

    getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .name(“sortByDate”) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }
  21. Adam Tornhill public class ComicsPresenter extends RosiePresenter<SamplePresenter.View> { public void

    getComics() { createUseCaseCall(getComics) .args(superHeroId, authorId) .name(“sortByDate”) .onSuccess(new OnSuccessCallback() { /*...*/ }) .onError(new OnErrorCallback() { /*...*/ }) .execute(); }
  22. Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name

    = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }
  23. Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name

    = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }
  24. Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name

    = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }
  25. Adam Tornhill public class GetComics extends RosieUseCase { @UseCase (name

    = “sortByDate”) public void getComicsSortedByDate(String superHeroId, String authorId) { try { Comics comics = getComics(superHeroId, authorId); notifySuccess(comics.getSortedByDate()); } catch (DataException e) { notifyError(generateDataError(e)); } } }
  26. Adam Tornhill public class UseCaseHandler {
 
 private final TaskScheduler

    taskScheduler;
 private final ErrorHandler errorHandler;
 
 public UseCaseHandler(TaskScheduler taskScheduler, ErrorHandler errorHandler) {
 this.taskScheduler = taskScheduler;
 this.errorHandler = errorHandler;
 }
  27. Adam Tornhill public class UseCaseHandler {
 
 private final TaskScheduler

    taskScheduler;
 private final ErrorHandler errorHandler;
 
 public UseCaseHandler(TaskScheduler taskScheduler, ErrorHandler errorHandler) {
 this.taskScheduler = taskScheduler;
 this.errorHandler = errorHandler;
 }
  28. Adam Tornhill public class UseCaseHandler {
 
 private final TaskScheduler

    taskScheduler;
 private final ErrorHandler errorHandler;
 
 public UseCaseHandler(TaskScheduler taskScheduler, ErrorHandler errorHandler) {
 this.taskScheduler = taskScheduler;
 this.errorHandler = errorHandler;
 }
  29. Adam Tornhill public interface TaskScheduler {
 void execute(UseCaseWrapper useCaseWrapper);
 }

    public class TaskSchedulerJobQueue implements TaskScheduler {
 private final JobManager jobManager;
 
 /**/
 
 @Override public void execute(UseCaseWrapper useCaseWrapper) {
 UseCaseWrapperJob useCaseWrapperJob = new UseCaseWrapperJob(useCaseWrapper);
 jobManager.addJobInBackground(useCaseWrapperJob);
 }
 }
  30. Adam Tornhill public interface TaskScheduler {
 void execute(UseCaseWrapper useCaseWrapper);
 }

    public class TaskSchedulerJobQueue implements TaskScheduler {
 private final JobManager jobManager;
 
 /**/
 
 @Override public void execute(UseCaseWrapper useCaseWrapper) {
 UseCaseWrapperJob useCaseWrapperJob = new UseCaseWrapperJob(useCaseWrapper);
 jobManager.addJobInBackground(useCaseWrapperJob);
 }
 }
  31. Adam Tornhill public interface TaskScheduler {
 void execute(UseCaseWrapper useCaseWrapper);
 }

    public class TaskSchedulerJobQueue implements TaskScheduler {
 private final JobManager jobManager;
 
 /**/
 
 @Override public void execute(UseCaseWrapper useCaseWrapper) {
 UseCaseWrapperJob useCaseWrapperJob = new UseCaseWrapperJob(useCaseWrapper);
 jobManager.addJobInBackground(useCaseWrapperJob);
 }
 }
  32. Adam Tornhill protected final UseCaseCall createUseCaseCall(RosieUseCase useCase) {
 UseCaseCall useCaseCall

    = new UseCaseCall(useCase, useCaseHandler);
 retainUseCaseCall(useCaseCall);
 return useCaseCall;
 }
  33. Adam Tornhill protected final UseCaseCall createUseCaseCall(RosieUseCase useCase) {
 UseCaseCall useCaseCall

    = new UseCaseCall(useCase, useCaseHandler);
 retainUseCaseCall(useCaseCall);
 return useCaseCall;
 }
  34. Adam Tornhill final class UseCaseParams {
 
 private final String

    useCaseName;
 private final Object[] args;
 private WeakReference<OnSuccessCallback> onSuccessCallback;
 private WeakReference<OnErrorCallback> onErrorCallback; /**/ }
  35. Adam Tornhill final class UseCaseParams {
 
 private final String

    useCaseName;
 private final Object[] args;
 private WeakReference<OnSuccessCallback> onSuccessCallback;
 private WeakReference<OnErrorCallback> onErrorCallback; /**/ }
  36. Repository DataSource Api Retrofit Api Model Mapper Domain Model Cache

    level DataSource Realm Realm Realm Model Mapper Use case/ rich model Populator
  37. Adam Tornhill public class SampleRepository extends RosieRepository<Key, Value> { @Inject

    public SampleRepository(SampleApiDataSource sampleApiDataSource, SampleCacheDataSource sampleCacheDataSource) { addReadableDataSources(sampleApiDataSource); addCacheDataSources(sampleCacheDataSource); } }
  38. Adam Tornhill public class SampleRepository extends RosieRepository<Key, Value> { @Inject

    public SampleRepository(SampleApiDataSource sampleApiDataSource, SampleCacheDataSource sampleCacheDataSource) { addReadableDataSources(sampleApiDataSource); addCacheDataSources(sampleCacheDataSource); } }
  39. Adam Tornhill public class SampleRepository extends RosieRepository<Key, Value> { @Inject

    public SampleRepository(SampleApiDataSource sampleApiDataSource, SampleCacheDataSource sampleCacheDataSource) { addReadableDataSources(sampleApiDataSource); addCacheDataSources(sampleCacheDataSource); } }
  40. Adam Tornhill public class SampleRepository extends RosieRepository<Key, Value> { @Inject

    public SampleRepository() { PaginatedCacheDataSource<Key, Value> inMemoryCacheDataSource = new InMemoryCacheDataSource<>(new TimeProvider(), MINUTES.toMillis(5)); addCacheDataSources(inMemoryCacheDataSource); } }
  41. Adam Tornhill Key key = /*…*/; Value value; Collection<Value> values;

    RosieRepository<Key, Value> repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();
  42. Adam Tornhill Key key = /*…*/; Value value; Collection<Value> values;

    RosieRepository<Key, Value> repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();
  43. Adam Tornhill Key key = /*…*/; Value value; Collection<Value> values;

    RosieRepository<Key, Value> repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();
  44. Adam Tornhill Key key = /*…*/; Value value; Collection<Value> values;

    RosieRepository<Key, Value> repository = /*...*/; value = repository.getByKey(key); value = repository.getByKey(key, ReadPolicy.CACHE_ONLY); values = repository.getAll(); values = repository.getAll(ReadPolicy.READABLE_ONLY); repository.addOrUpdate(value); repository.addOrUpdate(value, WritePolicy.WRITE_ONCE); repository.deleteByKey(key); repository.deleteAll();
  45. Repository DataSource Api Retrofit Api Model Mapper Domain Model Cache

    level DataSource Realm Realm Realm Model Mapper Use case/ rich model Populator
  46. Adam Tornhill public class InMemoryCacheDataSource<K, V extends Identifiable<K>> implements CacheDataSource<K,

    V> { protected final TimeProvider timeProvider; protected final long ttlInMillis; protected final Map<K, V> items; protected long lastItemsUpdate; public InMemoryCacheDataSource(TimeProvider timeProvider, long ttlInMillis) { this.timeProvider = timeProvider; this.ttlInMillis = ttlInMillis; this.items = new LinkedHashMap<>(); } @Override public synchronized V getByKey(K key) { return items.get(key); } @Override public synchronized Collection<V> getAll() { return new ArrayList<>(items.values()); }
  47. Adam Tornhill public class InMemoryCacheDataSource<K, V extends Identifiable<K>> implements CacheDataSource<K,

    V> { protected final TimeProvider timeProvider; protected final long ttlInMillis; protected final Map<K, V> items; protected long lastItemsUpdate; public InMemoryCacheDataSource(TimeProvider timeProvider, long ttlInMillis) { this.timeProvider = timeProvider; this.ttlInMillis = ttlInMillis; this.items = new LinkedHashMap<>(); } @Override public synchronized V getByKey(K key) { return items.get(key); } @Override public synchronized Collection<V> getAll() { return new ArrayList<>(items.values()); }
  48. Adam Tornhill public class InMemoryCacheDataSource<K, V extends Identifiable<K>> implements CacheDataSource<K,

    V> { protected final TimeProvider timeProvider; protected final long ttlInMillis; protected final Map<K, V> items; protected long lastItemsUpdate; public InMemoryCacheDataSource(TimeProvider timeProvider, long ttlInMillis) { this.timeProvider = timeProvider; this.ttlInMillis = ttlInMillis; this.items = new LinkedHashMap<>(); } @Override public synchronized V getByKey(K key) { return items.get(key); } @Override public synchronized Collection<V> getAll() { return new ArrayList<>(items.values()); }
  49. Adam Tornhill public Collection<V> getAll(ReadPolicy policy) throws Exception {
 Collection<V>

    values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }
  50. Adam Tornhill public Collection<V> getAll(ReadPolicy policy) throws Exception {
 Collection<V>

    values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }
  51. Adam Tornhill private Collection<V> getValuesFromCaches() throws Exception {
 Collection<V> values

    = null;
 
 for (CacheDataSource<K, V> cacheDataSource : cacheDataSources) {
 values = cacheDataSource.getAll();
 
 if (values != null) {
 if (areValidValues(values, cacheDataSource)) {
 break;
 } else {
 cacheDataSource.deleteAll();
 values = null;
 }
 }
 }
 
 return values;
 }
  52. Adam Tornhill private Collection<V> getValuesFromCaches() throws Exception {
 Collection<V> values

    = null;
 
 for (CacheDataSource<K, V> cacheDataSource : cacheDataSources) {
 values = cacheDataSource.getAll();
 
 if (values != null) {
 if (areValidValues(values, cacheDataSource)) {
 break;
 } else {
 cacheDataSource.deleteAll();
 values = null;
 }
 }
 }
 
 return values;
 }
  53. Adam Tornhill public Collection<V> getAll(ReadPolicy policy) throws Exception {
 Collection<V>

    values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }
  54. Adam Tornhill public Collection<V> getAll(ReadPolicy policy) throws Exception {
 Collection<V>

    values = null;
 
 if (policy.useCache()) {
 values = getValuesFromCaches();
 }
 
 if (values == null && policy.useReadable()) {
 values = getValuesFromReadables();
 }
 
 if (values != null) {
 populateCaches(values);
 }
 
 return values;
 }
  55. protected void populateCaches(Collection<V> values) throws Exception {
 for (CacheDataSource<K, V>

    cacheDataSource : cacheDataSources) {
 cacheDataSource.addOrUpdateAll(values);
 }
 }