Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

From Legacy to Hexagonal (An Unexpected Android Journey)

3,339 views

Published on

Esta charla comprende las lecciones aprendidas convirtiendo la app de Android de Teambox (una app repleta de deuda técnica y con un alto nivel de acoplamiento entre clases), en la versión actual de Redbooth, que intenta cumplir la arquitectura Hexagonal y los principios SOLID. Durante la exposición explicaremos como fuimos desenredando el código paso a paso; como aplicamos por partes los conceptos de la arquitectura hexagonal; como dejamos de lado componentes del framework de Android que dificultaban el mantenimiento de la app; y que errores cometimos, como los solucionamos y como se podrían haber evitado.

Published in: Software

From Legacy to Hexagonal (An Unexpected Android Journey)

  1. 1. From Legacy to Hexagonal (An Unexpected Android Journey) Rubén Serrano @Akelael Lead Android Developer @RedboothHQ José Manuel Pereira @JMPergar Android Software Engineer @RedboothHQ
  2. 2. Agenda 1. From Legacy Code 2. Towards Hexagonal Architecture 3. To infinity, and beyond!
  3. 3. 1. From Le gacy Code
  4. 4. Meet the team
  5. 5. One dev from a contractor
  6. 6. One dev from a contractor + one senior iOS dev
  7. 7. One dev from a contractor + one senior iOS dev
  8. 8. One dev from a contractor + one senior iOS dev + one junior iOS dev
  9. 9. One dev from a contractor + one senior iOS dev + one junior iOS dev
  10. 10. A different Android dev One dev from a contractor + one senior iOS dev + one junior iOS dev
  11. 11. A different Android dev One dev from a contractor + one senior iOS dev + one junior iOS dev + one confused Android team
  12. 12. Meet the code
  13. 13. Meet the problem
  14. 14. 1. We have a huge technical debt
  15. 15. 1. We have a huge technical debt
  16. 16. HUGE
  17. 17. 1. We have huge technical debt 2. We can’t stop developing new features
  18. 18. 1. We have huge technical debt 2. We can’t stop developing new features 3. We can’t remove debt at this point and we shouldn’t add any more
  19. 19. 2. Towards Hexagonal
  20. 20. Working with legacy code
  21. 21. Read this book
  22. 22. What have we learnt from pizza?
  23. 23. You just don’t eat the whole pizza at once
  24. 24. 1. Slice the big methods into small meaningful methods
  25. 25. 1. Slice the big methods into small meaningful methods 2. Identify different responsibilities and move them to other classes
  26. 26. 1. Slice the big methods into small meaningful methods 2. Identify different responsibilities and move them to other classes 3. Use less coupled framework components (or no components at all)
  27. 27. Model View Presenter
  28. 28. In theory
  29. 29. View Presenter Model
  30. 30. Notifies events View Presenter Model
  31. 31. Requests data View Presenter Model
  32. 32. Serves data View Presenter Model
  33. 33. Change data representation View Presenter Model
  34. 34. View Tells how to draw Presenter Model
  35. 35. Layout + Activity/ Fragment Presenter Data + Business logic
  36. 36. In code (Original code…) http://goo.gl/z5Xn2J
  37. 37. listAdapter = MainFragment new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1, null, new String[]{FakeDatabase.COLUMN_NAME}, new int[]{android.R.id.text1}, 0); listView.setAdapter(listAdapter);
  38. 38. getLoaderManager().initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return new CursorLoader(getActivity(), MainContentProvider.URI, null, null, null, null); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { listAdapter.swapCursor(data); } @Override public void onLoaderReset(Loader<Cursor> loader) { listAdapter.swapCursor(null); } }); MainFragment
  39. 39. In Code (… to MVP) http://goo.gl/Retvli
  40. 40. MainView & MainModel public interface MainView { public void swaplListData(Cursor cursor); } public interface MainModel { public void setPresenter(MainPresenter presenter); public void startLoadingData(Context context); }
  41. 41. MainFragment public class MainFragment extends Fragment implements MainView { //... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); presenter = PresenterFactory.getMainPresenter(this); } @Override public void onActivityCreated(Bundle savedInstanceState) { //... presenter.notifyOnCreate(getActivity()); } @Override public void swaplListData(Cursor cursor) { listAdapter.swapCursor(cursor); }
  42. 42. MainPresenter public class MainPresenter { private MainView mainView; private MainModel mainModel; //... public void notifyOnCreate(Context context) { mainModel.startLoadingData(context); } public void notifiyLoadedDataAvailable(Cursor cursor) { mainView.swaplListData(cursor); } }
  43. 43. PresenterFactory public class PresenterFactory { public static MainPresenter getMainPresenter(MainView view) { MainModel model = new MainCursorModel(); return MainPresenter.newInstance(view, model); } }
  44. 44. MainCursorModel public class MainCursorModel implements MainModel { //... @Override public void startLoadingData(Context context) { new LoadDataAsyncTask().execute(context); } private class LoadDataAsyncTask extends AsyncTask<Context, Void, Cursor > { //... @Override protected void onPostExecute(Cursor result) { super.onPostExecute(result); presenter.notifiyLoadedDataAvailable(result); } } }
  45. 45. Pros & Cons View decoupled from model Cleaner code and smaller fragment/activities View and model not really decoupled (cursor) All the components use the framework
  46. 46. Hexagonal Architecture
  47. 47. In theory
  48. 48. Layout + Activity/ Fragment Presenter Data + Business logic
  49. 49. Layout + Activity/ Fragment Data domain Business logic
  50. 50. Layout + Activity/ Fragment Database Business logic Network Sensors
  51. 51. Database Network Sensors Layout + Activity/ Fragment Business logic
  52. 52. Business logic Sensors Network Database Layout + Activity/ Fragment
  53. 53. Port Business logic Port Port Port Sensors Network Database Layout + Activity/ Fragment
  54. 54. Adapter Port Business logic Port Port Sensors Network Database Layout + Activity/ Fragment Adapter Adapter Port Adapter
  55. 55. Adapter Port Business logic Port Port Sensors Network Database Layout + Activity/ Fragment Adapter Adapter Port Adapter
  56. 56. Boundary Port Business logic Port Port Sensors Network Database Layout + Activity/ Fragment Boundary Boundary Port Boundary
  57. 57. App Core App App Module Core Module App Module App Module App Module App Core Core Core App
  58. 58. In code (Hexagonal) http://goo.gl/lIyH0o
  59. 59. MainFragment public class MainFragment extends Fragment { //... private MainFragmentBoundary viewBoundary; @Override public void onCreate(Bundle savedInstanceState) { //... viewBoundary = MainFragmentBoundary.newInstance(this); } @Override public void onActivityCreated(Bundle savedInstanceState) { //... viewBoundary.notifyOnCreate(); }
  60. 60. MainFragment public void setListAdapter() { listAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, new ArrayList<String>(0)); listView.setAdapter(listAdapter); } public void swapList(List<String> names) { listAdapter.clear(); listAdapter.addAll(names); }
  61. 61. MainFragmentBoundary public class MainFragmentBoundary implements MainViewPort { private MainFragment mainFragment; private MainLogic logic; //... public void notifyOnCreate() { logic.notifyOnCreate(); } @Override public void swaplListData(List<String> names) { mainFragment.swapList(names); }
  62. 62. MainModelBoundary public class MainModelBoundary implements MainModelPort { private MainLogic logic; private MainRepository repository; //... @Override public void startLoadingData() { repository.startLoadingData(new MainRepository.OnDataLoadedListener() { @Override public void onDataLodaded(Cursor cursor) { notifyDataLoaded(cursor); } }); } private void notifyDataLoaded(Cursor cursor) { List<String> names = mapCursorToList(cursor); logic.notifiyLoadedDataAvailable(names); }
  63. 63. MainModelBoundary private List<String> mapCursorToList(Cursor cursor) { List<String> names = new ArrayList<String>(); int nameColumnIndex = cursor.getColumnIndex(FakeDatabase.COLUMN_NAME); while (cursor.moveToNext()) { String name = cursor.getString(nameColumnIndex); names.add(name); } return names; }
  64. 64. Pros & Cons Logic is not going to be affected by framework changes Logic is pure Java: easier to test Less changes when replacing a plugin Easier for 2 devs to work on the same feature More complex architecture Need to map each POJO for each layer What happens when the plugins need to cooperate?
  65. 65. 3. To infinity, and beyond!
  66. 66. One plugin, N commands
  67. 67. Boundary Port Business logic Port Port Sensors Network Database Layout + Activity/ Fragment Boundary Boundary Port Boundary
  68. 68. Model Plugin Layout + Activity/ Fragment Boundary Business Boundary logic Port Port
  69. 69. Create task Send chat message Update note Request projects Business logic Port Model Plugin Boundary
  70. 70. Asynchrony?
  71. 71. Create task Send chat message Update note Request projects Business logic Port Model Plugin Boundary
  72. 72. We don’t like: callback’s hell + AsyncTasks
  73. 73. We don’t like: callback’s hell + AsyncTasks We don’t mind (but could be a problem): only commands in separate threads
  74. 74. We don’t like: callback’s hell + AsyncTasks We don’t mind (but could be a problem): only commands in separate threads We would love: RxJava
  75. 75. Use cases
  76. 76. Model Plugin Layout + Activity/ Fragment Boundary Business Boundary logic Port Port
  77. 77. Model Plugin Layout + Activity/ Fragment Presenter Use Case Model Plugin Use Case Repository Model Plugin Use Case
  78. 78. Conclusions photo by Daniel Sancho
  79. 79. When? • If you expect 2+ devs working on the same feature • Unless you are sure the app is going to die in a near future • You know for sure you will change your plugins
  80. 80. How? 1. Simple refactors 2. Model View Presenter 3. Hexagonal
  81. 81. How? 1. Simple refactors 2. Model View Presenter 3. Hexagonal 4. Clean Architecture
  82. 82. Thank you!
  83. 83. Questions?

×