From Legacy to Hexagonal (Codemotion Madrid 2014)

32606a585aa06b1c55d55cb113545db0?s=47 Rubén Serrano
November 24, 2014
330

From Legacy to Hexagonal (Codemotion Madrid 2014)

32606a585aa06b1c55d55cb113545db0?s=128

Rubén Serrano

November 24, 2014
Tweet

Transcript

  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. Agenda 1. From Legacy Code 2. Towards Hexagonal Architecture 3.

    To infinity, and beyond!
  3. 1. From Legacy Code

  4. Meet the team

  5. None
  6. None
  7. One dev from a contractor

  8. One dev from a contractor + one senior iOS dev

  9. One dev from a contractor + one senior iOS dev

  10. One dev from a contractor + one senior iOS dev

    + one junior iOS dev
  11. One dev from a contractor + one senior iOS dev

    + one junior iOS dev
  12. A different Android dev One dev from a contractor +

    one senior iOS dev + one junior iOS dev
  13. A different Android dev One dev from a contractor +

    one senior iOS dev + one junior iOS dev + one confused Android team
  14. Meet the code

  15. None
  16. Meet the problem

  17. 1. We have a huge technical debt

  18. 1. We have a huge technical debt

  19. HUGE

  20. None
  21. 1. We have huge technical debt 2. We can’t stop

    developing new features
  22. 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
  23. 2. Towards Hexagonal

  24. Working with legacy code

  25. Read this book

  26. What have we learnt from pizza?

  27. You just don’t eat the whole pizza at once

  28. 1. Slice the big methods into small meaningful methods

  29. 1. Slice the big methods into small meaningful methods 2.

    Identify different responsibilities and move them to other classes
  30. 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)
  31. Model View Presenter

  32. In theory

  33. View Presenter Model

  34. Notifies events View Presenter Model

  35. Requests data View Presenter Model

  36. Serves data View Presenter Model

  37. Change data representation View Presenter Model

  38. Tells how to draw View Presenter Model

  39. Layout + Activity/ Fragment Presenter Data + Business logic

  40. In code (Original code…) http://goo.gl/z5Xn2J

  41. listAdapter =
 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); MainFragment
  42. 
 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
  43. In Code (… to MVP) http://goo.gl/Retvli

  44. MainView & MainModel public interface MainView {
 public void swaplListData(Cursor

    cursor);
 } public interface MainModel {
 public void setPresenter(MainPresenter presenter);
 public void startLoadingData(Context context);
 }
  45. 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);
 }
  46. 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);
 }
 }
  47. PresenterFactory public class PresenterFactory {
 public static MainPresenter getMainPresenter(MainView view)

    {
 MainModel model = new MainCursorModel();
 return MainPresenter.newInstance(view, model);
 }
 }
  48. 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);
 }
 }
 }
  49. 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
  50. Hexagonal Architecture

  51. In theory

  52. Layout + Activity/ Fragment Presenter Data + Business logic

  53. Layout + Activity/ Fragment Data domain Business logic

  54. Layout + Activity/ Fragment Database Business logic Network Sensors

  55. Sensors Network Database Layout + Activity/ Fragment Business logic

  56. Business logic Sensors Network Database Layout + Activity/ Fragment

  57. Business logic Port Port Port Port Sensors Network Database Layout

    + Activity/ Fragment
  58. Sensors Network Database Layout + Activity/ Fragment Adapter Adapter Adapter

    Adapter Business logic Port Port Port Port
  59. Sensors Network Database Layout + Activity/ Fragment Adapter Adapter Adapter

    Adapter Business logic Port Port Port Port
  60. Sensors Network Database Layout + Activity/ Fragment Boundary Boundary Boundary

    Boundary Business logic Port Port Port Port
  61. Module Core Module App Module App Module App Module App

    Core Core Core Core App App App App
  62. In code (Hexagonal) http://goo.gl/lIyH0o

  63. 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();
 }
  64. 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);
 }
  65. 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);
 }
  66. 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);
 }
  67. 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;
 }
  68. 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?
  69. 3. To infinity, and beyond!

  70. One plugin, N commands

  71. Sensors Network Database Layout + Activity/ Fragment Boundary Boundary Boundary

    Boundary Business logic Port Port Port Port
  72. Model Plugin Layout + Activity/ Fragment Boundary Boundary Business logic

    Port Port
  73. Create task Send chat message Update note Request projects Business

    logic Port Model Plugin Boundary
  74. Asynchrony?

  75. Create task Send chat message Update note Request projects Business

    logic Port Model Plugin Boundary
  76. We don’t like: callback’s hell + AsyncTasks

  77. We don’t like: callback’s hell + AsyncTasks We don’t mind

    (but could be a problem): only commands in separate threads
  78. 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
  79. Use cases

  80. Model Plugin Layout + Activity/ Fragment Boundary Boundary Business logic

    Port Port
  81. Model Plugin Layout + Activity/ Fragment Presenter Use Case Model

    Plugin Use Case Repository Model Plugin Use Case
  82. Conclusions photo by Daniel Sancho

  83. 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
  84. How? 1. Simple refactors 2. Model View Presenter 3. Hexagonal

  85. How? 1. Simple refactors 2. Model View Presenter 3. Hexagonal

    4. Clean Architecture
  86. Thank you!

  87. Questions?