From Legacy to Hexagonal (An Unexpected Android Journey)

Post on 02-Jul-2015

1.126 views 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 of 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

Agenda

1. From Legacy Code

2. Towards Hexagonal Architecture

3. To infinity, and beyond!

1. From Legacy Code

Meet the team

One dev from a contractor

One dev from a contractor + one senior iOS dev

One dev from a contractor + one senior iOS dev

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

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

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

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

Meet the code

Meet the problem

1. We have a huge technical debt

1. We have a huge technical debt

HUGE

1. We have huge technical debt

2. We can’t stop developing new features

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

2. Towards Hexagonal

Working with legacy code

Read this book

What have we learnt from pizza?

You just don’t eat the whole pizza at once

1. Slice the big methods into small meaningful methods

1. Slice the big methods into small meaningful methods

2. Identify different responsibilities and move them to other classes

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)

Model View Presenter

In theory

View

Presenter

Model

Notifies events

View

Presenter

Model

Requests data

View

Presenter

Model

Serves data

View

Presenter

Model

Change data representation

View

Presenter

Model

Tells how to draw

View

Presenter

Model

Layout +

Activity/Fragment

Presenter

Data +

Business logic

In code (Original code…)

http://goo.gl/z5Xn2J

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

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

In Code (… to MVP)

http://goo.gl/Retvli

MainView & MainModel

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

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

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

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

PresenterFactory

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

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

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

Hexagonal Architecture

In theory

Layout +

Activity/Fragment

Presenter

Data +

Business logic

Layout +

Activity/Fragment

Data domain

Business logic

Layout +

Activity/Fragment

Database

Business logic

Network

Sensors

Sensors

Network

Database

Layout +

Activity/Fragment

Business logic

Business logic

Sensors

Network

Database

Layout +

Activity/Fragment

Business logic

Port Port

Port

Port

Sensors

Network

Database

Layout +

Activity/Fragment

Sensors

Network

Database

Layout +

Activity/Fragment

Adapter Adapter

Adapter

Adap

ter

Business logic

Port Port

Port

Port

Sensors

Network

Database

Layout +

Activity/Fragment

Adapter Adapter

Adapter

Adap

ter

Business logic

Port Port

Port

Port

Sensors

Network

Database

Layout +

Activity/Fragment

Boundary Boundary

Boundary

Boun

dary

Business logic

Port Port

Port

Port

Module Core

Module App

Module App

Module App

Module App

Core Core

Core

Core

App App

App

App

In code (Hexagonal)

http://goo.gl/lIyH0o

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

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

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

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

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

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?

3. To infinity, and beyond!

One plugin, N commands

Sensors

Network

Database

Layout +

Activity/Fragment

Boundary Boundary

Boundary

Boun

dary

Business logic

Port Port

Port

Port

Model Plugin

Layout +

Activity/Fragment

Boundary BoundaryBusiness logic

Port Port

Create task

Send chat message

Update note

Request projects

Business logic

Port Model Plugin

Boundary

Asynchrony?

Create task

Send chat message

Update note

Request projects

Business logic

Port Model Plugin

Boundary

We don’t like:

callback’s hell + AsyncTasks

We don’t like:

callback’s hell + AsyncTasks

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

only commands in separate threads

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

Use cases

Model Plugin

Layout +

Activity/Fragment

Boundary BoundaryBusiness logic

Port Port

Model Plugin

Layout +

Activity/Fragment

PresenterUse

Case

Model Plugin

Use Case

Repo

sito

ryModel Plugin

Use Case

Conclusions

photo by Daniel Sancho

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

How?

1. Simple refactors

2. Model View Presenter

3. Hexagonal

How?

1. Simple refactors

2. Model View Presenter

3. Hexagonal

4. Clean Architecture

Thank you!

Questions?