Страх и ненависть в event bus

32
СТРАХ И НЕНАВИСТЬ В EVENT-BUS Олег Годовых

Upload: 0legg

Post on 17-Jul-2015

65 views

Category:

Software


1 download

TRANSCRIPT

СТРАХ И НЕНАВИСТЬ В EVENT-BUSОлег Годовых

AT FIRST IT WAS LIKE THIS

BUT THEN 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 как нолик, а не как Олежка.