¿Por qué es una buena idea que las capas de aplicación "inferiores" no sean conscientes de las "superiores"?

66

En una aplicación web MVC típica (bien diseñada), la base de datos no conoce el código del modelo, el código del modelo no conoce el código del controlador y el código del controlador no conoce el código de vista. (Me imagino que incluso podría comenzar tan lejos como el hardware, o quizás incluso más, y el patrón podría ser el mismo).

Yendo en la otra dirección, puedes ir solo una capa hacia abajo. La vista puede ser consciente del controlador pero no del modelo; el controlador puede conocer el modelo pero no la base de datos; el modelo puede ser consciente de la base de datos pero no del sistema operativo. (Cualquier cosa más profunda es probablemente irrelevante.)

intuitivamente puedo entender por qué es una buena idea pero no puedo expresarlo. ¿Por qué este estilo unidireccional de capas es una buena idea?

    
pregunta Jason Swett 20.05.2013 - 20:02

13 respuestas

120

Las capas, los módulos, y de hecho la arquitectura en sí, son medios para hacer que los programas de computadora sean más fáciles de entender para los humanos . El método numéricamente óptimo para resolver un problema es casi siempre un embrollo impío de código no modular, auto-referenciado o incluso auto-modificable, ya sea un código de ensamblador altamente optimizado en sistemas integrados con restricciones de memoria paralizantes o secuencias de ADN después de millones de años de la presión de selección. Tales sistemas no tienen capas, ni una dirección discernible del flujo de información, de hecho, ninguna estructura que podamos discernir en absoluto. Para todos menos su autor, parecen funcionar por pura magia.

En ingeniería de software, queremos evitar eso. La buena arquitectura es una decisión deliberada de sacrificar algo de eficiencia con el fin de hacer que el sistema sea comprensible para las personas normales. Entender una cosa a la vez es más fácil que entender dos cosas que solo tienen sentido cuando se usan juntas. Es por eso que los módulos y las capas son una buena idea.

Pero, inevitablemente, los módulos deben llamar a las funciones entre sí, y las capas deben crearse una encima de la otra. Entonces, en la práctica, siempre es necesario construir sistemas para que algunas partes requieran otras partes. El compromiso preferido es construirlos de tal manera que una parte requiera otra, pero esa parte no requiera la primera. Y esto es exactamente lo que nos ofrece la capa unidireccional: es posible entender el esquema de la base de datos sin conocer las reglas de negocios y entender las reglas de negocios sin saber acerca de la interfaz de usuario. Sería bueno tener independencia en ambas direcciones: permitir que alguien programe una nueva UI sin saber nada acerca de las reglas comerciales, pero en la práctica esto casi nunca es posible. Las reglas de oro como "No hay dependencias cíclicas" o "Las dependencias solo deben alcanzar un nivel" simplemente capturan el límite prácticamente factible de la idea fundamental de que una cosa a la vez es más fácil de entender que dos cosas.

    
respondido por el Kilian Foth 20.05.2013 - 20:30
61

La motivación fundamental es la siguiente: desea poder arrancar una capa completa y sustituirla por otra completamente diferente (reescrita), y NADIE DEBE (PODRÍA) AVISO LA DIFERENCIA.

El ejemplo más obvio es arrancar la capa inferior y sustituirla por una diferente. Esto es lo que haces cuando desarrollas la (s) capa (s) superior (es) en contra de una simulación del hardware y luego las sustituyes en el hardware real.

El siguiente ejemplo es cuando se extrae una capa intermedia y se sustituye por una capa intermedia diferente. Considere una aplicación que usa un protocolo que se ejecuta sobre RS-232. Un día, tiene que cambiar la codificación del protocolo completamente, porque "algo más cambió". (Ejemplo: cambiar de la codificación ASCII directa a la codificación Reed-Solomon de las transmisiones ASCII, porque estaba trabajando a través de un enlace de radio desde el centro de Los Ángeles a Marina Del Rey, y ahora está trabajando a través de un enlace de radio desde el centro de Los Ángeles a una sonda que orbita Europa , una de las lunas de Júpiter, y ese enlace necesita una corrección de errores hacia delante mucho mejor.)

La única forma de hacer que esto funcione es si cada capa exporta una interfaz conocida y definida a la capa superior y espera una interfaz conocida y definida a la capa inferior.

Ahora, no es exactamente el caso que las capas inferiores no sepan NADA sobre las capas superiores. Más bien, lo que la capa inferior sabe es que la capa inmediatamente superior funcionará de acuerdo con su interfaz definida. No puede saber nada más, porque, por definición, cualquier cosa que no esté en la interfaz definida está sujeta a cambios SIN PREVIO AVISO.

La capa RS-232 no sabe si está ejecutando ASCII, Reed-Solomon, Unicode (página de códigos en árabe, página de códigos en japonés, página de códigos de la versión beta de Rigellian) o qué. Simplemente sabe que está obteniendo una secuencia de bytes y está escribiendo esos bytes en un puerto. La próxima semana, podría estar obteniendo una secuencia de bytes completamente diferente de algo completamente diferente. A el no le importa Solo mueve bytes.

La primera (y mejor) explicación del diseño en capas es el papel clásico de Dijkstra "Estructura del El sistema de multiprogramación ". Se requiere la lectura en este negocio.

    
respondido por el John R. Strohm 20.05.2013 - 20:46
8

Debido a que los niveles superiores pueden cambiarán.

Cuando eso sucede, ya sea debido a cambios en los requisitos, los nuevos usuarios, las diferentes tecnologías, una aplicación modular (es decir, unidireccionalmente en capas) deberían requerir menos mantenimiento y adaptarse más fácilmente para satisfacer las nuevas necesidades.

    
respondido por el user39685 20.05.2013 - 20:25
4

Creo que la razón principal es que hace que las cosas se acoplen más estrechamente. Cuanto más apretado sea el acoplamiento, es más probable que surjan problemas más adelante. Consulte este artículo más información: Acoplamiento

Aquí hay un extracto:

  

Desventajas

     

Los sistemas estrechamente acoplados tienden a mostrar las siguientes características de desarrollo, que a menudo se consideran desventajas:   Un cambio en un módulo generalmente obliga a un efecto dominó de cambios en otros módulos.   El ensamblaje de módulos puede requerir más esfuerzo y / o tiempo debido a la mayor dependencia entre módulos.   Un módulo en particular puede ser más difícil de reutilizar y / o probar porque se deben incluir los módulos dependientes.

Habiendo dicho eso, la razón para tener un sistema acoplado más tenso es por razones de rendimiento. El artículo que mencioné también tiene información sobre esto también.

    
respondido por el barrem23 20.05.2013 - 20:59
4

OMI, es muy simple. No puedes reutilizar algo que sigue haciendo referencia al contexto en el que se usa.

    
respondido por el Erik Reppen 21.05.2013 - 04:10
4

Las capas no deben tener dependencias bidireccionales

Las ventajas de una arquitectura en capas son que las capas deben poder utilizarse de forma independiente:

  • debería poder crear una capa de presentación diferente además de la primera sin cambiar la capa inferior (por ejemplo, crear una capa API además de una interfaz web existente)
  • debería poder refactorizar o eventualmente reemplazar la capa inferior sin cambiar la capa superior

Estas condiciones son básicamente simétricas . Explican por qué generalmente es mejor tener una sola dirección de dependencia, pero no cuál .

La dirección de la dependencia debe seguir la dirección del comando

La razón por la que preferimos una estructura de dependencia descendente es porque los objetos superiores crean y utilizan los objetos inferiores . Una dependencia es básicamente una relación que significa "A depende de B si A no puede funcionar sin B". Entonces, si los objetos en A usan los objetos en B, así es como deberían ir las dependencias.

Esto es de alguna manera un tanto arbitrario. En otros patrones, como MVVM, el control fluye fácilmente desde las capas inferiores. Por ejemplo, puede configurar una etiqueta cuyo subtítulo visible esté vinculado a una variable y cambie con ella. Sin embargo, normalmente es preferible tener dependencias descendentes, ya que los objetos principales son siempre con los que interactúa el usuario y esos objetos hacen la mayoría del trabajo.

Mientras que de arriba a abajo usamos la invocación de métodos, desde abajo hacia arriba (típicamente) usamos eventos. Los eventos permiten que las dependencias vayan de arriba a abajo, incluso cuando el control fluye al revés. Los objetos de la capa superior se suscriben a eventos en la capa inferior. La capa inferior no sabe nada sobre la capa superior, que actúa como un complemento.

También hay otras formas de mantener una sola dirección, por ejemplo:

  • continuaciones (pasar un lambda o un método a ser llamado y evento a un método asíncrono)
  • crear subclases (cree una subclase en A de una clase primaria en B que luego se inyecta en la capa inferior, un poco como un complemento)
respondido por el Sklivvz 25.05.2013 - 00:13
3

Me gustaría agregar mis dos centavos a lo que Matt Fenwick y Kilian Foth ya han explicado.

Un principio de la arquitectura de software es que los programas complejos deben construirse componiendo bloques más pequeños y autocontenidos (cajas negras): esto minimiza las dependencias y reduce la complejidad. Por lo tanto, esta dependencia unidireccional es una buena idea porque facilita la comprensión del software, y el manejo de la complejidad es uno de los problemas más importantes en el desarrollo de software.

Entonces, en una arquitectura en capas, las capas inferiores son cajas negras que implementan capas de abstracción sobre las cuales se construyen las capas superiores. Si una capa inferior (por ejemplo, la capa B) puede ver los detalles de una capa superior A, entonces B ya no es una caja negra: sus detalles de implementación dependen de algunos detalles de su propio usuario, pero la idea de una caja negra es que ¡El contenido (su implementación) es irrelevante para su usuario!

    
respondido por el Giorgio 20.05.2013 - 20:55
3

Sólo por diversión.

Piensa en una pirámide de porristas. La fila inferior es compatible con las filas por encima de ellos.

Si la animadora en esa fila mira hacia abajo, están estables y se mantendrán en equilibrio para que los que están encima de ella no se caigan.

Si levanta la vista para ver cómo está todo el mundo por encima de ella, perderá el equilibrio y hará que se caiga toda la pila.

No es realmente técnico, pero fue una analogía que pensé podría ayudar.

    
respondido por el Bill Leeper 21.05.2013 - 19:08
3

Si bien la facilidad de comprensión y hasta cierto punto los componentes reemplazables son ciertamente buenas razones, una razón igualmente importante (y probablemente la razón por la que se inventaron las capas en primer lugar) es desde el punto de vista del mantenimiento del software. La conclusión es que las dependencias causan el potencial de romper cosas.

Por ejemplo, supongamos que A depende de B. Dado que nada depende de A, los desarrolladores tienen la libertad de cambiar A por el contenido de sus corazones sin tener que preocuparse de que puedan romper otra cosa que no sea A. Sin embargo, si el desarrollador desea cambiar B entonces, cualquier cambio en B que se realice podría potencialmente romper A. Este fue un problema frecuente en los primeros días de la computación (piense en un desarrollo estructurado) donde los desarrolladores corregirían un error en una parte del programa y generaría errores en partes aparentemente totalmente no relacionadas del programa en otro lugar. Todo por dependencias.

Para continuar con el ejemplo, ahora supongamos que A depende de B y B depende de A. IOW, una dependencia circular. Ahora, cada vez que se realiza un cambio en cualquier lugar, podría romper el otro módulo. Un cambio en B todavía podría romper A, pero ahora un cambio en A también podría romper B.

Entonces, en tu pregunta original, si estás en un equipo pequeño para un proyecto pequeño, todo esto es bastante exagerado porque puedes cambiar libremente los módulos a tu antojo. Sin embargo, si se encuentra en un proyecto importante, si todos los módulos dependen de los demás, cada vez que se necesita un cambio, podría romper los otros módulos. En un proyecto grande, conocer todos los impactos puede ser difícil de determinar, por lo que es probable que pierda algunos impactos.

Empeora en un proyecto grande donde hay muchos desarrolladores (por ejemplo, algunos que solo trabajan en la capa A, en la capa B y en la capa C). A medida que sea probable que cada cambio tenga que ser revisado / discutido con los miembros en las otras capas para asegurarse de que sus cambios no se rompan o obligen a volver a trabajar en lo que están trabajando. Si sus cambios obligan a los demás, entonces debe convencerlos de que deben hacer el cambio, ya que no querrán trabajar más solo porque tiene esta nueva forma de hacer las cosas en su módulo. IOW, una pesadilla burocrática.

Pero si limita las dependencias a A depende de B, B depende de C, solo las personas de la capa C deben coordinar sus cambios en ambos equipos. La capa B solo necesita coordinar los cambios con el equipo de la capa A y el equipo de la capa A es libre de hacer lo que quiera porque su código no afecta a la capa B o C. Así que, idealmente, diseñará sus capas para que la capa C cambie mucho. poco, la capa B cambia un poco y la capa A hace la mayoría de los cambios.

    
respondido por el Dunk 06.06.2013 - 16:32
1

La razón más básica por la que las capas inferiores no deberían ser conscientes de las capas superiores es que hay muchos más tipos de capas superiores. Por ejemplo, hay miles y miles de programas diferentes en su sistema Linux, pero llaman a la misma función C library malloc . Así que la dependencia es de estos programas a esa biblioteca.

Tenga en cuenta que las "capas inferiores" son en realidad las capas medias.

Piense en una aplicación que se comunique a través del mundo exterior a través de algunos controladores de dispositivo. El sistema operativo está en el centro .

El sistema operativo no depende de los detalles dentro de las aplicaciones, ni dentro de los controladores del dispositivo. Hay muchos tipos de controladores de dispositivo del mismo tipo y comparten el mismo marco de controladores de dispositivo. A veces, los piratas informáticos del kernel tienen que colocar algún caso especial en el marco de trabajo por el bien de un hardware o dispositivo en particular (ejemplo reciente que encontré: código específico de PL2303 en el marco de usb-serial de Linux). Cuando eso sucede, por lo general ponen comentarios sobre cuánto apesta y deben eliminarse. A pesar de que el sistema operativo llama a las funciones en los controladores, las llamadas pasan a través de enlaces que hacen que los controladores tengan el mismo aspecto, mientras que cuando los controladores llaman al sistema operativo, a menudo usan funciones específicas directamente por nombre.

Entonces, de alguna manera, el sistema operativo es realmente una capa inferior desde la perspectiva de la aplicación y desde la perspectiva de la aplicación: un tipo de centro de comunicación donde las cosas se conectan y los datos se cambian ir por los caminos apropiados. Ayuda al diseño del centro de comunicaciones para exportar un servicio flexible que puede ser utilizado por cualquier cosa, y no para mover ningún dispositivo o hacks específicos de la aplicación al centro.

    
respondido por el Kaz 21.05.2013 - 05:40
1

La separación de las preocupaciones y los enfoques de dividir / conquistar pueden ser otra explicación para estas preguntas. La separación de las inquietudes brinda la capacidad de portabilidad y, en algunas arquitecturas más complejas, le brinda a la plataforma un escalamiento independiente y ventajas de rendimiento.

En este contexto, si piensa en una arquitectura de 5 niveles (cliente, presentación, negocio, integración y nivel de recursos), el nivel más bajo de la arquitectura no debería ser consciente de la lógica y el negocio de los niveles más altos y viceversa. Me refiero a nivel inferior como integración y niveles de recursos. Las interfaces de integración de bases de datos proporcionadas en la integración y los servicios web y de bases de datos reales (proveedores de datos de terceros) pertenecen al nivel de recursos. Entonces, supongamos que cambiará su base de datos MySQL a una base de datos NoSQL como MangoDB en términos de escalabilidad o lo que sea.

En este enfoque, al nivel de negocio no le importa cómo el nivel de integración proporciona la conexión / transmisión por el recurso. Solo busca objetos de acceso a datos proporcionados por el nivel de integración. Esto podría expandirse a más escenarios, pero básicamente, la separación de preocupaciones podría ser la razón número uno para esto.

    
respondido por el Mahmut Canga 22.05.2013 - 00:52
1

Ampliando la respuesta de Kilian Foth, esta dirección de capas corresponde a una dirección en la que un humano explora un sistema.

Imagina que eres un nuevo desarrollador encargado de corregir un error en el sistema de capas.

Los errores suelen ser una falta de coincidencia entre lo que el cliente necesita y lo que recibe. A medida que el cliente se comunica con el sistema a través de la IU y obtiene el resultado a través de la IU (IU significa literalmente 'interfaz de usuario'), los errores también se informan en términos de IU. Así que, como desarrollador, no tienes más remedio que empezar a ver la interfaz de usuario también, para averiguar qué sucedió.

Por eso es necesario tener conexiones de capa de arriba a abajo. Ahora, ¿por qué no tenemos conexiones que van en ambos sentidos?

Bueno, tienes tres escenarios de cómo podría ocurrir ese error.

Puede ocurrir en el propio código de la interfaz de usuario y, por lo tanto, estar localizado allí. Esto es fácil, solo necesitas encontrar un lugar y arreglarlo.

Podría ocurrir en otras partes del sistema como resultado de las llamadas realizadas desde la interfaz de usuario. Lo que es moderadamente difícil, rastrea un árbol de llamadas, encuentra un lugar donde se produce el error y lo arregla.

Y podría ocurrir como resultado de una llamada INTO a su código de UI. Lo que es difícil, debe captar la llamada, encontrar su origen y luego averiguar dónde ocurre el error. Teniendo en cuenta que un punto en el que comienza está situado en el fondo de una sola rama de un árbol de llamadas Y que primero debe encontrar un árbol de llamadas correcto, podría haber varias llamadas al código de la interfaz de usuario.

Para eliminar el caso más difícil tanto como sea posible, las dependencias circulares están fuertemente desaconsejadas, las capas se conectan principalmente de manera descendente. Incluso cuando se necesita una conexión a la inversa, generalmente es limitada y está claramente definida. Por ejemplo, incluso con las devoluciones de llamada, que son una especie de conexión inversa, el código al que se llama en la devolución de llamada generalmente proporciona esta devolución de llamada en primer lugar, implementando una especie de "opt-in" para conexiones inversas, y limitando su impacto en la comprensión de un sistema.

Layering es una herramienta, dirigida principalmente a desarrolladores que admiten un sistema existente. Bueno, las conexiones entre capas también lo reflejan.

    
respondido por el Kaerber 06.06.2013 - 15:23
-1

Otra razón por la que me gustaría que se mencione explícitamente aquí es reutilizable del código . Ya hemos tenido el ejemplo de los medios RS232 que se reemplazan, así que vamos a dar un paso más ...

Imagina que estás desarrollando drivers. Es tu trabajo y escribes bastante. Los protocolos pueden comenzar a repetirse en algún momento, al igual que los medios físicos.

Entonces, lo que comenzarás a hacer, a menos que seas un gran fan de hacer lo mismo una y otra vez, es escribir capas reutilizables para estas cosas.

Supongamos que tiene que escribir 5 controladores para dispositivos Modbus. Uno de ellos usa Modbus TCP, dos usa Modbus en RS485 y el resto pasa por RS232. No vas a volver a implementar Modbus 5 veces, porque estás escribiendo 5 controladores. Además, no volverá a implementar Modbus 3 veces, porque tiene 3 capas físicas diferentes debajo de usted.

Lo que haces es escribir un acceso a medios TCP, un acceso a medios RS485 y posiblemente un acceso a medios RS232. ¿Es inteligente saber que en este punto habrá una capa de modbus arriba? Probablemente no. El siguiente controlador que implementará también puede usar Ethernet, pero usa HTTP-REST. Sería una pena si tuviera que volver a implementar Ethernet Media Access para poder comunicarse a través de HTTP.

Una capa arriba, implementará Modbus solo una vez. Esa capa Modbus, una vez más, no sabrá de los controladores, que son una capa superior. Estos controladores, por supuesto, tendrán que saber que se supone que deben hablar de modbus, y se supone que deben saber que usan Ethernet. Howevever implementó la forma en que lo describí, no solo podría arrancar una capa y reemplazarla. por supuesto, podría, y para mí es el mayor beneficio de todos, seguir adelante y reutilizar esa capa Ethernet existente para algo que no tenga nada que ver con el proyecto que originó su creación.

Esto es algo, probablemente los vemos todos los días como desarrolladores y eso nos ahorra muchísimo tiempo. Hay innumerables bibliotecas para todo tipo de protocolos y otras cosas. Estos existen debido a principios como la dirección de dependencia que sigue a la dirección del comando, lo que nos permite crear capas de software reutilizables.

    
respondido por el juwi 02.02.2014 - 21:20

Lea otras preguntas en las etiquetas