Introducción
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
¿Por qué fallan nuestros diseños?
¿Qué te viene a la mente cuando oyes la palabra caos? Quizá pienses en una ruidosa bolsa de valores, o en tu cocina por la mañana: todo confuso y revuelto. Cuando piensas en la palabra orden, quizá pienses en una habitación vacía, serena y tranquila. Sin embargo, para los científicos, el caos se caracteriza por la homogeneidad (igualdad) y el orden por la complejidad (diferencia).
Por ejemplo, un jardín bien cuidado es un sistema muy ordenado. Los jardineros definen los límites con caminos y vallas, y delimitan los parterres de flores o los huertos. Con el tiempo, el jardín evoluciona, haciéndose más rico y espeso; pero sin un esfuerzo deliberado, el jardín se volverá salvaje. Las malas hierbas y las gramíneas ahogarán a otras plantas, cubriendo los caminos, hasta que al final todas las partes vuelvan a tener el mismo aspecto: salvaje y desordenado.
Los sistemas de software también tienden al caos. Cuando empezamos a construir un nuevo sistema, tenemos la gran idea de que nuestro código será limpio y estará bien ordenado, pero con el tiempo descubrimos que va acumulando desechos y casos de perímetro y acaba siendo una confusa maraña de clases gestoras y módulos de utilidades. Descubrimos que nuestra arquitectura de capas sensatas se ha derrumbado sobre sí misma como una bagatela demasiado empapada. Los sistemas de software caóticos se caracterizan por la uniformidad de sus funciones: Manejadores de API que tienen conocimiento del dominio y envían correo electrónico y realizan registros; clases de "lógica empresarial" que no realizan cálculos pero sí realizan E/S; y todo acoplado a todo lo demás, de modo que cambiar cualquier parte del sistema se convierte en algo peligroso. Esto es tan común que los ingenieros de software tienen su propio término para el caos: el antipatrón de la Gran Bola de Barro(Figura P-1).
Consejo
Una gran bola de barro es el estado natural del software, del mismo modo que la naturaleza salvaje es el estado natural de tu jardín. Se necesita energía y dirección para evitar el colapso.
Afortunadamente, las técnicas para evitar crear una gran bola de barro no son complejas.
Encapsulación y abstracciones
La encapsulación y la abstracción son herramientas a las que todos acudimos instintivamente como programadores, aunque no todos utilicemos estas palabras exactas. Permítenos detenernos en ellas un momento, ya que son un tema recurrente de fondo del libro.
El término encapsulación abarca dos ideas estrechamente relacionadas: simplificar el comportamiento y ocultar los datos. En esta discusión, utilizaremos el primer sentido. Encapsulamos el comportamiento identificando una tarea que debe realizarse en nuestro código y asignando esa tarea a un objeto o función bien definidos. Llamamosabstracción a ese objeto o función.
Echa un vistazo a los dos siguientes fragmentos de código Python:
Haz una búsqueda con urllib
import
json
from
urllib.request
import
urlopen
from
urllib.parse
import
urlencode
params
=
dict
(
q
=
'Sausages'
,
format
=
'json'
)
handle
=
urlopen
(
'http://api.duckduckgo.com'
+
'?'
+
urlencode
(
params
))
raw_text
=
handle
.
read
()
.
decode
(
'utf8'
)
parsed
=
json
.
loads
(
raw_text
)
results
=
parsed
[
'RelatedTopics'
]
for
r
in
results
:
if
'Text'
in
r
:
(
r
[
'FirstURL'
]
+
' - '
+
r
[
'Text'
])
Haz una búsqueda con peticiones
import
requests
params
=
dict
(
q
=
'Sausages'
,
format
=
'json'
)
parsed
=
requests
.
get
(
'http://api.duckduckgo.com/'
,
params
=
params
)
.
json
()
results
=
parsed
[
'RelatedTopics'
]
for
r
in
results
:
if
'Text'
in
r
:
(
r
[
'FirstURL'
]
+
' - '
+
r
[
'Text'
])
Ambos listados de códigos hacen lo mismo: envían valores codificados en un formulario a una URL para utilizar la API de un motor de búsqueda. Pero el segundo es más sencillo de leer y comprender porque opera a un nivel de abstracción superior.
Podemos dar un paso más identificando y nombrando la tarea que queremos que el código realice por nosotros y utilizando una abstracción de nivel aún más alto para hacerla explícita:
Haz una búsqueda con el módulo duckduckgo
import
duckduckgo
for
r
in
duckduckgo
.
query
(
'Sausages'
)
.
results
:
(
r
.
url
+
' - '
+
r
.
text
)
Encapsular el comportamiento mediante abstracciones es una poderosa herramienta para hacer que el código sea más expresivo, más comprobable y más fácil de mantener.
Nota
En la literatura del mundo orientado a objetos (OO), una de las caracterizaciones clásicas de este enfoque se denomina diseño impulsado por la responsabilidad; utiliza las palabras funciones y responsabilidades en lugar de tareas. El punto principal es pensar en el código en términos de comportamiento, en lugar de en términos de datos o algoritmos.1
La mayoría de los patrones de este libro implican elegir una abstracción, por lo que verás muchos ejemplos en cada capítulo. Además,el Capítulo 3 trata específicamente de algunas heurísticas generales para elegir abstracciones.
Capas
La encapsulación y la abstracción nos ayudan ocultando detalles y protegiendo la coherencia de nuestros datos, pero también debemos prestar atención a las interacciones entre nuestros objetos y funciones. Cuando una función, módulo u objeto utiliza a otro, decimos que uno depende del otro. Estas dependencias forman una especie de red o grafo.
En una gran bola de barro, las dependencias están fuera de control (como has visto enla Figura P-1). Cambiar un nodo del gráfico se hace difícil porque tiene el potencial de afectar a muchas otras partes del sistema. Las arquitecturas en capas son una forma de abordar este problema. En una arquitectura por capas, dividimos nuestro código en categorías discretas o roles, e introducimos reglas sobre qué categorías de código pueden llamarse entre sí.
Uno de los ejemplos más comunes es la arquitectura de tres capas que se muestra enla Figura P-2.
[ditaa,apwp_0002] +----------------------------------------------------+ | Presentation Layer | +----------------------------------------------------+ | V +----------------------------------------------------+ | Business Logic | +----------------------------------------------------+ | V +----------------------------------------------------+ | Database Layer | +----------------------------------------------------+
La arquitectura en capas es quizá el modelo más común para construir software empresarial. En este modelo tenemos componentes de interfaz de usuario, que pueden ser una página web, una API o una línea de comandos; estos componentes de interfaz de usuario se comunican con una capa de lógica empresarial que contiene nuestras reglas empresariales y nuestros flujos de trabajo; y, por último, tenemos una capa de base de datos que se encarga de almacenar y recuperar los datos.
Durante el resto de este libro, vamos a dar la vuelta sistemáticamente a este modelo obedeciendo a un sencillo principio.
El principio de inversión de la dependencia
Puede que ya conozcas el principio de inversión de la dependencia (PID), porque es la D de SOLID.2
Lamentablemente, no podemos ilustrar la DIP mediante tres pequeños listados de código, como hicimos con la encapsulación. Sin embargo, toda la Parte I es esencialmente un ejemplo práctico de aplicación de la DIP en una aplicación, así que tendrás tu ración de ejemplos concretos.
Mientras tanto, podemos hablar de la definición formal de DIP:
-
Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de abstracciones.
-
Las abstracciones no deben depender de los detalles. Por el contrario, los detalles deben depender de las abstracciones.
Pero, ¿qué significa esto? Vayamos por partes.
Los módulos dealto nivel son el código que realmente interesa a tu organización. Quizás trabajes para una empresa farmacéutica, y tus módulos de alto nivel se ocupan de los pacientes y los ensayos. Tal vez trabajes para un banco, y tus módulos de alto nivel gestionen operaciones e intercambios. Los módulos de alto nivel de un sistema de software son las funciones, clases y paquetes que se ocupan de nuestros conceptos del mundo real.
En cambio, los módulos de bajo nivel son el código que no interesa a tu organización. Es poco probable que tu departamento de RRHH se entusiasme con los sistemas de archivos o los sockets de red. No es frecuente que hables de SMTP, HTTP o AMQP con tu equipo financiero. Para nuestros interlocutores no técnicos, estos conceptos de bajo nivel no son interesantes ni relevantes. Lo único que les importa es si los conceptos de alto nivel funcionan correctamente. Si la nómina se ejecuta a tiempo, es poco probable que a tu empresa le importe si se trata de una tarea cron o de una función transitoria que se ejecuta en Kubernetes.
Depende de no significa necesariamente importaciones o llamadas, sino una idea más general de que un módulo conoce o necesita a otro módulo.
Y ya hemos mencionado las abstracciones: son interfaces simplificadas que encapsulan el comportamiento, del mismo modo que nuestro módulo duckduckgo encapsulaba la API de un motor de búsqueda.
Todos los problemas en informática pueden resolverse añadiendo otro nivel de indirección.
David Wheeler
Así que la primera parte del DIP dice que nuestro código empresarial no debe depender de detalles técnicos; en su lugar, ambos deben utilizar abstracciones.
¿Por qué? A grandes rasgos, porque queremos poder cambiarlos independientemente unos de otros. Los módulos de alto nivel deben ser fáciles de cambiar en respuesta a las necesidades del negocio. Los módulos de bajo nivel (detalles) suelen ser, en la práctica, más difíciles de cambiar: piensa en refactorizar para cambiar el nombre de una función frente a definir, probar e implementar una migración de base de datos para cambiar el nombre de una columna. No queremos que los cambios en la lógica empresarial se ralenticen porque estén estrechamente acoplados a detalles de infraestructura de bajo nivel. Pero, del mismo modo, es importante poder cambiar los detalles de tu infraestructura cuando lo necesites (piensa en fragmentar una base de datos, por ejemplo), sin necesidad de hacer cambios en tu capa de negocio. Añadir una abstracción entre ellas (la famosa capa extra de indirección) permite que ambas cambien (más) independientemente la una de la otra.
La segunda parte es aún más misteriosa. "Las abstracciones no deben depender de los detalles" parece bastante claro, pero "Los detalles deben depender de las abstracciones" es difícil de imaginar. ¿Cómo podemos tener una abstracción que no dependa de los detalles que abstrae? Cuando lleguemos al Capítulo 4, tendremos un ejemplo concreto que debería aclarar un poco todo esto.
Un lugar para toda nuestra lógica empresarial: El Modelo de Dominio
Pero antes de que podamos darle la vuelta a nuestra arquitectura de tres capas, tenemos que hablar más de esa capa intermedia: los módulos de alto nivel o lógica empresarial. Una de las razones más comunes por las que nuestros diseños salen mal es que la lógica empresarial se dispersa por las capas de nuestra aplicación, lo que dificulta su identificación, comprensión y modificación.
El Capítulo 1 muestra cómo construir una capa de negocio con un patrón de Modelo de Dominio. El resto de los patrones de la Parte I muestran cómo podemos mantener el modelo de dominio fácil de cambiar y libre de preocupaciones de bajo nivel eligiendo las abstracciones adecuadas y aplicando continuamente el DIP.
1 Si te has topado con tarjetas de responsabilidad-colaborador en clase (CRC), apuntan a lo mismo: pensar en las responsabilidades te ayuda a decidir cómo repartir las cosas.
2 SOLID es un acrónimo de los cinco principios de diseño orientado a objetos de Robert C. Martin: responsabilidad única, abierto a la extensión pero cerrado a la modificación, sustitución de Liskov, segregación de interfaces e inversión de dependencias. Véase "S.O.L.I.D: Los 5 primeros principios del diseño orientado a objetos", de Samuel Oloruntoba.
Get Patrones de Arquitectura con Python now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.