Вопреки провокационному заголовку, это никакая не новая архитектура, а попытка перевода простых и проверенных временем практик на новояз, на котором говорит современное Android-комьюнити

Введение


В последнее время стало больно смотреть на то, что творится в мире разработки под мобильные платформы. Архитектурная астронавтика процветает, каждый хипстер считает своим долгом придумать новую архитектуру, а для решения простой задачи вместо двух строчек вставить несколько модных фреймворков.

Айтишные сайты заполонили туториалы по модным фреймворкам и переусложненным архитектурам, но при этом даже нет best practice для REST-клиентов под Android. Хотя это один из самых частых кейсов приложений. Хочется чтобы нормальный подход к разработке тоже пошел в массы. Поэтому и пишу эту статью

Чем плохи существующие решения


По большому счету проблема новомодных MVP, VIPER и им подобных — ровно одна, их авторы не умеют проектировать. А их последователи — тем более. И поэтому не понимают важных и очевидных вещей. И занимаются обычным оверинжинирингом.

1. Архитектура должна быть простой


Чем проще, тем лучше. Тем проще для понимания, надежнее и гибче. Переусложнить и наделать кучу абстракций может любой дурак, а чтобы сделать просто — нужно хорошенько подумать.

2. Оверинжиниринг это плохо


Добавлять новый уровень абстракции нужно только когда старый уже не решает проблем. После добавления нового уровня система должна стать проще для понимания, а кода меньше. Если, например, после этого у вас вместо одного файла, стало три, а система стала более запутанной, то вы сделали ошибку, а если по-простому — написали херню.

Фанаты MVP, например, сами в своих статьях пишут открытым текстом что MVP тупо приводит к значительному усложнению системы. И оправдывают это тем что так гибче и поддерживать проще. Но, как мы знаем из пункта номер 1, это взаимоисключающие вещи.

Теперь про VIPER, просто посмотрите, например, на схему из этой статьи.

Схема
image

И это для каждого экрана! Моим глазам больно. Особенно сочувствую тем, кому на работе с этим приходится сталкиваться не по своей воле. Тем же, кто это сам внедрил, сочувствую по немного другим причинам.

Новый подход


Эй, я тоже хочу модное название. Поэтому предлагаемая архитектура называется RESSRequest, Event, Screen, Storage. Буковки и названия подробраны так тупо для того чтобы получилось читаемое слово. Ну и чтобы не создавать путаницу с уже используемыми названиями. Ну и с REST созвучно.

Сразу оговорюсь, эта архитектура для REST-клиентов. Для других типов приложений она, вероятно, не подойдет.



1. Storage


Хранилище данных (в других терминах Model, Repository). Этот класс хранит данные и занимается их обработкой(сохраняет, загружает, складывает в БД и т.п.), а так же все данные от REST-сервиса сначала попадают сюда, парсятся и сохраняются здесь.

2. Screen


Экран приложения, в случае Android это ваше Activity. В других терминах это обычный ViewController как в MVC от Apple.

3. Request


Класс, который отвечает за посылку запросов к REST-сервису, а так же прием ответов и уведомление об ответе остальных компонентов системы.

4. Event


Связующее звено между остальными компонентами. Например, Request посылает эвент об ответе сервера, тем кто на него подписался. А Storage посылает эвент об изменении данных.

Далее пример упрощенной реализации. Код написан с допущениями и не проверялся, поэтому могут быть синтаксические ошибки и опечатки

Request
public class Request
{
	public interface RequestListener
	{
		default void onApiMethod1(Json answer) {}
		default void onApiMethod2(Json answer) {}
	}

	private static class RequestTask extends AsyncTask<Void, Void, String>
	{
		public RequestTask(String methodName)
		{
			this.methodName = methodName;
		}

		private String methodName;

		@Override
		protected String doInBackground(Void ... params)
		{
			URL url = new URL(Request.serverUrl + "/" + methodName);
			HttpURLConnection httpConnection = (HttpURLConnection)url.openConnection();

			// ...
			// Делаем запрос и читаем ответ
			// ...

			return result;
		}

		@Override
		protected void onPostExecute(String result)
		{
			// ...
			// Парсим JSON из result
			// ...

			Requestr.onHandleAnswer(methodName, json);
		}
	}

	private static String serverUrl = "myserver.com";
	private static List<OnCompleteListener> listeners = new ArrayList<>();

	private static void onHandleAnswer(String methodName, Json json)
	{
		for(RequestListener listener : listeners)
		{
			if(methodName.equals("api/method1")) listener.onApiMethod1(json);
			else if(methodName.equals("api/method2")) listener.onApiMethod2(json);
		}
	}

	private static void makeRequest(String methodName)
	{
		new RequestTask(methodName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
	}

	public static void registerListener(RequestListener listener)
	{
		listeners.add(listener);
	}

	public static void unregisterListener(RequestListener listener)
	{
		listeners.remove(listener);
	}

	public static void apiMethod1()
	{
		makeRequest("api/method1");
	}

	public static void onApiMethod2()
	{
		makeRequest("api/method2");
	}
}


Storage
public class DataStorage
{
	public interface DataListener
	{
		default void onData1Changed() {}
		default void onData2Changed() {}
	}

	private static MyObject1 myObject1 = null;
	private static List<MyObject2> myObjects2 = new ArrayList<>();

	public static void registerListener(DataListener listener)
	{
		listeners.add(listener);
	}

	public static void unregisterListener(DataListener listener)
	{
		listeners.remove(listener);
	}

	public static User getMyObject1()
	{
		return myObject1;
	}

	public static List<MyObject2> getMyObjects2()
	{
		return myObjects2;
	}

	public static Request.RequestListener listener = new Request.RequestListener()
	{
		private T fromJson<T>(Json answer)
		{
			// ...
			// Парсим или десереализуем JSON
			// ...

			return objectT;
		}

		@Override
		public void onApiMethod1(Json answer)
		{
			myObject1 = fromJson(answer);

			for(RequestListener listener : listeners) listener.data1Changed();
		}

		@Override
		public void onApiMethod2(Json answer)
		{
			myObject2 = fromJson(myObjects2);

			for(RequestListener listener : listeners) listener.data2Changed();
		}
	};
}


Screen
public class MyActivity extends Activity implements DataStorage.DataListener
{
	private Button button1;
	private Button button2;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		button1.setOnClickListener((View) -> {
			Request.apiMethod1();
		});

		button2.setOnClickListener((View) -> {
			Request.apiMethod2();
		});

		updateViews();
	}

	@Override
	protected void onPause()
	{
		super.onPause();

		DataStorage.unregisterListener(this);
	}

	@Override
	protected void onResume()
	{
		super.onResume();

		DataStorage.registerListener(this);
		updateViews();
	}

	private void updateViews()
	{
		updateView1();
		updateView2();
	}

	private void updateView1()
	{
		Object1 data = DataStorage.getObject1();

		// ...
		// Тут обновляем нужные вьюшки
		// ...
	}

	private void updateView2()
	{
		List<Object2> data = DataStorage.getObjects2();

		// ...
		// Тут обновляем нужные вьюшки
		// ...
	}

	@Override
	public void onData1Changed()
	{
		updateView1();
	}

	@Override
	public void onData2Changed()
	{
		updateView2();
	}
}


App
public class MyApp extends Application
{
	@Override
	public void onCreate()
	{
		super.onCreate();
		
		Request.registerListener(DataStorage.listener);
	}
}


Та же схемка, но в терминах RESS, для понимания


Работает это так: При нажатии на кнопку, дергается нужный метод у Request, Request посылает запрос на сервер, обрабатывает ответ и уведомляет сначала DataStorage. DataStorage парсит ответ и кеширует данные у себя. Затем Request уведомляет текущий активный Screen, Screen берет данные из DataStorage и обновляет UI.

Screen подписывается и отписывается от умедомлений в onResume и onPause соотвественно. А так же обновляет UI дополнительно в onResume. Что это дает? Уведомления приходят только в текущую активную Activity, никаких проблем с обработкой запроса в фоне или поворотом Activity. Activity будет всегда в актуальном состоянии. До фоновой активити уведомление не дойдет, а при возвращении в активное состояние, данные возьмутся из DataStorage. В итоге никаких проблем при повороте экрана и пересоздании Activity.

И для всего этого хватает дефолтных апи из Android SDK.

Вопросы и ответы на будующую критику


1. Какой профит?


Реальная простота, гибкость, поддерживаемость, масштабируемость и минимум зависимостей. Вы всегда можете усложнить определенную часть системы, если вам необходимо. Очень много данных? Аккуратно разбиваете DataStorage на несколько. Огромное REST API у сервиса? Делаете несколько Request. Листенеры это слишком просто, некруто и немодно? Возьмите EventBus. Косо смотрят в барбершопе на HttpConnection? Ну возьмите Retrofit. Жирный Activity с кучей фрагментов? Просто считайте что каждый фрагмент это Screen, или разбейте на сабклассы.

2. AsyncTask это моветон, возьми хотя бы Retrofit!


Да? И какие проблемы он в данном коде вызывает? Утечки памяти? Нет, тут AsyncTask не хранит ссылки на активити, а только ссылку на статик метод. Ответ теряется? Нет, ответ всегда приходит в статик DataStorage, пока приложение не убито. Пытается обновить активити на паузе? Нет, уведомления приходят только в активную Activity.

Да и как тут поможет Retrofit? Просто смотрим сюда. Автор взял RxJava, Retrofit и все равно лепит костыли, чтобы решить проблему, которой в RESS попросту нет.

3. Screen это же ViewController! Нужно разделять логику и представление, arrr!


Бросьте уже эту мантру. Типичный клиент для REST-сервиса это одна большая вьюшка для серверной части. Вся ваша бизнес-логика это установить нужный стейт для кнопки или текстового поля. Что вы там собрались разделять? Говорите так будет проще поддерживать? Поддерживать 3 файла с 3 тоннами кода, вместо 1 файла с 1 тонной проще? Ок. А если у нас активити с 5 фрагментами? Это у нас уже 3 x (5 + 1) = 18 файлов.

Разделение на Controller и View в таких кейсах просто плодит кучу бессмысленного кода, пора бы уже это признать. Добавлять функционал в проект с MVP особенно весело: хочешь добавить обработчик кнопки? Ок, поправь Presenter, Activity и View-интерфейс. В RESS для этого я напишу пару строк кода в одном файле.

Но ведь в больших проектах ViewController ужасно разрастается? Так вы не видели больших проектов. Ваш REST-клиент для очередного сайта на 5тыс строк это мелкий проект, а 5тыс строк там только потому что на каждый экран по 5 классов. Реально большие проекты на RESS с 100+ экранов и несколькими командами по 10 человек прекрасно себя чувствуют. Просто делают несколько Request и Storage. А Screen для жирных экранов содержат внутри себя дополнительные Screen для крупных элементов UI, например, тех же фрагментов. Проект на MVP тех же масштабов просто захлебнется в куче презентеров, интерфейсов, активити, фрагментов и неочевидных связей. А переход на VIPER вообще заставит всю команду уволиться одним днем.

Заключение


Надеюсь эта статья сподвигнет многих разработчиков пересмотреть свои взгляды на архитектуру, не плодить абстракции и посмотреть на более простые и проверенные временем решения

Комментарии (32)


  1. mobi
    22.09.2018 16:03

    Аббревиатура RESS в мобильном мире когда-то закрепилась за Responsive Design + Server Side (https://www.lukew.com/ff/entry.asp?1392).


  1. Logos_Intellect
    22.09.2018 18:11

    Хорошо было бы посмотреть на вашу архитектуру в действии. Может вам стоит написать какое-нибудь приложения для демонстрации? Честно говоря у меня возникает сомнение в целесообразности применении данной архитектуры даже в средних приложениях. Что-то подобное у нас делал Junior разработчик в небольшом проекте и от этого кода дыбом волосы становились, особенно, когда мне пришлось исправлять баги и дорабатывать экраны…


    1. ilitaexperta Автор
      22.09.2018 18:14

      В статье есть пример небольшого приложения, опущены только детали реализации UI

      Честно говоря у меня возникает сомнение в целесообразности применении данной архитектуры даже в средних приложениях
      Так приведите аргументы почему?

      Отсутствие проблем с Activty lifecycle, поворотами экрана, упрощение архитектуры и как следствие увеличение понимания системы любым членом команды, уменьшение количества мусорного кода, упрощение поддержки, ускорение и удешевление стоимости разработки для вас нецелесообразно?

      делал Junior разработчик в небольшом проекте и от этого кода дыбом волосы становились
      А от статей по VIPER у вас волосы дыбом не встают? Вы точно уверены кто там у вас джуниор в команде? Ну и скорее всего ваш джун просто все коряво сделал


      1. Logos_Intellect
        22.09.2018 21:49

        В статье есть пример небольшого приложения, опущены только детали реализации UI

        Я, наверное, не правильно выразился. Я имел ввиду законченное клиент-серверное приложение среднего масштаба. Например небольшой клиент для GitHub. Ваш пример очень простой и может не выявить определённых проблем в архитектуре, а они присутствуют в любой из них. Тем более я уверен, что Вы будете дальше развивать вашу архитектуру, и мне, как неуверенному джуниору, нужен будет пример реализации

        Отсутствие проблем с Activty lifecycle, поворотами экрана, упрощение архитектуры и как следствие увеличение понимания системы любым членом команды, уменьшение количества мусорного кода, упрощение поддержки, ускорение и удешевление стоимости разработки для вас нецелесообразно?

        Сильное заявление, проверять я его, конечно, не буду. Если сделаете рабочее приложение и выложите на GitHub, будет очень круто. Ну а пока я просто вижу несколько проблем, которые, конечно, можно решить, но надо посмотреть как это всё будет работать вместе и не усложнит ли всю архитектуру. Далее привожу естественно возникшие вопросы

        • В каком компоненте будет реализована навигация?
        • Я так понимаю, что данные в DataStorage пока не чистятся. В какой момент будет происходит очистка?
        • Будет ли активность использовать несколько Request и DataStorage для переиспользования кода?
        • Могу ли отменить Request?
        • Как обрабатываются ошибки?


        Думаю хватит, не буду к каждой мелочи придираться. Вообще это хорошо, что ты придумываешь как было бы лучше писать приложения, но трудно оценить реальную пользу на искусственном примере

        А от статей по VIPER у вас волосы дыбом не встают?

        Я Android разработчик, у нас VIPER не так популярен как на iOS, но благодаря Вам я прочёл одну статью, и на удивление мне всё понятно. Это обычная реализация Clean Architecture


        1. ilitaexperta Автор
          22.09.2018 22:45

          Сразу скажу, предлагаемая архитектура в статье описывает вопросы по проектированию приложения, а не то, на какой секунде выполения удалять данные из кеша, это зависит от задачи

          Я имел ввиду законченное клиент-серверное приложение среднего масштаба. Например небольшой клиент для GitHub.
          Если будет свободное время, то выложу конечно

          Сильное заявление, проверять я его, конечно, не буду.
          Аргументация уровня ТНТ

          В каком компоненте будет реализована навигация?
          Что вы имеете ввиду под навигацией? Переходы между Activity? Как правило в Activity. В кейсах когда Activity на экране нет, то подходящий вариант — Application

          Я так понимаю, что данные в DataStorage пока не чистятся. В какой момент будет происходит очистка?

          Зависит от вашей задачи и требований. Самый частые кейсы — перезаписывать новыми данными, удалять лишние при обновлении.

          Будет ли активность использовать несколько Request и DataStorage для переиспользования кода?
          Вопрос вообще не очень корректно поставлен. Request\DataStorage — статик классы, вы можете использовать их из любой части приложения. В RESS рекомендуется делать по одному Request и DataStorage на все приложение, и разбивать их на несколько только в случае сильного разрастания их объема(Например, при широком API, или большом разнообразии данных)

          Могу ли отменить Request?
          Опять же, не корректный вопрос. Request — статик класс с набором методов, делающим запросы к REST-API, а не отдельный запрос. Если вы хотели, спросить можно ли отменить запрос посланный к сервису, этого нет в примере, но никаких ограничений архитектура не накладывает.

          Как обрабатываются ошибки?
          Зависит от задач и типа ошибок. Ошибки от сервиса приходят вместе с answer в листенер подписанный на Request. Подписывайтесь где вам удобно и обрабатывайте. Визуальные можете в Activity, с данными в DataStorage, или вообще вынести отдельный статик ErrorHandler. Не думаю что это имеет смысл разжовывать, это очевидно

          Я Android разработчик, у нас VIPER не так популярен как на iOS, но благодаря Вам я прочёл одну статью, и на удивление мне всё понятно
          Хорошо. Вот, например, мне не все понятно. Если вы разобрались, распишите почему разбиение одного экрана приложения, даже очень простого, на ~10 сущностей(Router, Configurator, Presenter, Interactor, View и интерфейс к каждому) и файлов некоторые люди, в том числе видимо и вы, считают адекватным инженерным подходом?


          1. Logos_Intellect
            22.09.2018 23:30

            Далее не вижу смысла задавать вопросы, лучше посмотрю на реализацию всего этого чуда. Лучше отвечу про VIPER, а точнее про то как я реализовываю Clean Architecture в своём приложении. Становится ясно, что вы не поняли её смысл. Никаких 10 сущностей на каждый экран нет. На каждый экран есть View и Presenter, причём презентер в некоторых редких случаях можно переиспользовать. Роутер у вас один на всё приложение, а не на каждый экран, не знаю откуда вы это взяли вообще. Далее интеракторы и репозитории тоже на всё приложение и разбиты по фичам, то есть максимально переиспользуются во всём приложении. Далее даю ссылки на статью1 и статью2 для ознакомления. Вы же пишите в топик по Android и должны их в пример приводить, а не VIPER с реализацией под iOS. Судя по вашему коду и ответам на вопросы у вас не так много опыта в разработке больших приложений под Android. Не хотелось бы ещё разводить холивар по поводу переноса фигурной скобки на новую строку, но в Java так не пишут, есть устоявшийся стиль форматирования (Ctrl + Alt + L в помощь)


            1. ilitaexperta Автор
              23.09.2018 00:17

              но в Java так не пишут, есть устоявшийся стиль форматирования
              Всю жизнь в джаве скобку на новую строку ставил и никто не умер. В тексте прям чувствуется ваше желание меня поучить. Вы на чем-то кроме джавы писали?

              Вы же пишите в топик по Android и должны их в пример приводить, а не VIPER с реализацией под iOS
              Вы видите прям такую принципиальную разницу между этими системами в части реализации архитектуры?

              На каждый экран есть View и Presenter
              Про интерфейс для View не забыли? Итого, классический MVP. По 3 сущности на экран. Уже не 10 конечно, но тоже перебор.

              Далее интеракторы и репозитории тоже на всё приложение и разбиты по фичам
              Ага, видимо по одному интерактору и репозиторию на фичу, а фича чаще всего соответствует экрану. Т.е добавили новую фичу, впилили кучу классов и интерфейсов на всех уровнях, и это еще без реализации самой фичи. Это адекватный инженерный подход? Ок

              В приведенных вами статьях, нет ни слова про VIPER. А в той, что привел я, автор предлагает делать ViewController, Presenter, Iteractor, Configurator под каждый экран, и говорит что это соотвествует принципам Clean Architecture. Вы же говорите третье.
              Где же правда?

              В любом случае даже в приведенных вами статьях, как и в VIPER подходах, общий посыл это наплодить 100500 сущностей, где можно обойтись двумя-тремя. Автор вероятно в детстве порезался бритвой Оккама


              1. Logos_Intellect
                23.09.2018 00:35

                Дабы не быть многословным, я просто подскажу книгу, которая поможет тебе в твоём познании в архитектуре приложений. У тебя огромные пробелы в понимании чистого кода и архитектуры. Я просто не хочу сейчас разводить бессмысленные споры в комментариях

                Мартин Роберт. Чистая архитектура. Искусство разработки программного обеспечения


                1. ilitaexperta Автор
                  23.09.2018 00:50

                  Аргумент «просто не шаришь», я тебя понял


          1. kapmayn
            24.09.2018 13:34

            1) onHandleAnswer — это вообще что такое? а если будет 30 запросов, там будет ifelse на 30 условий? рили?
            2) урлы запросов тоже будете дублировать?
            3) сейчас ваша архитектура не позволяет отменять запросы, а если добавить эту возможность, то опять же будет множество переменных для каждого запроса
            4) если экрану не нужен ответ от определённого запроса, то зачем он подпсывается на него?
            5) а если нужно обработать два ответа разных запросов и только после этого показывать пользователю? подозреваю, появится куча флагов.
            6) и где будет происходить эта обработка? dataStorage отвечает только за хранение данных, а screen должен отвечать только за отображение
            7) если делать обработку в screen, то получится каша из логики обработки и отображения, в которой разбираться долго, как минимум, придётся как-то отделять/разносить, с чем отлично справляется mvp
            8) плюс mvp, например, в том, что я могу просто открыть интерфейс презентера/view и по методам понять, что происходит на экране. в вашей архитектуре это сделать очень сложно
            9) у вас есть проблемы со структурой класса и, как уже отметили ниже, с кодстайлом, если уж пишите на java, то соблюдайте принятые нормы. в чужой монастырь со своим уставом, как говорится

            конечно, в мелкие проекты на 1-2 экрана тащить clean или просто mvp смысла нет, но в остальном такое вот юзать будут только очень странные люди


            1. ilitaexperta Автор
              24.09.2018 14:01

              Наконец-то свидетели MVP подъехали, без вас было скучно

              Как сказал один мой друг увидев ваш комент:

              Чувак не может url в конфиг вынести, о чем ещё говорить?)

              1. См.цитату
              2. См.цитату
              3. См.цитату
              4. В листенере несколько методов? Проблема проблем. См.цитату
              5. Показывать после второго? State
              6. Плохо читали статью. dataStorage отвечает в том числе и за приведение данных в удобоваримый вид
              7. См. пункт 6
              8. Натягиваение глобуса на сову
              9. Какие конкретно? Скобка не там — ррря, проблемы с кодстайлом!

              Архитектура регламентирует подход к проектированию, а не количество ифов в методе. Вы не можете элементарно модифицировать простейший код чтобы несколько ифов убрать, как маленькие прямо.

              Зато считаете что разбираетесь в проектировании, прочитав пару статей про Clean и MVP


              1. kapmayn
                24.09.2018 14:37

                Использую я mvvm, так что мимо


                1) ну вы привидите решение без ifelse. В данном случае красиво это не решить
                2) вынести можно всё, но не всему там место, здесь претензия была больше к коду. Вы же пытаетесь прорекламировать вашу архитектуру, а код пишите такой себе, Алик плюсов и не видно, если они есть, конечно
                3) вообще без аргументов
                4) тут отсутствует логика, как минимум
                5) вы мой комментарий читали? Я и говорю, что появится куча флагов
                6) следуя из названия, класс отвечает за хранение данных. Проблемы в нейминг?
                7) ответил выше
                8) опять без аргументов написали бред
                9) если проблемы с русским, то 'и' — это союз, перед которым было написано про структуру класса


                Если не можете воспринимать критику, то удалите статью просто, не ваше это
                Я показал конкретные недостатки в вашей реализации вашей архитектуры. Сказать можно много чего, а вы попробуйте написать правильный и красивый код с данной "архитектурой", потом уже можете говорить что-то про ее идеальность и превосходство


                1. ilitaexperta Автор
                  24.09.2018 15:11

                  1. Аннотации
                  2. Я и не спорю что код в примерах не идеален, это не главное в статье
                  3. Запросы в мапку, отменять по idшнику. Один метод cancel(id). Очевидные вещи нужно расписывать?
                  4. В чем проблема то вообще? Вы предлагаете делать по одному листенеру на ответ или что?
                  5. А вы ответ читали? Обновлять UI после последнего ответа из нескольких и никаких флагов
                  6. Проблема проблем. Ну назовите подругому
                  8. Вы в качестве плюса MVP приводите смехотворный и вообще сомнительный пример. Обычно чем занимается экран понятно по названию, или проблемы в нейминг?
                  9. Нечего сказать — докопайся до орфографии. Так и вопрос был, какие именно проблемы со структурой? И как сделать лучше, по-вашему?

                  Если ты не согласен с моим мнением, то удоли статью!
                  Я смотрю вы критику и сами не воспринимаете толком. Вы приводите недостатки кода в конкретном примере. Я же отвечаю что, конкретно к архитектуре большая часть этих недостатков отношения не имеет. Код в статье чтобы показать общую схему работы, а не чтобы его копипастить.

                  Ну в целом я согласен, что видимо стоит в примеры вылизанный вариант выкладывать, чтобы можно было сразу в проект копировать, а не головой думать


                  1. kapmayn
                    25.09.2018 06:58

                    1) не решают
                    2) ещё раз повторяю: вы продаёте архитектуру, а в данном случае она выглядит слишком убого, поэтому в ваших интересах написать правильный код. вы же наставиали на использовании asyncTask, то есть выставили требования к реализации. или это работает так «ну я вот тут я разобрался, как делать, поэтому делайте так, а тут не разобрался, поэтому придумайте сами»? двойными стандартами пахнет
                    3) а вы часто пишите велосипеды? зачем писать что-то своё, если есть аналог, который поддерживается людьми, тестируется и имеет больше функционала?
                    4) я говорю про то, что в любой архитектуре, которые вы так сильно хаете, этой проблем нет, ибо подписка идёт только на то, что нужно экрану и всё
                    5) как вы поймёте, какой ответ был последний? или будете запускать их последовательно?
                    6) ну вот ещё один косяк в вашей «арзитектуре»: она должна быть понятна изначально, все термины/названия должны быть понятны, а не так, как вы говорите «ну переименуй, в чём проблема». я могу и модифицировать её до mvp, но это же уже не будет вашим решением
                    8) «экран отображает сообщения» и «на экране обрабатываются такие-то эвенты, отображаются такие-то данные и открываются такие-то экраны» — это совсем разные знания, согласны? интерфейс предоставляет второе, что гораздно полезнее
                    9) я не докапывался до орфографии, просто вы слишком часто подменяете понятия. в данном случае структура класса — это последовательность полей и методов с различными областями видимости

                    вы не можете этого видеть, потому что меня сейчас не критикуют
                    снова повторяю, все недостатки имеют отношение к архитектуре, потому что вы должны показать рабочий, карсивый и правильный вариант её использования, а показываете вот это

                    снова подмена понятий: никтоне собирается копировать ваш код, успокойтесь уже
                    «mvp — слишком сложна, я не хачу думать» — вот суть ваших претензий, а сейчас, так что ваш ответ выглядит глупо


  1. potan
    22.09.2018 22:22

    Очень простая архитектура TEA (The Elm Architecture) хорошо себя зарекомендовала в веб-приложениях. В том числе и в работе с http.
    Думаю, для мобильных приложений она бы тоже подошла.


  1. 20912
    22.09.2018 22:53

    Восстановил пароль от Хабра для этого комментария.
    Полностью поддерживаю автора. Ушел на похожую «архитектуру» год назад (с MVP, кстати). Не пожалел ни разу:
    1. «Эфемерность» андроидных вью решается встроенным объектом стейта (vanilla/Bundle, Icepick), либо «улучшенными фрагментами Conductor».
    2. Однонаправленный поток данных? Сетевой слой в сервис (по возможности IntentService — чтобы о байндингах и экземплярах при множественных запросах не думалось). Никаких ответов во вью слой: результат — в удобный storage ObjectBox.
    3. Обновление вью? Подписка на представление хранилища (Rx рулит).
    4. Несколько сетевых API, использование аппаратных провайдеров, логика на стороне клиента? О да, достаем DI. Только не монструозного Dagger2, а лаконичного Toothpick.

    — И, ой! Это же классический MVC?! Ему же лет 40! Фу! Да, это так.
    — И что, приложение магазина «весит» меньше 10МБ вместе с картинками? Да, но мне и не за LOC (количество строк кода) платят.
    — А какая «магия» нужна для обновления данных отображаемых в неактивном parentView при изменениях в его Child? Никакой, это дефолтная особенность «архитектуры».


  1. advance
    23.09.2018 01:25

    Тоже поддерживаю автора. Плюсанул бы, да кармы нет)
    Вообще за 5 лет тоже пришел к тому, что существующие решения переусложнены. Сам часто делаю на упрощенной clean, да и только потому, что заказчик непредсказуем, а чистого rest становится всё меньше.
    Не понимаю всего фана по архитектуре- многие воспринимают mvp\viper как стандарт де-факто. Ни шагу в сторону. На практике- да, знать их надо, но использовать надо как базу, а не идентичное решение. Как паттерны. Потому чем больше типовых архитектур- тем лучше.
    И вообще, всё чаще прихожу к тому, что миром девелоперов сегодня больше управляет мода нежели здравый смысл.
    На мой взгляд, RESS (как бы его не называли) выглядит вполне рабочим решением для определенного круга проектов (не только rest).
    Два вопроса:

    1. Я не против асинктасков, но Вам не кажется, что сегодня ходить в веб через HttpUrlConnection банально опасно, например, отказом нормальной работы? Например, у OkHttp свое обращение с сокетами- бесшовный реконнект, более грамотная работа с heartbeat, нет проблем с gzip и много еще чего. От того HttpUrlConnection, начиная с 5го андроида, основан на OkHttp в кишках.
    Пример из практики- надо закачать файл на сервак. Реализация на HttpUrlConnection. На 4.4+ всё норм, на 4.3 возвращалась ошибка. Как выяснилось- рано закрывался сокет, на 4.3 и ниже HttpUrlConnection не умеет его переоткрывать. Решилось переделкой на OkHttp.
    На мой взгляд, асинктаски можно использовать в ряде задач вместе с грамотным управлением потоками (экзекуторы), но внутри от HttpUrlConnection лучше отказаться.

    2. А что с тестированием на моках? Получается, так или иначе, придем к DI на Gradle Flavours\Dagger и репозиториям? а там уже «привет clean architecture»?


    1. ilitaexperta Автор
      23.09.2018 01:38

      HttpUrlConnection лучше отказаться.
      Так я и не против, для примера взял то что под руку подвернулось.

      А что с тестированием на моках?
      А какой в нем смысл? REST-клиент это приложение на 80% состоящее из UI, если и тестировать то UI. А учитывая качество UI-компонентов в Android, то и большая часть багов там. Усложнять код, только для того чтобы протестировать копеечную бизнес логику, не выглядит хорошей идеей


      1. advance
        23.09.2018 02:41

        Если говорить о простеньком варианте приложения (который Вы, как я понял, подразумеваете)- то да, тут Вы правы. Но ведь такие rest-клиенты никто (ну или почти никто) в продакшене не ждет?
        Добавим сюда кеширование или возможность работы оффлайн, пуши, авторизацию, обновляемые токены. Возможно даже простенький чат на rest. И вот это уже похоже на определенный минимум, который (на мой взгляд) вполне укладывается в RESS, и бизнес-логика там не такая уж и копеечная. Так или иначе придем к тому, что минимум 1-2 компонента надо тестировать


        1. ilitaexperta Автор
          23.09.2018 03:29

          То что вы описали, вполне стандартная и не сложная логика, половина вообще обертка над запросами ответами к REST-API, которое и так тестами обложена.

          Я вообще скепьтически отношусь к обкладыванию всего и вся тестами, особенно если игнорируется UI. Смысл тратить время на тестирование Presenterа, если потом всеравно косяки во View нам наделают багов. Это вообще какаято малоосмысленная затея, как по мне. Больше похоже на культ карго, чем на реальную необходимость.

          Мы усложняем архитектуру, плодим абстракции, затем пишем мусорный код для их связи, как следствие получаем больше багов, тратим больше времени. А потом еще тратим время и пишем тесты, чтобы эти баги отловить. Выгода тут вообще сомнительная


  1. balajah
    23.09.2018 13:23

    Если Request убрать, еще проще будет — обычный клиент-сервер. Веб-приложения я стараюсь писать 2-х звенными, если конечно не нужен серверный пуш. Клиент-сервер неубиваем, опять же хорошо ложится на микросервисы. Клиент — это тот, кому нужно, то есть инициатор соединения, активное звено. Сервер — пассивное. Например в случае серверного пуша клиентом будет Model а конечным сервером — View. Посредники типа контроллеров или презентеров — вообще спорная вещь, если не нужен паттерн фасад.


    1. kuraga333
      24.09.2018 09:35

      А можно привести пример, чуть конкретнее? Я не понял, где же, собственно, логика? Или «клиент»-«серверов» несколько?


      1. NLO
        00.00.0000 00:00

        НЛО прилетело и опубликовало эту надпись здесь


        1. kuraga333
          24.09.2018 10:37

          Ну во приложение с двумя компонентами: 1) поле ввода текста, 2) div, отображающий текст из компонента 1. Тут как будет устроено?


          1. NLO
            00.00.0000 00:00

            НЛО прилетело и опубликовало эту надпись здесь


            1. kuraga333
              24.09.2018 11:30

              То есть микросервис «поле вывода»: поле-клиент, модель-сервер; микросервис «дисплей вывода»: вью-сервер; и еще сессия. Правильно?

              И сессия выступает для каждого из микросервиса и клиентом, и сервером?


              1. NLO
                00.00.0000 00:00

                НЛО прилетело и опубликовало эту надпись здесь


                1. kuraga333
                  24.09.2018 12:44

                  Несостыковка:

                  клиентами могут быть все компоненты кроме Сессии
                  в Сессию, а та рассылает по своим Вью

                  А вообще, создаетсся впечатление, что это эволюция MVC/MVP/etc. Там у нас одна Model, один View/etc., один Controller/etc. А тут: одна Session (похожая на контроллер?) и много Client'ов и Server'ов, которые могут выполнять, в т.ч., роли моделей и вьюх. Итак, отличия: 1) замена на «абстрактные» Client и Server, 2) не по одному, а много.


  1. kuraga333
    24.09.2018 09:32

    Спасибо за новую архитектуру!

    Я правильно понимаю, что «под мобильные приложения» здесь важно, и имеется ввиду «под нынешние фреймворки Android/iOS»? Я не занимался мобильными приложения, но в веб-, используя, полагаю, MVP, я не наблюдал проблемы «усложненной архитектуры», но, надо признать, для этого я написал свою микро-библиотеку (а-ля урезанный React.JS, видимо), то есть я не был стеснен ничем, кроме «вид — это DOM».

    P.S. Я не спорю с Вами, я просто хочу сравнить с MVP подробнее. Ибо как увидел схему VIPER, так срочно закрыл вкладку от ужаса. Там все понятно, а про MVP интересно.


  1. oleg_shishkin
    24.09.2018 13:33

    Вспоминаю — использовал я EventBus и забыл я что такое слушатели. Опыт использования Контроллера Прерываний при проектировании железа как никак был. И было все шустро и красиво. Но народ почему то плевался. Ну очень нравятся слушатели народу. И перешел я на Service Locator. И плевать мне, делим ли мы на View/Presenter/Router или не делим. И плевать мне на все, что выдумает Google, а завтра это же и обосрет. Главное — решает ли проблемы архитектуры андроид вкупе с задачами клиента или нет.
    github.com/shishkin1966/CleanArchitecture5


  1. blainepwnz
    24.09.2018 16:48
    +1

    С виду ваша архитектура — обычное MVC.
    Не понимаю откуда столько агрессии к MVP, отличная архитектура, которая позволяет отлично разделять логику и переиспользовать компоненты, на данный момент у меня в проекте VIPER(проект больше 200к строчек кода), без випера давно утонул уже километровых классах( презентер в главном экране, где есть куча бизнесс логики — 300 строчек, большая часть методов по 5-10 строчек и обращаются с многочисленными интеракторами, каждый из которых отвечает строго за свою логику), как результат легко тестировать, легко поддерживать, для маленьких и тестовых проектов можно хоть всю логику писать в активити


    1. ilitaexperta Автор
      24.09.2018 16:55
      -1

      у меня в проекте VIPER
      Ну так поэтому и 200к строчек. На каждый чих абстракции добавлять, ясное дело будет много кода.