СТРАХ И НЕНАВИСТЬ В
EVENT-BUS
Олег Годовых
AT FIRST IT WAS LIKETHIS
BUTTHEN I UMLD…
МАГАЗИНЧИК
• 500 страниц документации.
• В том числе 200 страниц
описания API.
• Большое количество сложных
use-cases.
• Разветвлённая логика и схема
зависимостей.
МАГАЗИНЧИК
• 4 человеко-года на каждую платформу (iOS,Android).
• 6 разработчиков в команде Android.
• Общий размер команды — до 27 человек.
• ≈40000 строк кода.
ОСОБЕННОСТИ
• Android 4.0+
• Одно действие — один запрос.
• Настройками кэширования занимается сервер.
• Ссылки для последующих запросов приходят с сервера.
ПРОБЛЕМЫ
• Много разнородных запросов (около 100 в последней
версии API).
• Компонент-инициатор != компонент-получатель.
• Инициаторов может быть несколько.
• Получателей тоже!
КАК ДЕЛАТЬ ЗАПРОСЫ
• In-place AsyncTask.
• AQuery и иже с ними.
• Сервисы, контент-провайдеры, броадкасты…
• Реактивное программирование.
ЧЕМ ПЛОХИ ASYNCTASK
• Очень сложно переиспользовать.
• Очень много boilerplate-кода.
• Гроб, гроб, кладбище, утечка контекста.
requestDataTask = new AsyncTask<Void, Void, JSONObject>() {
@Override
protected JSONObject doInBackground(Void... params) {
final String requestResult = apiService.getData();
final JSONObject json = JsonUtils.parse(requestResult);
lruCache.cacheJson(json);
return json;
}
};
AsyncTask
Понятность +
Удобство -1
Переисполь
зование
—
Пацаны так
делают?
—
ПОЧЕМУ НЕ КАТИТ AQUERY
• Слишком локальное применение.
• Опять же — много повторяющегося кода.
• Сложно прикручивать другие библиотеки.
aq.ajax("http://example.com", String.class, CACHE_TIME, new AjaxCallback<String>() {
@Override
public void callback(String url, String object, AjaxStatus status) {
Type listType = new TypeToken<List<User>>() {}.getType();
List<User> list = new Gson().fromJson(object, listType);
listener.onResponse(list);
}
});
AsyncTask AQuery
Понятность + +
Удобство -1 2
Переисполь
зование
— —
Пацаны так
делают?
— ≈
ДАВАЙТЕ ПОСЛУШАЕМ
GOOGLE IO
• Храним в БД
• Скачиваем сервисом
• Отдаём через ContentProvider
ЧТО НЕ ТАК С CP
• Дважды конструируем
объект (при разборе
результата запроса и
при десериализации из
БД).
• Нужно инвалидировать
данные. Зачем нам SQL?
Нам SQL не нужен
AsyncTask AQuery Service + CP
Понятность + + ≈
Удобство -1 3 2
Переиспользо
вание
— — +
Не делаем
лишнего
— + —
Пацаны так
делают?
— ≈ +
ГОТОВЫЙ ФРЕЙМВОРК
• Сильная связность
• Есть явная «точка сопряжения»
final Subscription subscription =
createApiRequestObservable() //создали Observable с запросом
.timeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS) //поставили таймаут
.retry(RETRY_COUNT_FOR_REQUEST) //поставили кол-во повторов
.onErrorResumeNext(createRequestErrorHandler()) // назначили обработчик ошибки
.map(createJsonMapOperator()) //модифицировали Observable, чтобы получать JSONObject
.onErrorReturn(createJsonErrorHandler()) //возвращаем в случае ошибки то, что ожидаем
.doOnNext(createCacheOperation()); //кэшируем JSONObject
.subscribeOn(Schedulers.newThread()) //трудоёмкое в отдельном потоке
.observeOn(AndroidSchedulers.mainThread()) // обработка результата - в main thread
.subscribe(subscriber); //обработчик результата
AsyncTask AQuery
Service +
CP
RxJava
Понятность + + ≈ ≈
Удобство -1 3 2 4
Переиспользо
вание
— — + +
Не делаем
лишнего
— + — ≈
Пацаны так
делают?
— ≈ + +
VOLLEY
• Несколько уровней кэширования.
• Абстрагируемся от HTTP насколько возможно.
• Очень гибкое управление очередью.
ТИПИЧНАЯ СХЕМА
ДЕЙСТВИЙ
• Создаём по действию новый запрос
• Добавляем его в очередь
• Забываем
• …
• ОТВЕТ СЕРВЕРА!!!
ОТВЕТ СЕРВЕРА
• Распространяется через event bus.
• Все желающие его получают.
• Не страшно, если никому он уже не нужен.
НИ КАПЕЛЬКИ НЕ СТРАШНО
OTTO
• Подписка на события через
аннотации.
• Подписались на то, что не
приходит — не страшно.
• Приходит то, на что не
подписывались — не беда!
public class BaseFragment extends Fragment {
protected Bus bus = AzbukaApplication.getBus();
@Override
public void onResume() {
super.onResume();
bus.register(this);
NetworkFacade.cancelOldRequests(this);
}
@Override
public void onPause() {
super.onPause();
bus.unregister(this);
NetworkFacade.markToCancel(this);
}
ЧЕМ МАНИПУЛИРУЕМ
• BaseRequest — инкапсулирует парсинг и рассылку
сообщений
• BaseResponse — bean + id
public static class PutUserNickRequest extends BaseRequest<PutUserNickResponse, NewUserNick,
PutUserNickError> {
/**
* API v0.36 p. 74, Change user password (4.10.4)
* @param url - POST /user/<user-id>/phone
* @param nick - typed nick
*/
public PutUserNickRequest(String url, String nick) {
super(Method.PUT, url, PutUserNickResponse.class, PutUserNickError.class, new
NewUserNick(nick));
}
}
ПОДПИСКА
• Нам не важно, кто отправил.
• Нам нужно лишь уметь обрабатывать данный тип ответа.
• Кто на экране — тот и главный!
@Subscribe
public void onResponse(BasketRequest.BasketContentResponse basketContentResponse) {
loadWelcome();
}
@Subscribe
public void onError(OtherError error) {
if (TextUtils.isEmpty(error.getMessage())) {
Toast.makeText(getActivity(), getString(R.string.failed), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), error.getMessage(), Toast.LENGTH_LONG).show();
}
}
LET IT CRASH
• Есть одна точка получения данных об ошибке.
• Есть один общий workflow обработки ошибки.
• При особой надобности — можем подписаться на
ошибки конкретного класса.
ПОДВОДНЫЕ КАМНИ
• Event bus хорош, когда всё в нём хорошо.
• Отладка — снежный ком.
ПЛЮСЫ И ПЛЮШКИ
• Лёгкая проверка работоспособности back-end.
• Часть приложения сделана как конечный автомат на
основе Event Bus.
• Архитектура «непривязанных запросов» отлично ложится
почти на любое приложение.
СПАСИБО ЗА ВНИМАНИЕ
А ещё я в твиттере есть,
меня @0leGG зовут,
там 0 как нолик,
а не как Олежка.

Олег Годовых «Страх и ненависть в Event Bus»

  • 1.
    СТРАХ И НЕНАВИСТЬВ EVENT-BUS Олег Годовых
  • 4.
    AT FIRST ITWAS LIKETHIS
  • 5.
  • 6.
    МАГАЗИНЧИК • 500 страницдокументации. • В том числе 200 страниц описания API. • Большое количество сложных use-cases. • Разветвлённая логика и схема зависимостей.
  • 7.
    МАГАЗИНЧИК • 4 человеко-годана каждую платформу (iOS,Android). • 6 разработчиков в команде Android. • Общий размер команды — до 27 человек. • ≈40000 строк кода.
  • 8.
    ОСОБЕННОСТИ • Android 4.0+ •Одно действие — один запрос. • Настройками кэширования занимается сервер. • Ссылки для последующих запросов приходят с сервера.
  • 9.
    ПРОБЛЕМЫ • Много разнородныхзапросов (около 100 в последней версии API). • Компонент-инициатор != компонент-получатель. • Инициаторов может быть несколько. • Получателей тоже!
  • 10.
    КАК ДЕЛАТЬ ЗАПРОСЫ •In-place AsyncTask. • AQuery и иже с ними. • Сервисы, контент-провайдеры, броадкасты… • Реактивное программирование.
  • 11.
    ЧЕМ ПЛОХИ ASYNCTASK •Очень сложно переиспользовать. • Очень много boilerplate-кода. • Гроб, гроб, кладбище, утечка контекста. requestDataTask = new AsyncTask<Void, Void, JSONObject>() { @Override protected JSONObject doInBackground(Void... params) { final String requestResult = apiService.getData(); final JSONObject json = JsonUtils.parse(requestResult); lruCache.cacheJson(json); return json; } };
  • 12.
  • 13.
    ПОЧЕМУ НЕ КАТИТAQUERY • Слишком локальное применение. • Опять же — много повторяющегося кода. • Сложно прикручивать другие библиотеки. aq.ajax("http://example.com", String.class, CACHE_TIME, new AjaxCallback<String>() { @Override public void callback(String url, String object, AjaxStatus status) { Type listType = new TypeToken<List<User>>() {}.getType(); List<User> list = new Gson().fromJson(object, listType); listener.onResponse(list); } });
  • 14.
    AsyncTask AQuery Понятность ++ Удобство -1 2 Переисполь зование — — Пацаны так делают? — ≈
  • 15.
    ДАВАЙТЕ ПОСЛУШАЕМ GOOGLE IO •Храним в БД • Скачиваем сервисом • Отдаём через ContentProvider
  • 16.
    ЧТО НЕ ТАКС CP • Дважды конструируем объект (при разборе результата запроса и при десериализации из БД). • Нужно инвалидировать данные. Зачем нам SQL? Нам SQL не нужен
  • 17.
    AsyncTask AQuery Service+ CP Понятность + + ≈ Удобство -1 3 2 Переиспользо вание — — + Не делаем лишнего — + — Пацаны так делают? — ≈ +
  • 18.
    ГОТОВЫЙ ФРЕЙМВОРК • Сильнаясвязность • Есть явная «точка сопряжения» final Subscription subscription = createApiRequestObservable() //создали Observable с запросом .timeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS) //поставили таймаут .retry(RETRY_COUNT_FOR_REQUEST) //поставили кол-во повторов .onErrorResumeNext(createRequestErrorHandler()) // назначили обработчик ошибки .map(createJsonMapOperator()) //модифицировали Observable, чтобы получать JSONObject .onErrorReturn(createJsonErrorHandler()) //возвращаем в случае ошибки то, что ожидаем .doOnNext(createCacheOperation()); //кэшируем JSONObject .subscribeOn(Schedulers.newThread()) //трудоёмкое в отдельном потоке .observeOn(AndroidSchedulers.mainThread()) // обработка результата - в main thread .subscribe(subscriber); //обработчик результата
  • 19.
    AsyncTask AQuery Service + CP RxJava Понятность+ + ≈ ≈ Удобство -1 3 2 4 Переиспользо вание — — + + Не делаем лишнего — + — ≈ Пацаны так делают? — ≈ + +
  • 21.
    VOLLEY • Несколько уровнейкэширования. • Абстрагируемся от HTTP насколько возможно. • Очень гибкое управление очередью.
  • 22.
    ТИПИЧНАЯ СХЕМА ДЕЙСТВИЙ • Создаёмпо действию новый запрос • Добавляем его в очередь • Забываем • … • ОТВЕТ СЕРВЕРА!!!
  • 23.
    ОТВЕТ СЕРВЕРА • Распространяетсячерез event bus. • Все желающие его получают. • Не страшно, если никому он уже не нужен.
  • 24.
  • 25.
    OTTO • Подписка насобытия через аннотации. • Подписались на то, что не приходит — не страшно. • Приходит то, на что не подписывались — не беда! public class BaseFragment extends Fragment { protected Bus bus = AzbukaApplication.getBus(); @Override public void onResume() { super.onResume(); bus.register(this); NetworkFacade.cancelOldRequests(this); } @Override public void onPause() { super.onPause(); bus.unregister(this); NetworkFacade.markToCancel(this); }
  • 26.
    ЧЕМ МАНИПУЛИРУЕМ • BaseRequest— инкапсулирует парсинг и рассылку сообщений • BaseResponse — bean + id public static class PutUserNickRequest extends BaseRequest<PutUserNickResponse, NewUserNick, PutUserNickError> { /** * API v0.36 p. 74, Change user password (4.10.4) * @param url - POST /user/<user-id>/phone * @param nick - typed nick */ public PutUserNickRequest(String url, String nick) { super(Method.PUT, url, PutUserNickResponse.class, PutUserNickError.class, new NewUserNick(nick)); } }
  • 27.
    ПОДПИСКА • Нам неважно, кто отправил. • Нам нужно лишь уметь обрабатывать данный тип ответа. • Кто на экране — тот и главный! @Subscribe public void onResponse(BasketRequest.BasketContentResponse basketContentResponse) { loadWelcome(); } @Subscribe public void onError(OtherError error) { if (TextUtils.isEmpty(error.getMessage())) { Toast.makeText(getActivity(), getString(R.string.failed), Toast.LENGTH_LONG).show(); } else { Toast.makeText(getActivity(), error.getMessage(), Toast.LENGTH_LONG).show(); } }
  • 29.
    LET IT CRASH •Есть одна точка получения данных об ошибке. • Есть один общий workflow обработки ошибки. • При особой надобности — можем подписаться на ошибки конкретного класса.
  • 30.
    ПОДВОДНЫЕ КАМНИ • Eventbus хорош, когда всё в нём хорошо. • Отладка — снежный ком.
  • 31.
    ПЛЮСЫ И ПЛЮШКИ •Лёгкая проверка работоспособности back-end. • Часть приложения сделана как конечный автомат на основе Event Bus. • Архитектура «непривязанных запросов» отлично ложится почти на любое приложение.
  • 32.
    СПАСИБО ЗА ВНИМАНИЕ Аещё я в твиттере есть, меня @0leGG зовут, там 0 как нолик, а не как Олежка.