Mostrando entradas con la etiqueta Estándares. Mostrar todas las entradas
Mostrando entradas con la etiqueta Estándares. Mostrar todas las entradas

Patrones de Diseño (I): Definición y Clasificación


Estoy seguro que has escuchado más de una vez el término Patrón de Diseño (Design Pattern).  De lo que no estoy tan seguro es de que todos sepamos bien a qué se refiere ese término.  Así que aclaremos...


¿Qué es un Patrón de Diseño?

En resumen, un Patrón de Diseño es una solución típica para un problema común.  Dicho así, no suena tan impactante, ¿cierto?

Cada Patrón es como una guía, una plantilla, una herramienta reutilizable a aplicar ante un problema de un cierto tipo.  Así como siempre usamos un martillo para clavar un clavo, o unas tijeras para cortar tela, de forma similar podemos identificar fácilmente qué herramienta general usar para los problemas más comúnes de tecnología, y a partir de ahí, personalizar la herramienta hasta obtener un resultado óptimo.

Nota que un Patrón no es un trozo de código específico, o un algoritmo, sino un concepto general, por lo que no puedes simplemente copiar y pegar el Patrón en tu código.  Pero sí puedes usar esa guía para apoyarte en la creación de tu código.


Beneficios de los Patrones de Diseño

Puedes pasar toda tu vida profesional sin haber leído, aprendido, o aplicado, algún Patrón, de la misma forma que puedes pasarla sin usar un martillo ni una sola vez.  Pero sufrirás clavando cualquier clavo que te consigas.  Y ¿por qué querríamos que eso pasara?

Los Patrones de Diseño son soluciones probadas y aprobadas que te ayudarán a estar seguro de la validez de lo que estás implementando.  Te ahorrarán tiempo y esfuerzo, dado que podrás recorrer una senda ya abierta por otros pies, en lugar de estar sufriendo abriéndote paso por terreno agreste.  ¿Por qué reinventar el agua tibia?

Además, hablar en lenguaje de Patrones te permitirá comunicarte de forma más eficiente.  Cuando digas "usaré un Adapter" o "lo mejor es usar un Singleton", todo el equipo te entenderá... ¡o debería hacerlo!


Clasificación de los Patrones de Diseño

En la actualidad hay 23 Patrones de Diseño, divididos en tres categorías: 

  • Patrones creacionales (5 Patrones), que proveen mecanismos de creación de objetos que aumentan la flexibilidad y la reutilización del código existente.
  • Patrones estructurales (7 Patrones), que explican cómo componer clases y objetos en estructuras más grandes, mientras se mantienen eficientes y flexibles.
  • Patrones de comportamiento (11 Patrones), que se encargan de la comunicación entre objetos, así como asignación de responsabilidades.


Continuará...

Hoy pararemos este artículo aquí.  Pronto publicaré un artículo para cada tipo de Patrón, y con esa trilogía (¿de la que este artículo es la precuela? XD) cubriremos los Patrones existentes.

¡Escríbanme sus comentarios y prometo responder!

-- Gorka Siverio


Agregando certificados a Java

 

Cuando comenzamos a preocuparnos de la seguridad en nuestros desarrollos, sin duda alguna, una de las primeras cosas con las que nos tropezaremos serán las conexiones seguras, y los certificados que necesitamos para las mismas.

Comencemos desde el principio: 


¿Qué es HTTP?

HTTP (por las siglas de Hypertext Transfer Protocol) es un protocolo de comunicación que permite transmitir mensajes y documentos.  Fue pensado para la comunicación entre servidores web y navegadores web, pero puede ser usado también para otros propósitos.  En otras palabras, es la forma estándar en la que tu navegador se comunica con otras máquinas cada vez que buscas algo (como este artículo!).


¿Qué es HTTPS?

HTTPS (por las siglas de HTTP Secure) es el hermano responsable de HTTP.  Mientras que HTTP solo se encarga de definir la forma en la que el mensaje se envía desde el origen y se recibe en el destino, HTTPS se encarga además de garantizar que en dicha comunicación participen solamente dichos origen y destino, evitando que la comunicación sea interceptada y las identidades suplantadas.

Básicamente, este protocolo permite que el mensaje sea encriptado usando SSL (Secure Sockets Layer), su hijo más actualizado TLS (Transport Layer Security), o el protocolo de encriptación que sea el apropiado.  De esta forma, solamente los participantes adecuados, el origen y el destino, pueden participar en una conversación dada.


¿Qué es un Certificado?

Un certificado no es otra cosa, ni más ni menos, que un documento que prueba que uno de los participantes de una conversación es quien dice ser.  Podemos tener certificados tanto suministrados por el servidor para el cliente, como por el cliente para el servidor, pero lo más común siempre es el primer caso.

Para compartirles un ejemplo resumido, si yo tengo un servidor que publica cierta información, y quiero que uno de ustedes tenga acceso seguro (usando HTTPS/TLS), sería mi responsabilidad compartirles mi certificado (normalmente un archivo .cer), y responsabilidad de ustedes instalarlo para que su navegador pueda usarlo.  De esa forma, cuando mi servidor les mandara información, ustedes podrían saber si en efecto la estoy enviando yo.

Normalmente, para empresas "públicas" (Google, Amazon, y casi cualquier otro site que consigas en Internet) a las que accedemos usando nuestros navegadores, el intercambio de certificados es bastante transparente para nosotros como usuarios: existen entidades responsables de validar los certificados (las CAs o Certificate Authorities), de tal forma que nuestro navegador tenga contra qué comparar que ese certificado de Google que le está llegando es realmente de Google.  Sin embargo, para las comunicaciones "privadas" que normalmente manejamos como desarrolladores, esa responsabilidad recae en nosotros.

Noten que un certificado puede tener fecha de caducidad, según lo que decida su dueño, por lo que puede ser necesario reimportarlo de cuando en cuando.


¿Qué es un Java KeyStore?

Un Java KeyStore (JKS, o simplemente KeyStore) es un repositorio de certificados, sean públicos o privados, y de cualquier clave privada que sea necesaria para su uso.  Básicamente, es el lugar donde debemos, como desarrolladores, importar el certificado de la otra máquina con la que queremos ser capaces de comunicarnos vía comunicación segura.

Si nuestro programa o herramienta usa la comunicación TLS o similar, debemos añadir un certificado al almacén de claves JKS en cada una de las máquinas (servidores, estaciones de trabajo, etc) con las que queramos comunicarnos.  Así de sencillo.


Y al fin, ¿cómo importo un certificado .cer a mi KeyStore Java?

Pues simplemente usaremos el comando keytool de Java para importar el certificado al almacén de claves.  Podemos ejecutarlo vía línea de comandos, o vía algún programa o interfaz con la que contemos.  Si usamos la línea de comandos, tendríamos algo como esto:

keytool -importcert -file <certificate.cer> -keystore <keystore.jks>

Donde:

  • <certificate.cer> es la ubicación y nombre del certificado que nos ha compartido el servidor.
  • <keystore.jks> es la ubicación y nombre del KeyStore dentro de la instalación Java del servidor o cliente que estamos usando.

Además de estos parámetros podemos usar muchos otros (para asignarle una clave a un KeyStore y que no cualquiera pueda revisarlo, por ejemplo, o asignarle un alias a un certificado importado).  Pueden ver el detalle del comando aquí.

Noten que el mismo comando puede ser usado no solo para la importación de un certificado (que es lo que hemos cubierto en este artículo), sino para todo lo que tenga que ver con un KeyStore, incluyendo su creación! No teman en pasear por la documentación.


Como siempre, ¡Quedo a la orden ante cualquier duda!

-- Gorka Siverio


Errores básicos usando log4j


Log4j es una librería open source desarrollada en Java por la Apache Software Foundation. La librería permite a los desarrolladores de software elegir la salida y el nivel de granularidad de los logs a tiempo de ejecución, y no a tiempo de compilación como es comúnmente realizado.

Dicha configuración se hace en tiempo de ejecución mediante el uso de archivos de configuración externos (normalmente, el famoso log4j.properties). Log4J ha sido implementado en otros lenguajes como: C, C++, C#, Perl, Python, Ruby y Eiffel, si bien este artículo se enfoca principalmente en su uso en Java.

Su uso básico es realmente sencillo. Sin profundizar mucho aquí -ya lo haremos en otro post-, sencillamente se agrega la librería al proyecto donde se desee usar, se configura el archivo de configuración para indicar las salidas y el nivel de detalle de los logs deseados, y se modifica en el código los usos de System.out.print y similares por las llamadas apropiadas al logger de log4j (normalmente, log.debug, log.error, etc).

Las trazas en log4j manejan niveles de prioridad. A saber, ALL, TRACE, DEBUG, INFO, WARNING, ERROR, FATAL y OFF. Cada una es "mayor" o "menor" que la anterior o posterior, lo que nos permite configurar "niveles" de granularidad, detalle, o como quieran llamarlo. Por ejemplo, si en mi archivo de configuración de log4j indico que tengo el nivel de trazas seteado en INFO, eso quiere decir que todo lo superior e igual a INFO se mostrará (INFO, WARNING, ERROR y FATAL), mientras que lo inferior (TRACE, DEBUG) no se logueará. Si seteamos el nivel de trazas a ALL se mostrarán todas, y si lo colocamos en OFF no se verá ninguna.

Esto nos permite configurar el nivel de logueo de acuerdo a las necesidades de cada ambiente en particular. Por ejemplo, para nuestro ambiente de Desarrollo o incluso de Prueba, el archivo de configuración de log4j debería estar en nivel DEBUG, TRACE o incluso en ALL, para que podamos ver todas las trazas. Para el ambiente del cliente (Producción), se podría setear INFO, para que les lleguen los mensajes sin información de debugging, o incluso WARNING o ERROR, por cuestiones de performance (no me interesa que me digan que la operación fué un éxito, sólo me interesa enterarme de que falló y por qué).

Para que esto funcione apropiadamente, debemos asegurarnos de usar la prioridad apropiada en cada traza que coloquemos en el sistema. En general, al colocar una traza para información de debug o desarrollo, deberíamos usar el nivel de debug o el de trace:

log.debug("esta es mi traza");

Para colocar una traza con información de un error, deberíamos usar el nivel de error. Es decir, en todos los catch de la aplicación sólo deberíamos loguear usando el nivel error o fatal, y pasarle la excepción al mismo para poder controlar las trazas completamente (no llamar a e.printStackTrace()):

log.error("error haciendo algo", e);

Los demás niveles de logueo son un poco menos usados:

- log.info(...) se usa cuando queremos mostrar información de éxito en la ejecución de un programa. Como comenté arriba, normalmente cuando estamos en producción no nos interesa que nos aparezcan trazas diciendo "todo va como debería", por lo que yo tengo como regla no usar trazas de este nivel.

- log.warn(...) debe usarse cuando se encuentre una situación potencialmente peligrosa. Si no sabes qué es esto, eso quiere decir que no debes usar el nivel warn, por lo que tampoco acostumbro a usarlo. Normalmente, esto refleja una situación en la que algo falló, pero que el sistema pudo manejar de alguna forma, como asumiendo valores por defecto.

(De ahora en adelante, por comodidad y para ahorrar bits, todos los ejemplos del documento serán dados usando el nivel de debug/DEBUG, dado que este es el caso más común. Sin embargo, debemos notar que en general, todos aplican a los demás niveles indicados arriba (cuando hablamos de log.debug aplica también a log.info y log.error; cuando hablamos de isDebugEnabled() hablamos también de isInfoEnabled(), etc).

Validación del nivel de la traza:


A veces hemos visto que se acostumbra en las trazas validar a mano si el nivel de trazas usado permite o no que se muestre la traza en cuestión. Ejemplo:

if (log.isDebugEnabled())
log.debug("Traza del objeto ", this);

Si revisamos la implementación del método isDebugEnabled() vemos que lo que revisa es que las trazas del nivel indicado (en el caso del ejemplo, DEBUG) estén permitidas, y que el nivel de traza configurado acepte el nivel indicado:

public boolean isDebugEnabled() {
if(repository.isDisabled( Level.DEBUG_INT))
return false;
return Level.DEBUG.isGreaterOrEqual(
this.getEffectiveLevel());
}

Si revisamos la implementación del método debug(...) vemos que hace exactamente las mismas validaciones antes de imprimir la traza indicada:

public void debug(Object message) {
if(repository.isDisabled(Level.DEBUG_INT))
return;
if(Level.DEBUG.isGreaterOrEqual(
this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG, message, null);
}
}

Por ende, llamando al método isDebugEnabled() no solo agregamos líneas de más a la clase, haciéndola más ilegible, sino que además agregamos procesamiento innecesario, causando mayores tiempos de respuesta sin necesidad (sí, es sólo un if, pero es un if en cada llamada al log del sistema). Después de todo, es precisamente de ello de lo que se encarga la librería log4j!

No se me ocurren muchas utilidades para que tengamos que llamar alguna vez al método isDebugEnabled(), aunque estoy seguro de que alguna habrá (de hecho, hay una justo más abajo. Sigan leyendo). Como todo método, se puede usar, pero normalmente su uso será redundante, por lo que no lo recomendamos.

Performance


log4j nos da la facilidad de "apagar" las trazas como creamos necesario, de acuerdo a lo hablado en la primera parte de este documento. Sin embargo, esto no es del todo cierto, pues las concatenaciones necesarias para formar el mensaje a mostrar aún se realizan, por lo que -si las trazas están apagadas- forzamos al sistema a realizar operaciones de más, que afectan el performance general de la misma, para nada. Ver, por ejemplo, lo siguiente:

log.debug("Se cambio la propiedad '" + entry.name +
"', del valor anterior '" + prevValue +
"' al nuevo valor '" + entry.value + "'.");

Es deseable pensar en usar, en lugar de los métodos de logueo que reciben como parámetro un único String formado por la concatenación de varios, una solución alterna, como StringBuffers, o una lista o arreglo con todos los parámetros a imprimir, y que sea el método de logueo en nuestra clase logger (que hereda del Logger de log4j) el que -luego de validar que el nivel de traza es el apropiado para que la misma aparezca- concatene el mensaje. Una implementación sencilla de la solución con arreglos podría ser algo como:

public String buildMessage(String[] message) {
// Deberíamos usar un StringBuffer o alguna
// otra solución aquí...
String result = "";
for (int ii = 0; ii < message.length; ii++) {
result += message[ii];
}
return result;
}

Que luego podría ser usado por los métodos de logueo en nuestra clase logger:

public void debug(String[] message) {
if (repository.isDisabled(Level.DEBUG_INT)) {
return;
}
if (Level.DEBUG.isGreaterOrEqual(
this.getEffectiveLevel())) {
forcedLog(FQCN, Level.DEBUG,
buildMessage(message), null);
}
}

Este es un cambio seguro y fácil de realizar, pero no rápido, pues habría que cambiar todas las llamadas a métodos de logueo (debug(...), error(...), etc) ya existentes en nuestras aplicaciones. Es una prueba interesante, para analizar su uso en los desarrollos venideros, hacer una prueba sencilla para validar el performance de dicho cambio.

Para realizar dicho cambio habría que cambiar las llamadas actuales al log:

log.debug("Se cambio la propiedad '" + entry.name +
"', del valor anterior '" + prevValue +
"' al nuevo valor '" + entry.value + "'.");

Por:

log.debug(new String[] {"Se cambio la propiedad '",
entry.name,
"', del valor anterior '",
prevValue,
"' al nuevo valor '",
entry.value, "'."});

O:

String[] message = {"Se cambio la propiedad '",
entry.name,
"', del valor anterior '",
prevValue,
"' al nuevo valor '",
entry.value, "'."}
log.debug(message);

La mejora en el performance es notoria, pero la aplicación pierde algo de legibilidad, por lo que este punto debe analizarse con cuidado. Siempre queda la opción de usar unos métodos de logueo en las aplicaciones donde el performance no es significativo, y usar otros cuando sí lo sea.