
Contrariamente al título provocativo, esta no es una arquitectura nueva, sino un intento de traducir prácticas simples y probadas en el tiempo a Newspeak, que es hablado por la comunidad moderna de Android
Introduccion
Recientemente, se ha vuelto doloroso ver lo que está sucediendo en el mundo del desarrollo para plataformas móviles. La astronáutica arquitectónica está en auge, cada hipster considera que es su deber crear una nueva arquitectura y resolver una tarea simple, en lugar de dos líneas, insertar varios marcos de moda.
Estos sitios han completado tutoriales sobre marcos modernos y arquitecturas sofisticadas, pero ni siquiera existe una mejor práctica para los clientes REST de Android. Aunque este es uno de los casos de aplicación más frecuentes. Quiero que el enfoque normal del desarrollo también llegue a las masas. Por lo tanto, estoy escribiendo este artículo
¿Por qué las soluciones existentes son malas?
En general, el problema del nuevo MVP, VIPER y similares es exactamente el mismo, sus autores no saben cómo diseñar. Y sus seguidores, aún más. Y por lo tanto no entienden cosas importantes y obvias. Y se dedican a la
sobre ingeniería convencional.
1. La arquitectura debe ser simple
Cuanto más simple, mejor. Esto hace que sea más fácil de entender, más confiable y más flexible. Cualquier tonto puede volver a complicarse y hacer un montón de abstracciones, pero para hacerlo simplemente, debe pensar cuidadosamente.
2. El exceso de ingeniería es malo
Debe agregar un nuevo nivel de abstracción solo cuando el anterior no resuelva los problemas. Después de agregar un nuevo nivel, el sistema debería ser
más fácil de entender y
menos código. Si, por ejemplo, después de eso tuvo tres archivos en lugar de uno y el sistema se volvió más complicado, cometió un error y, de una manera simple,
escribió basura .
Los fanáticos de MVP, por ejemplo, en sus propios artículos escriben en texto plano que MVP estúpidamente conduce a una
complicación significativa
del sistema. Y lo justifican por el hecho de que es tan
flexible y
más fácil de mantener . Pero, como sabemos por el párrafo número 1, estas son cosas mutuamente excluyentes.
Ahora sobre VIPER, solo mira, por ejemplo, el diagrama de
este artículo.
¡Y esto es para cada pantalla! Me duelen los ojos. Simpatizo especialmente con aquellos que en el trabajo tienen que lidiar con esto en contra de su voluntad. Para aquellos que lo presentaron, simpatizo con razones
ligeramente diferentes .
Nuevo enfoque
Oye, también quiero un nombre de moda . Por lo tanto, la arquitectura propuesta se llama
RESS -
R equest,
E vent,
S creen,
S torage. Las letras y los nombres se detallan tan estúpidamente para obtener una palabra legible. Bueno, para no crear confusión con los nombres ya utilizados. Bueno, con REST sintonizado.
Inmediatamente haga una reserva, esta arquitectura es para clientes REST. Para otros tipos de aplicaciones, probablemente no funcionará.

1. Almacenamiento
Data Warehouse (en otros términos Modelo, Repositorio). Esta clase almacena datos y los procesa (guarda, carga, agrega a la base de datos, etc.), así como todos los datos del servicio REST primero llegan aquí, analizados y almacenados aquí.
2. Pantalla
La pantalla de la aplicación, en el caso de Android, esta es su Actividad. En otros términos, es un ViewController normal como el MVC de Apple.
3. Solicitud
La clase responsable de enviar solicitudes al servicio REST, así como de recibir respuestas y notificar sobre la respuesta de otros componentes del sistema.
4. Evento
El enlace entre los otros componentes. Por ejemplo, Solicitud envía un evento sobre la respuesta del servidor a quienes se suscriben. Y Storage envía un evento sobre cambios de datos.
El siguiente es un ejemplo de una implementación simplificada. El código se escribió con supuestos y no se verificó, por lo que puede haber errores de sintaxis y errores tipográficos.
Solicitudpublic 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();
Almacenamiento 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) {
Pantalla 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();
La aplicación public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); Request.registerListener(DataStorage.listener); } }
El mismo shemka, pero en términos de RESS, para entender Funciona así: cuando hace clic en el botón, el método deseado para Solicitar se contrae, Solicitud envía una solicitud al servidor, procesa la respuesta y notifica a DataStorage primero. DataStorage analiza la respuesta y almacena en caché los datos en casa. La solicitud luego notifica a la pantalla actualmente activa, la pantalla toma datos del almacenamiento de datos y actualiza la interfaz de usuario.
Pantalla de signos y cancelaciones de mediocridad en onResume y onPause, respectivamente. También actualiza la IU además de onResume. Que da Las notificaciones solo aparecen en la Actividad activa actual, no hay problemas para procesar la solicitud en segundo plano o para activar la Actividad. La actividad siempre estará actualizada. La notificación no alcanzará la actividad en segundo plano y, cuando regrese al estado activo, los datos se tomarán del Almacenamiento de datos. Como resultado, no hay problemas cuando gira la pantalla y recrea la Actividad.
Y para todo esto, la API predeterminada del SDK de Android es suficiente.
Preguntas y respuestas para futuras críticas
1. ¿Cuál es el beneficio?
Simplicidad real, flexibilidad, mantenibilidad, escalabilidad y un mínimo de dependencias. Siempre puede complicar una determinada parte del sistema si es necesario. ¿Demasiados datos? Rompe suavemente el DataStorage en varios. Enorme servicio REST API? Haga algunas solicitudes. ¿Listear es demasiado simple, incómodo y pasado de moda? Toma el EventBus. ¿Buscas de reojo en una barbería en HttpConnection? Bueno, toma Retrofit. ¿Actividad audaz con un montón de fragmentos? Solo considere que cada fragmento es Pantalla, o divídalo en subclases.
2. AsyncTask es un mal hombre, ¡toma al menos Retrofit!
¿Eh? ¿Y qué problemas causa en este código? ¿Fugas de memoria? No, aquí AsyncTask no almacena enlaces a activaciones, sino solo un enlace a un método estático. La respuesta está perdida? No, la respuesta siempre llega al DataStorage estático hasta que se cierra la aplicación. ¿Intentando actualizar la actividad en pausa? No, las notificaciones solo vienen en Actividad activa.
¿Y cómo puede ayudar Retrofit aquí? Solo mira
aquí . El autor tomó RxJava, Retrofit y todavía esculpe muletas para resolver un problema que RESS simplemente no tiene.
3. ¡La pantalla es el mismo ViewController! Necesito separar la lógica y la presentación, arrr!
Suelta este mantra ya. Un cliente típico para un servicio REST es una gran vista para el lado del servidor. Toda su lógica empresarial es establecer el estado correcto para un botón o campo de texto. ¿Qué vas a compartir allí? ¿Decir que será más fácil de mantener? ¿Mantener 3 archivos con 3 toneladas de código, en lugar de 1 archivo con 1 tonelada más fácil? Ok ¿Y si tenemos actividad con 5 fragmentos? Esto ya es 3 x (5 + 1) = 18 archivos.
La separación en Controlador y Vista en tales casos simplemente produce un montón de código sin sentido, es hora de admitirlo. Agregar funcionalidad a un proyecto con MVP es especialmente divertido: ¿desea agregar un controlador de botones? Ok, arregle Presentador, Actividad y Ver-interfaz. En RESS, para esto escribiré un par de líneas de código en un solo archivo.
¿Pero en grandes proyectos ViewController crece terriblemente? Entonces no has visto grandes proyectos. Su cliente REST para el próximo sitio con 5 mil líneas es un proyecto pequeño, y 5 mil líneas allí solo porque hay 5 clases en cada pantalla. Proyectos realmente grandes en RESS con más de 100 pantallas y varios equipos de 10 personas se sienten geniales. Solo haga algunas Solicitudes y Almacenamiento. Y Pantalla para pantallas en negrita contiene Pantalla adicional para elementos de IU grandes, por ejemplo, los mismos fragmentos. Un proyecto en un MVP de la misma escala simplemente se ahogará en un grupo de presentadores, interfaces, activaciones, fragmentos y conexiones no evidentes. Y la transición a VIPER generalmente hará que todo el equipo renuncie un día.
Conclusión
Espero que este artículo anime a muchos desarrolladores a reconsiderar sus puntos de vista sobre la arquitectura, no a producir abstracciones y buscar soluciones más simples y probadas con el tiempo.