from legacy to hexagonal (an unexpected android journey)

87
From Legacy to Hexagonal (An Unexpected Android Journey ) Rubén Serrano @Akelael Lead Android Developer @RedboothHQ José Manuel Pereira @JMPergar Android Software Engineer @RedboothHQ

Upload: jose-manuel-pereira-garcia

Post on 02-Jul-2015

1.126 views

Category:

Software


0 download

DESCRIPTION

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.

TRANSCRIPT

Page 1: From Legacy to Hexagonal (An Unexpected Android Journey)

From Legacy to Hexagonal (An Unexpected Android Journey)

Rubén Serrano @Akelael Lead Android Developer @RedboothHQ

José Manuel Pereira @JMPergar Android Software Engineer @RedboothHQ

Page 2: From Legacy to Hexagonal (An Unexpected Android Journey)

Agenda

1. From Legacy Code

2. Towards Hexagonal Architecture

3. To infinity, and beyond!

Page 3: From Legacy to Hexagonal (An Unexpected Android Journey)

1. From Legacy Code

Page 4: From Legacy to Hexagonal (An Unexpected Android Journey)

Meet the team

Page 5: From Legacy to Hexagonal (An Unexpected Android Journey)
Page 6: From Legacy to Hexagonal (An Unexpected Android Journey)
Page 7: From Legacy to Hexagonal (An Unexpected Android Journey)

One dev from a contractor

Page 8: From Legacy to Hexagonal (An Unexpected Android Journey)

One dev from a contractor + one senior iOS dev

Page 9: From Legacy to Hexagonal (An Unexpected Android Journey)

One dev from a contractor + one senior iOS dev

Page 10: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 11: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 12: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 13: From Legacy to Hexagonal (An Unexpected Android Journey)

A different Android dev One dev from a contractor + one senior iOS dev + one junior iOS dev + one confused Android team

Page 14: From Legacy to Hexagonal (An Unexpected Android Journey)

Meet the code

Page 15: From Legacy to Hexagonal (An Unexpected Android Journey)
Page 16: From Legacy to Hexagonal (An Unexpected Android Journey)

Meet the problem

Page 17: From Legacy to Hexagonal (An Unexpected Android Journey)

1. We have a huge technical debt

Page 18: From Legacy to Hexagonal (An Unexpected Android Journey)

1. We have a huge technical debt

Page 19: From Legacy to Hexagonal (An Unexpected Android Journey)

HUGE

Page 20: From Legacy to Hexagonal (An Unexpected Android Journey)
Page 21: From Legacy to Hexagonal (An Unexpected Android Journey)

1. We have huge technical debt

2. We can’t stop developing new features

Page 22: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 23: From Legacy to Hexagonal (An Unexpected Android Journey)

2. Towards Hexagonal

Page 24: From Legacy to Hexagonal (An Unexpected Android Journey)

Working with legacy code

Page 25: From Legacy to Hexagonal (An Unexpected Android Journey)

Read this book

Page 26: From Legacy to Hexagonal (An Unexpected Android Journey)

What have we learnt from pizza?

Page 27: From Legacy to Hexagonal (An Unexpected Android Journey)

You just don’t eat the whole pizza at once

Page 28: From Legacy to Hexagonal (An Unexpected Android Journey)

1. Slice the big methods into small meaningful methods

Page 29: From Legacy to Hexagonal (An Unexpected Android Journey)

1. Slice the big methods into small meaningful methods

2. Identify different responsibilities and move them to other classes

Page 30: From Legacy to Hexagonal (An Unexpected Android Journey)

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)

Page 31: From Legacy to Hexagonal (An Unexpected Android Journey)

Model View Presenter

Page 32: From Legacy to Hexagonal (An Unexpected Android Journey)

In theory

Page 33: From Legacy to Hexagonal (An Unexpected Android Journey)

View

Presenter

Model

Page 34: From Legacy to Hexagonal (An Unexpected Android Journey)

Notifies events

View

Presenter

Model

Page 35: From Legacy to Hexagonal (An Unexpected Android Journey)

Requests data

View

Presenter

Model

Page 36: From Legacy to Hexagonal (An Unexpected Android Journey)

Serves data

View

Presenter

Model

Page 37: From Legacy to Hexagonal (An Unexpected Android Journey)

Change data representation

View

Presenter

Model

Page 38: From Legacy to Hexagonal (An Unexpected Android Journey)

Tells how to draw

View

Presenter

Model

Page 39: From Legacy to Hexagonal (An Unexpected Android Journey)

Layout +

Activity/Fragment

Presenter

Data +

Business logic

Page 40: From Legacy to Hexagonal (An Unexpected Android Journey)

In code (Original code…)

http://goo.gl/z5Xn2J

Page 41: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 42: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 43: From Legacy to Hexagonal (An Unexpected Android Journey)

In Code (… to MVP)

http://goo.gl/Retvli

Page 44: From Legacy to Hexagonal (An Unexpected Android Journey)

MainView & MainModel

public interface MainView { public void swaplListData(Cursor cursor);}

public interface MainModel { public void setPresenter(MainPresenter presenter); public void startLoadingData(Context context);}

Page 45: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 46: From Legacy to Hexagonal (An Unexpected Android Journey)

MainPresenterpublic class MainPresenter { private MainView mainView; private MainModel mainModel; //... public void notifyOnCreate(Context context) { mainModel.startLoadingData(context); } public void notifiyLoadedDataAvailable(Cursor cursor) { mainView.swaplListData(cursor); }}

Page 47: From Legacy to Hexagonal (An Unexpected Android Journey)

PresenterFactory

public class PresenterFactory { public static MainPresenter getMainPresenter(MainView view) { MainModel model = new MainCursorModel(); return MainPresenter.newInstance(view, model); }}

Page 48: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 49: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 50: From Legacy to Hexagonal (An Unexpected Android Journey)

Hexagonal Architecture

Page 51: From Legacy to Hexagonal (An Unexpected Android Journey)

In theory

Page 52: From Legacy to Hexagonal (An Unexpected Android Journey)

Layout +

Activity/Fragment

Presenter

Data +

Business logic

Page 53: From Legacy to Hexagonal (An Unexpected Android Journey)

Layout +

Activity/Fragment

Data domain

Business logic

Page 54: From Legacy to Hexagonal (An Unexpected Android Journey)

Layout +

Activity/Fragment

Database

Business logic

Network

Sensors

Page 55: From Legacy to Hexagonal (An Unexpected Android Journey)

Sensors

Network

Database

Layout +

Activity/Fragment

Business logic

Page 56: From Legacy to Hexagonal (An Unexpected Android Journey)

Business logic

Sensors

Network

Database

Layout +

Activity/Fragment

Page 57: From Legacy to Hexagonal (An Unexpected Android Journey)

Business logic

Port Port

Port

Port

Sensors

Network

Database

Layout +

Activity/Fragment

Page 58: From Legacy to Hexagonal (An Unexpected Android Journey)

Sensors

Network

Database

Layout +

Activity/Fragment

Adapter Adapter

Adapter

Adap

ter

Business logic

Port Port

Port

Port

Page 59: From Legacy to Hexagonal (An Unexpected Android Journey)

Sensors

Network

Database

Layout +

Activity/Fragment

Adapter Adapter

Adapter

Adap

ter

Business logic

Port Port

Port

Port

Page 60: From Legacy to Hexagonal (An Unexpected Android Journey)

Sensors

Network

Database

Layout +

Activity/Fragment

Boundary Boundary

Boundary

Boun

dary

Business logic

Port Port

Port

Port

Page 61: From Legacy to Hexagonal (An Unexpected Android Journey)

Module Core

Module App

Module App

Module App

Module App

Core Core

Core

Core

App App

App

App

Page 62: From Legacy to Hexagonal (An Unexpected Android Journey)

In code (Hexagonal)

http://goo.gl/lIyH0o

Page 63: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 64: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 65: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 66: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 67: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 68: From Legacy to Hexagonal (An Unexpected Android Journey)

Pros & ConsLogic 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?

Page 69: From Legacy to Hexagonal (An Unexpected Android Journey)

3. To infinity, and beyond!

Page 70: From Legacy to Hexagonal (An Unexpected Android Journey)

One plugin, N commands

Page 71: From Legacy to Hexagonal (An Unexpected Android Journey)

Sensors

Network

Database

Layout +

Activity/Fragment

Boundary Boundary

Boundary

Boun

dary

Business logic

Port Port

Port

Port

Page 72: From Legacy to Hexagonal (An Unexpected Android Journey)

Model Plugin

Layout +

Activity/Fragment

Boundary BoundaryBusiness logic

Port Port

Page 73: From Legacy to Hexagonal (An Unexpected Android Journey)

Create task

Send chat message

Update note

Request projects

Business logic

Port Model Plugin

Boundary

Page 74: From Legacy to Hexagonal (An Unexpected Android Journey)

Asynchrony?

Page 75: From Legacy to Hexagonal (An Unexpected Android Journey)

Create task

Send chat message

Update note

Request projects

Business logic

Port Model Plugin

Boundary

Page 76: From Legacy to Hexagonal (An Unexpected Android Journey)

We don’t like:

callback’s hell + AsyncTasks

Page 77: From Legacy to Hexagonal (An Unexpected Android Journey)

We don’t like:

callback’s hell + AsyncTasks

We don’t mind (but could be a problem):

only commands in separate threads

Page 78: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 79: From Legacy to Hexagonal (An Unexpected Android Journey)

Use cases

Page 80: From Legacy to Hexagonal (An Unexpected Android Journey)

Model Plugin

Layout +

Activity/Fragment

Boundary BoundaryBusiness logic

Port Port

Page 81: From Legacy to Hexagonal (An Unexpected Android Journey)

Model Plugin

Layout +

Activity/Fragment

PresenterUse

Case

Model Plugin

Use Case

Repo

sito

ryModel Plugin

Use Case

Page 82: From Legacy to Hexagonal (An Unexpected Android Journey)

Conclusions

photo by Daniel Sancho

Page 83: From Legacy to Hexagonal (An Unexpected Android Journey)

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

Page 84: From Legacy to Hexagonal (An Unexpected Android Journey)

How?

1. Simple refactors

2. Model View Presenter

3. Hexagonal

Page 85: From Legacy to Hexagonal (An Unexpected Android Journey)

How?

1. Simple refactors

2. Model View Presenter

3. Hexagonal

4. Clean Architecture

Page 86: From Legacy to Hexagonal (An Unexpected Android Journey)

Thank you!

Page 87: From Legacy to Hexagonal (An Unexpected Android Journey)

Questions?