En este Post mi intención no es agregar mas información a los miles de artículos que ya existen en la red, lo que busco es mostrar una manera sencilla de comprender la arquitectura Clean de la manera en como yo la comprendo, así como una serie de definiciones que he encontrado en la red y con eso poder tener un conocimiento inicial si es que estas aprendiendo esta arquitectura.
La arquitectura limpia es aquella en la que todas las capas que componen el software son independientes unas de otras de manera que, a la hora de ampliar las funcionalidades de un proyecto o cambiar un componente por otro, no haya conflicto y este cambio se realice con el menor coste de tiempo posible. Utilizando este concepto podemos lograr un desacoplamiento en las capas que componen un sistema.
os circulos concentricos representan las areas del software. En general, entre mas nos alejamos del centro mas elevado se vuelve el software. Los circulos exteriores son mecanismos y los interiores politicas. El objetivo de la arquitectura es poder escribir software que no este acoplado ni a nuestro modelo de datos, ni a representacion de los mismos, ni al framework en si. La idea es escribir software pensado en la logica del dominio y abstraernos de las implementaciones externas mas cercanas al framework. Utilizando estas arquitectura podriamos hacer una primera version del software que guarde los datos de forma local y de una forma sencilla cambiarlo a remoto.
La regla que permite que esta arquitectura funcione es la regla de la dependencia, la cual indica que las dependencias de codigo fuente deben de apuntar hacia adentro. Por lo tanto cumpliendo esta regla, nada dentro de los circulos centrales podran saber nada de los circulos exteriores. Lo que si puede ocurrir es que una capa externa dependa de una capa interna. Por ejemplo, nuestra persistencia si puede hacer useo de nuestra clase de Dominio, pero los casos de uso no deberia conocer nada de las SharedPreferences o de objetos de Firebase.
Utilizando Interface Adapters: Utilizando el patron de diseño Adapter crearemos interfaces que devuelvan objetos de dominio y adaptamos nuestras clases de infraestructura para que devuelvan ese contrato.
Capas
En la imagen podemos visualizar la división en tres capas .
- Presentation Layer: Se encarga de encapsular todas las operaciones relacionadas con el pintado de la vista.
- Domain Layer: Contiene todas aquellas operaciones y modelos que definen nuestra aplicacion.
- Data Layer: Acceso de datos que nos abstrae del origen de los datos.
Presentation Layer
Abstraernos del sistema de pintado de la interfaz es el objetivo principal de esta capa y es importante comprender que no solo estaremos hablando de Activities y Fragments, sino cualquier tipo de sistema delivery como:
- Notifications
- Widgets
- Otros sistemas de interaccion con el usuario.
Para lograr esta abstraccion podemos utilizar cualquier patron de diseño como MVVM o MVP.
Podemos visualizar que acorde a las reglas de dependencia, los presentadores hablarian con la capa de negocio y podrian recibir objetos de dominio.
Casos de uso y capa de dominio
En la capa de dominio definiremos las operaciones del software y contendra aquellos modelos que necesitamos para definir esas operaciones. Estableceremos dentro de la capa la entidad denominada casos de uso tambien conocida como interactor en esta capa definiremos las operaciones que puede realizar nuestro software. La utilizaremos como la definicion de reglas de negocio.
Como estandar las nombraremos con un verbo para identificarlas rapidamente. Es importante mencionar y tener en cuenta que NO realizaremos las operaciones directas sobre los modelos de negocio, por lo tanto solo es un orquestador sobre nuestro negocio.
Definicion de casos de uso: La definicion de estos casos la realizaremos a traves del patron Command con lo ejecutaremos tareas sobre la logica del dominio, en caso de que nuestros casos de uso sean muy complejos podemos tener modelos de dominio que contienen operacionessobre el mismo y los conoceremos como “rich model”.
El devolver objetos inmutables nos permite que los modelos deban hacerse siempre a partir de nuestras definiciones de negocioy que nadie pueda mutar un objeto desde una clase externa. Ejemplo:
- Creación de la clase
public class GetMiPedidosInteractor {
private OnGetPedidosListener listener;
@Inject
public GetMiPedidosInteractor() {
}
public void call() {
if (listener != null) {
listener.onGetPedidos(CinepolisApplication.getInstance().getPedidos());
}
}
public void setListener(OnGetPedidosListener listener) {
this.listener = listener;
}
public interface OnGetPedidosListener {
void onGetPedidos(List<PedidoAlimentos> pedidos);
}
}
2. Uso de la clase creada implementación.
public class MisPedidosPresenter extends SimpleDroidMVPPresenter<MisPedidosView, PedidoAlimentos> implements GetMiPedidosInteractor.OnGetPedidosListener {
private GetMiPedidosInteractor interactor;
@Inject
public MisPedidosPresenter(GetMiPedidosInteractor interactor) {
this.interactor = interactor;
this.interactor.setListener(this);
}
public void getMisPedidos() {
if (getMvpView() != null) {
getMvpView().showLoading();
interactor.call();
}
}
@Override
public void onGetPedidos(List<PedidoAlimentos> pedidos) {
if (getMvpView() != null) {
getMvpView().onGetPedidos(pedidos);
getMvpView().hideLoading();
}
}
public void borrarPedido(String codigoConfirmacion) {
CinepolisApplication.getInstance().borrarPedido(codigoConfirmacion);
}
}
Capa de datos
En esta capa debemos ser capaces de abstraernos del acceso a datos para abstraer la parte de representación, para evitar una invasión de la red o de la persistencia por todo nuestro software.
Utilizando Repository
Usando este patrón de diseño nuestra lógica de negocio permanecerá independiente del origen de los datos, así permitimos un acceso común y la posibilidad de ejecutar consultas de datos sobre una fuente de datos, trabajando siempre objetos de dominio.
En el diagrama podemos visualizar como es que el patrón esta formado por una parte del repositorio en si que nos permitira ejecutar consultas y obtener datos sobre los orignes de datos. También contamos con un Mapper, clase que nos convertirá el objeto de persistencia de dominio y viceversa. Además agregaremos el patrón adapter que envolverá nuestra dependencia del framework y la adaptará al repositorio que trabaja con objetos de dominio.
- Creamos la clase Repositorio
public class BookRepository{
private final LibrosStorage librosStorage;
public BooksRepository(Librosstorage librosStorage){
this.librosStorage = librosStorage;
}
public int getLastBook(){
return librosStorage.getLastBook();
}
2. Usamos el repositorio en lugar de acceder directamente al storage.
private final BooksRepository(BooksRepositiry booksRepository){
public ObtainLastBook(BooksRepository booksRepository){
this.booksRepository = booksRepository;
}
public int execute(){
return booksRepository.getLastBook();
}
}
Durante nuestras primeras implementacion de la arquitectura CLEAN siempre nos preguntamos, como es que debo de organizar mis carpetas, es por eso que a continuación presento una ejempleficacion de organizacion de carpetas dentro de Android Studio.
Como podemos visualizar en la imagen se han creado tres elemento con el nombre de cada capa.
- Presentation Paquete con las clases referentes a la capa de presentación.
- Data Bundle de recursos con la información perteneciente a nuestra capa de datos
- Domain Modulo que contiene la implementación del dominio
para mas información podemos revisar este repositorio de Fernando Ceja.
Incluso Google nos provee del siguiente repositorio para profundizar en el tema Android Architecture Blueprints
Muchas gracias por llegar hasta aquí, espero que este sencillo Post te haya ayudado a comprender un poco más acerca de la Arquitectura Clean, recuerda que hay muchos recursos en la la Red y este es uno más de los que te ayudarán a formar tu propio concepto.
MUCHAS GRACIAS Y HASTA LA PRÓXIMA (`∇´ゞ