from legacy to hexagonal (an unexpected android journey)
Post on 02-Jul-2015
1.126 Views
Preview:
DESCRIPTION
TRANSCRIPT
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
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
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
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
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?
top related