¡Maravilloso este punto, tantas opciones para elegir puede hacer que tomes una decisión no acertada… pero si basamos nuestra decisión en la experiencia ajena (exitosamente probada), seguro que llegamos a puerto seguro!
Dominio: Arquitectura de Software.
Primer paso para crear una arquitectura. Pararse en hombros de gigantes Aprovechar el conocimiento existente para nuestra solución.
- Productos “de la estantería”. Productos ya echos que resuelvan parte de nuestros problemas.
- Frameworks y librerías. Ayuda a empezar/proponer desde una arquitectura más especifica.
- Arquitecturas especificas del dominio. Decisiones de diseño ya tomadas para ciertos dominios del problema.
- Patrones de arquitectura. Empezar desde un punto mas solido y restringir nuestro diseño a las partes importantes que quedan por resolver.
Herramientas y partes de un diseño: Tipos de conectores
La arquitectura está separada en dos partes fundamentales:
- Componentes: Son partes de nuestro sistema que cumplen una función específica. Estos mismos componentes “modulares” pueden estar formados por más componentes, ya bien objetos o capas, que actuan como subcomponentes en su interior. La comunicación existente entre ellos se lleva a cabo por medio de conectores.
- Conectores: Estos no están asociados a un dominio específico y son independientes a la hora de su análisis, pudiendo un e-commerce o una red social el mismo tipo de conector.
Tipos de conectores:
- Llamado a procedimiento: Invocan de un componente a otro componente y esperan una respuesta.
- Enlace: Vinculan fuertemente un componente a otro, incluso para la compilación. Visto en lenguajes compilados, y en componentes que forman parte de un monolito
- Evento: Permiten a un componente notificar un evento (que algo sucedió), y a otros componentes escuchar y reaccionar ante un evento.
- Adaptador: Ayudan a compatibilizar la interfaz de un componente con la de otro componente
- Acceso a datos: Nos ayudan a acceder a recursos compartidos de datos, como APIs, sistemas de archivos y bases de datos. Compatibiliza la interfaz del dato con la interfaz que espera el componente que estamos usando.
- Flujo: Permite la recolección de datos en un flujo de información continuo por parte de otro componente que tiene intereses en obtener varios o todos los datos del flujo.
- Arbitraje: Coordinan los permisos de acceso a un recurso entre componentes y deciden quien se encarga de distribuir dichos comportamientos. Ej: Test A/B, teniendo varios componentes disponibles recibimos un pedido y se decide qué versión enviar para comparar diferentes atributos de calidad.
- Distribuidor: Facilita la distribución de un mensaje a varios componentes a través de un solo conector.
Conectores
Llamado asincrónico / sincrónico. Modelo Cliente servidor
- Llamado asincrónico: Un componente llama a otro. pero no espera a que termine esta ejecución, sigue con su trabajo y en algún momento evaluara cual fue el resultado. Típico en sistemas desconectados.
- Llamado sincrónico: El emisor envía un mensaje al receptor y no sigue ejecutando hasta que no reciba el resultado. Típico en lenguajes orientado a objetos
- Cliente/Servidor: La comunicación es del cliente al servidor y el cliente queda esperando la respuesta.
Enrutador, difusión
- Enrutador: Facilita la conexión entre un componente que emite un mensaje y entre un set especifico de componentes que les interesa el mensaje. El enrutador sabe de entre todos los componentes a cuales les interesa.
- Difusión: Dado el mensaje de un emisor, lo difunde a muchos otros componentes interesados.
Pizarra, repositorio, colas, modelo PUBSUB
- Cola. Cuando se tiene un productor que tiene mas velocidad que el consumidos. Para conectarlos, se debe compatibilizar su velocidad. Para eso se debe encolar/agendar el procesamiento de los mensajes. y por eso el consumidor puede ir leyendo esos mensajes a medida que su velocidad se lo permita.
- Repositorio / Pizarra. Orientado a escribir o leer datos de un componente que funciona como base de datos
- Publicar suscribir (PUBSUB). Permite mandar mensajes desde un componente que publica eventos a uno que se suscriba a esos eventos sin necesidad que se conozcan.
Escenarios y tácticas
Framework de diseño orientado a atributos plantea una estructura de Escenarios y tácticas en donde cada escenario ayudará a conectar atributos con diferentes tácticas de implementación.
La estructura básica de todo escenario del framework en donde un escenario que va a estar asociado a un atributo de calidad especifico va a plantear un estímulo, el cual va a tener que ver con algo que afecta directamente a este atributo de calidad y luego va a plantear diferentes tácticas para controlar la respuesta a este estímulo, por último la respuesta es lo que esperamos o nuestro caso de éxito como pudimos resolver este estimulo con la implementación de algunas de estas tácticas.
Escenarios: Disponibilidad, detección, reparación
Escenario de disponibilidad. En este caso el estímulo es la falla, algo pasó que compromete la disponibilidad. vamos a ver las diferentes tácticas que podemos usar para trabajar con este posible escenario.
Detección, en este caso contamos con varias tácticas:
- Ping / Eco. que se trata de como un componente envía un mensaje genérico a otro componente para saber si el otro componente esta disponible o no.
- Latido. Esta táctica es similar pero en vez de que haya interacción entre dos componentes, cada uno de estos envían una señal propia que indica que continua activo.
- Excepciones. Un método para reconocer fallas es encontrar una excepción, que se produce cuando se reconoce una de las clases de fallas. El manejador de excepciones generalmente se ejecuta en el mismo proceso que introdujo la excepción.
Recuperación, como podemos estar listos para que si algo falla podamos recuperar rápidamente el sistema.
- Votación. El algoritmo de votación puede ser “reglas de mayoría” o “componente preferido” o algún otro algoritmo. Este método se usa para corregir el funcionamiento defectuoso de algoritmos o fallas de un procesador y se usa a menudo en sistemas de control.
- Redundancia activa. Cuando se produce una falla, el tiempo de inactividad de los sistemas que utilizan esta táctica suele ser de milisegundos, ya que la copia de seguridad es actual y el único momento de recuperación es el tiempo de conmutación. La redundancia activa a menudo se utiliza en una configuración cliente / servidor, como los sistemas de administración de bases de datos, donde las respuestas rápidas son necesarias incluso cuando ocurre una falla.
- Redundancia pasiva. Un componente (el primario) responde a los eventos e informa a los otros componentes (los recursos) de las actualizaciones de estado que deben realizar. Cuando ocurre una falla, el sistema primero debe asegurarse de que el estado de la copia de seguridad sea lo suficientemente reciente antes de reanudar los servicios.
- Repuesto. Una plataforma de computación de reserva en espera está configurada para reemplazar muchos componentes diferentes que fallaron. Debe reiniciarse a la configuración de software apropiada y debe tener su estado inicializado cuando ocurre una falla.
Escenarios: Reintroducción y prevención
Reintroducción, Hay tácticas de reparación que se basan en la reintroducción de componentes. Cuando un componente redundante falla, puede reintroducirse después de haber sido corregido. Tales tácticas son el funcionamiento en la sombra, la resincronización del estado y la reversión.
- Modo sombra. Un componente previamente fallido puede ejecutarse en “modo sombra” durante un corto período de tiempo para asegurarse de que imita el comportamiento de los componentes en funcionamiento antes de restaurarlo al servicio.
- Resincronización del estado. Las tácticas de redundancia pasiva y activa requieren que el componente que se está restaurando tenga su estado actualizado antes de su regreso al servicio.
- Punto de control / retroceso. Un punto de control es una grabación de un estado consistente creado periódicamente o en respuesta a eventos específicos.
Prevención, Las siguientes son algunas tácticas de prevención de fallas.
- Remoción del servicio. Esta táctica elimina un componente del sistema de la operación para someterse a algunas actividades para evitar fallas anticipadas. Un ejemplo es reiniciar un componente para evitar que las pérdidas de memoria causen una falla.
- Transacciones. Una transacción es la agrupación de varios pasos secuenciales, de modo que todo el paquete se puede deshacer a la vez. Las transacciones se utilizan para evitar que cualquier dato se vea afectado si falla un paso de un proceso y también para evitar colisiones entre varios subprocesos simultáneos que acceden a los mismos datos.
- Monitor de proceso. Una vez que se ha detectado un error en un proceso, un proceso de supervisión puede eliminar el proceso no productivo y crear una nueva instancia del mismo, inicializado en un estado apropiado como en la táctica de repuesto.
Escenarios: Mantenibilidad
En este caso el estimulo es un pedido de cambio (llega un requerimiento y tenemos que cambiar el sistema).
Familias de tácticas:
Confinar modificaciones. Las tácticas van a intentar trabajar sobre nuestros módulos para que cada cambio que nos pidan esté confinado a sólo un módulo. Cuando logramos esto logramos que las dependencias entre módulos sean más ligeras y el cambio que nos proponen no afecte a muchas partes del sistema.
- Coherencia semántica. Habla de la relación entre las responsabilidades de los módulos. Hablamos de acoplamiento y cohesión. Si logramos encontrar la cohesión en un módulo entonces vamos a poder hacer que ese módulo sea más mantenible. De lo contrario es posible que ese módulo cambie por diferentes razones. Abstraer servicios comunes. Cuando encontramos que la aplicación tiene servicios más genéricos de no necesario podemos abstraerlos a un punto común y que las dependencias vayan de los módulos cohesivos a un servicio o modulo externo.
- Generalizar. Al generalizar un módulo podemos separar lo específico de lo genérico.
- Limitar opciones disponibles. Limitar el rango de modificación nos ayuda a que sea más mantenible.
- Anticipar cambios. Prepararnos para algún cambio que nosotros mismo sepamos que se deberá de dar en el futuro tomando en cuenta una estrategia sobre como incorporar el nuevo cambio. Los patrones de diseño suelen estar orientados a esto (Patrón estrategia).
Prevenir efectos domino. Trabaja estrictamente con las dependencias, es decir cuando podemos detectar que un cambio generaría problemas en otros módulos o dependencias.
Diferir enlace. Cómo podemos hacer para que un cambio en el código no requiera desplegar toda la aplicación.
Escenarios: Prevenir efectos dominó y diferir enlace
Prevenir efectos dominó. Trabaja estrictamente con las dependencias. Es decir, cuándo podemos detectar que un cambio generaría problemas en otros módulos u otras dependencias.
- Ocultar información. Cualquier módulo u objeto que diseñemos, tenga la capacidad de ocultar cierta parte de la información para que los agentes externos no dependan de esa información puntual sino de una interfaz clara que no puedan cambiar por más que la información cambie. De esta forma podemos garantizar que, si el cambio de la información es importante, los dependientes no necesiten cambiar porque están pasando por una interfaz que no cambió.
- Mantener la interfaz. Si tengo un servicio que hace algo, la dependencia a ese servicio va a ser a través de una interfaz clara, de lo contrario cualquier acción cuando cambie puede poner en riesgo el módulo.
- Restringir comunicación. Para generar sistemas que estén acoplados de forma ligeras, en vez de conocer las dependencias de tus dependencias, siempre te limites a tus dependencias directas, de esta forma cualquier cambio en la forma que tus dependencias trabajan no afecta al módulo en el que estás trabajando.
- Intermediarios. Hablamos de un punto donde podamos compatibilizar a un módulo con otro y si dejan de ser compatibles, estos intermediarios puedan servir como punto de compatibilidad.
Diferir enlace. Habla sobre cómo podemos hacer para que un cambio en nuestro código no requiera desplegar toda la aplicación completa.
- Registro en ejecución. Cuando un módulo o servicio depende de otro, si dependen fuertemente van a requerir estar compilados juntos. Si nosotros podemos diferir esa compilación y que se registre un servicio en momento de ejecución, es decir que se ponga disponible a sus dependencias en el momento de ejecución, podemos hacer que estos servicios se puedan desplegar independientemente.
- Archivos de configuración. Van a servir para en momento de ejecución saber cómo conectar varias partes. Es imprescindible que nuestros módulos dependan de interfaces y no de implementaciones específicas.
- Polimorfismos. Un objeto pueda comportarse de forma diferente en base a su estado. A través del polimorfismo podemos postergar la forma en que se resuelve un problema dependiendo de qué instancia del objeto será.
- Reemplazo de componentes. Tener la capacidad de desplegar un componente y luego desplegar su reemplazo, o quizás otro componente que respete esa interfaz, y que todo el resto de nuestra aplicación no necesite cambiar.
- Adherir a protocolos. Nos permite tener un protocolo claro entre dos módulos y no necesitar saber la instancia específica o el tipo específico de un módulo.
Escenarios: Eficiencia de ejecución
En el caso de eficiencia de ejecución vamos a tener eventos ingresando a nuestro sistema como estímulo y luego vamos a trabajar sobre las tácticas para controlar esta eficiencia para que la respuesta sea dentro del tiempo esperado.
Demanda de recursos. Trata sobre cuándo entra un evento, cómo hacemos para que ese evento tenga los recursos disponibles y cuánto de esos recursos necesita.
- Mejorar la eficiencia computacional. Podemos analizar nuestros algoritmos y podemos analizar nuestro procedimiento para encontrar cuáles son los puntos en dónde no estamos siendo eficientes.
- Reducir sobrecarga. Habla sobre cuántos pasos o qué acciones estamos tomando para una misma tarea o responder a un mismo evento.
- Manejar tasa de eventos. Cuántos eventos vamos a emitir a un componente específico y si es necesario ser tan fino en estos eventos. Si podemos reducir esa tasa de eventos podemos optimizar esa comunicación.
- Frecuencia de muestreo. Si yo sí estoy recibiendo todo este stream, cómo puedo hacer para decidir procesar esos eventos en una forma grupal, entonces en lugar de hacer una tarea por evento puedo hacer una tarea cada cierta cantidad de tiempo y agrupar todos los eventos en una tarea única puedo hacer mejor uso de los recursos procesando una sola vez un grupo de eventos.
Gestión de recursos. Cómo ponemos disponibles más o menos recursos y cómo hacemos para que estén cuando se le necesitan.
- Concurrencia. Trabajamos sobre cómo paralelizar nuestro proceso para que se pueda responder en menor tiempo usando recursos de forma paralela o en menor tiempo.
- Réplicas. Cómo podemos duplicar el procesamiento o los datos para hacer más accesibles estos recursos a nuestro proceso.
- Aumentar recursos. El poder medir y decidir cuándo crecer la cantidad de recursos que tenemos disponibles.
- Políticas de planificación de tareas. Yo puedo decidir que cada recurso tiene que responder en el momento entonces tiene que tener un acceso sincrónico, un acceso prioritario a cada uno de esos recursos o puede postergar tareas y agendarlas para que se hagan en un momento futuro. Incluso puedo priorizar esos pedidos paralelos y decidir si un pedido es más importante que otro o el orden en que se van a resolver.
Escenarios: Seguridad
Nuestro estimulo de entrada es un ataque, nuestras tácticas para controlar la seguridad y tener como resultado la detección, resistencia o recuperación en nuestro sistema. Tendremos tres familias:
- Detectar ataques: Van a tratar de identificar que el estado actual de la aplicación está bajo un ataque, puede ser por medio de sensores.
- Detectores de intrusos: Ayuda a tener implementaciones para saber cuándo se está usando de manera no apropiada. Levantará alertas para tomar decisiones sobre nuestro sistema (pueden ser complejas, automatizadas o simples). Podremos ir descendiendo en niveles cuanto más complejo sea.
Recuperación de ataques: Trata de tener bases o tácticas para regresar a un estado basal y también poder comprender cuáles fueron las acciones del atacante para poder evitarlas a futuro
- Restauración: Cómo hacemos para entender nuestros estados del sistema y entender qué pasa con mi sistema. Es muy similar a la estrategia de Disponibilidad (estados conocidos, estados diferentes para comparar)
- Identificación: Saber qué específicamente hizo el atacante. La traza de auditoria sirve para que cuando detectamos un atacante tengamos todo el rastro de nuestro usuario y poder restablecer mi sistema a ese punto basal, ignorando lo que hizo el atacante
Resistencia de ataques: Va a tratar que el atacante no tenga éxito.
- Autenticación: Cómo sabemos que el usuario es quien realmente die ser (Contraseñas, datos biométricos, RS, etc.)
- Autorización: Saber quién es y qué puede o no hacer esa persona (Roles entre sistemas)
- Confidencialidad de datos: Cómo garantizamos que el dato sea visto por quien debe verlo (encriptación)
- Integridad: Cómo garantizo que el mensaje es íntegro, es decir, cómo el emisor lo envió (Hashes para comprobar información)
- Limitar exposición: Si un atacante entra podemos determinar que este no pueda (por lo menos) entrar a consultar la información mas sensible del usuario. Lo podemos hacer separando información (separar info sensible de info “normal”).
- Limitar acceso: Entender cuáles son los vectores de acceso y restringir lo menor posible esos accesos que pueden ser puntos iniciales de penetración (Puertos de red: 8080, SSH, etc.)
Escenarios: Capacidad de prueba
Capacidad de prueba: Nuestro estimulo de entrada será una nueva funcionalidad para implementar, tendremos técnicas para controlar la capacidad de prueba y nuestro resultado será detectar fallas para repararla y volver a iterar. Tendremos dos grandes familias, entradas/salidas y monitoreo que tiene en cuenta más que nada la ejecución.
Entrada/Salida: Cómo hacer para dado un estímulo de entrada, evaluar una salida.
- Captura y reproducción: Sirve para, en un escenario de comunicación, grabarla para poder usar esa comunicación en un test de prueba. De esta forma podemos garantizar que el uso normal está cubierto por un test y sabemos qué es lo que tiene que dar mi sistema. Es muy útil cuando queremos trabajar con sistemas externos. VCR (librería: video cam recording) es una herramienta muy útil aquí.
- Separar la interfaz de la implementación: De esta forma podemos evaluar si la implementación está recibiendo lo que se espera. Podemos hacer pruebas con diferentes implementaciones.
- Acceso exclusivo para pruebas: Trata sobre partes de la aplicación que no podemos funcionar desde fuera de la aplicación, para esto es posible que tenga que escribir código específico para el contexto de test, es importante garantizar que no llegue a ambientes productivos. Son útiles para probar microservicios.
Monitoreo: Tendrá en cuenta la ejecución y que se está ejecutando exitosamente
- Monitoreo interno: Significa incorporar a la misma aplicación funcionalidades que nos permiten tener información de lo que se está ejecutando para mantener el control de lo que está consumiendo cada aplicación.
Escenarios: Usabilidad
Separar la interfaz de usuario. Que cualquier otro artefacto que haya dentro de nuestra aplicación, cualquier módulo que hagamos, esté separado de la interfaz de usuario. De esta forma podemos iterar y podemos revisar la cantidad de veces que sea necesario la interfaz de usuario para poder trabajar constantemente sobre la usabilidad de lo que estamos proponiendo, y esto puede trabajarse independientemente de la lógica de negocios o de la estructura de datos.
Iniciativas de usuarios. Acciones que el usuario va a hacer y cómo el sistema puede ayudarlo.
- Cancelar. Permite a un usuario dada una acción previa el poder arrepentirse.
- Deshacer. Volver para atrás al estado anterior le permite al usuario re evaluar lo que va a hacer y luego tomar mejores decisiones.
- Agregación. Tiene que ver con entender cuándo las funcionalidades que estamos presentando al usuario en realidad deberían estar agrupadas.
- Múltiples vistas. Refiere a cómo hacemos para que el usuario tenga solamente la información necesaria para poder hacer sus acciones de la forma más eficiente posible.
Iniciativas del sistema. La idea es poder entender del lado del sistema cuál es el estado actual de la aplicación.
- Modelo del usuario. Esto significa que del lado del sistema entendamos cuál es el estado actual del usuario para poder empezar una iniciativa, es decir, enviar un mensaje del lado del sistema y que tenga sentido con lo que está viendo.
- Modelo del sistema. Implica qué sabemos de nosotros mismos, qué sabemos como aplicación de lo que está pasando en este momento.
- Modelo de la tarea. Tiene que ver con cuánto entiende el sistema de la tarea que está realizando el usuario.
Validar las decisiones de diseño: Arquitectura en evolución
Validar decisiones. Dependiendo el contexto, la validación de arquitectura, es un proceso que tenemos que hacer antes de entrar en desarrollo o bien como un proceso continuo cada vez que desarrollamos.
En metodologías tradicionales, el diseño de la arquitectura es importante antes de empezar a desarrollar
En metodologías ágiles se revalúa la arquitectura en cada iteración.
Método ATAM (Método de análisis de Trade-off)
Los objetivos de negocio, atributos de calidad y los escenarios planteados se combinan con la arquitectura que definamos, sus estrategias y decisiones. Se analizan para que las partes interesadas tengan voz y voto y decidir sobre la misma.
Comentarios
Publicar un comentario