Quantcast
Channel: Planeta Código
Viewing all 2713 articles
Browse latest View live

Variable not found: La interfaz IMiddleware: Middlewares tipados per request en ASP.NET Core

$
0
0
ASP.NET CoreTradicionalmente los middlewares de ASP.NET Core los hemos implementado como clases independientes más o menos con la siguiente pinta:
public class MyCustomMiddleware
{
private readonly RequestDelegate _next;
public MyCustomMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context)
{
// Hacer algo antes de pasar el control al siguiente middleware
await _next(context); // Pasar el control al siguiente middleware
// Hacer algo después de ejecutar el siguiente middleware
}
}
Estas clases no heredan de ninguna otra ni implementan interfaces proporcionadas por el framework, aunque atienden a convenciones simples, como la recepción en el constructor del delegado al siguiente middleware en el pipeline, o la existencia de un método Invoke() o InvokeAsync(), que es donde introduciremos nuestra lógica, recibiendo el contexto HTTP.

La ausencia de interfaces o clases base aporta flexibilidad, pero elimina las ayudas propias del tipado fuerte y puede ser fuente de problemas si no atendemos a estas convenciones con cuidado. Es decir, si en lugar del método Invoke() por error escribimos Invke(), nada fallará en compilación. Tendremos que esperar a ejecutar la aplicación para que explote.

También es importante tener en cuenta que una clase middleware sólo es instanciada una vez, cuando la aplicación está arrancando; luego, en cada petición será ejecutado su método Invoke() sobre la misma instancia, lo que es a priori muy poco intuitivo y puede causarnos algún dolor de cabeza si no somos cuidadosos.

Por ejemplo, en el constructor no podemos recibir dependencias con un lifetimeper request, como podría ser un contexto de Entity Framework, porque en el momento de creación de la instancia ni siquiera se han recibido peticiones todavía. Es decir, el siguiente código fallará al intentar inyectar un componente registrado como scoped:
public class AuditMiddleware
{
private readonly RequestDelegate _next;
private readonly IMyDataContext _dataContext;
public AuditMiddleware(RequestDelegate next, IMyDataContext dataContext) // Fallará
{
_next = next;
_dataContext = dataContext;
}

public async Task Invoke(HttpContext httpContext)
{
_dataContext.Auditing.Add(
new AuditEntry() { Description = "New request", Path = httpContext.Request.Path }
);
await _dataContext.SaveChangesAsync();
await _next(httpContext);
}
}
De hecho, las dependencias scoped deben añadirse como parámetros al método Invoke(). El siguiente código sí funcionaría correctamente, porque la referencia al contexto de datos IMyDataContext se recibe en el interior de un scope delimitado por la petición actual:
public class AuditMiddleware
{
private readonly RequestDelegate _next;
public AuditMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext httpContext, IMyDataContext dataContext)
{
dataContext.Auditing.Add(
new AuditEntry() { Description = "New request", Path = httpContext.Request.Path }
);
await dataContext.SaveChangesAsync();
await _next(httpContext);
}
}

La interfaz IMiddleware

ASP.NET Core proporciona una fórmula para mejorar un poco la forma de crear middlewares: la interfaz IMiddleware. Esta se encuentra definida en el espacio de nombres Microsoft.AspNetCore.Http de la siguiente forma:
public interface IMiddleware
{
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
Las clases middleware que implementen IMiddleware presentan dos diferencias principales respecto a las "clásicas":
  • Primero, el uso del tipado fuerte nos evitará fallos en tiempo de ejecución, puesto que estaremos obligados a implementar el método InvokeAsync(), que es desde donde se procesarán las peticiones. Fijaos además que se recibe tanto el contexto HTTP como el delegado que nos permitirá ceder el control al siguiente middleware del pipeline.
     
  • Segundo, las instancias de este middleware serán solicitadas al contenedor de servicios de ASP.NET Core en cada petición a través de una factoría. Es decir, no se trata de una instancia única como teníamos en el caso anterior. Como consecuencia:

    • Debemos registrar obligatoriamente el middleware en el método ConfigureServices() de la clase Startup para que esté disponible. Si no es así, la aplicación explotará cuando llegue la primera petición porque el framework no será capaz de crear la instancia.
       
    • Su ciclo de vida será el que especifiquemos en el momento de registrarlas. Lo habitual será hacerlo como transient, pero podríamos elegir otra estrategia si es necesario.
       
    • Dado que es el propio framework el que creará las instancias, podemos utilizar inyección de dependencias en el constructor.
Por tanto, un middleware como el anterior podríamos reescribirlo usando IMiddleware de la siguiente forma:
public class AuditMiddleware: IMiddleware
{
private readonly IMyDataContext _dataContext;
public AuditMiddleware(IMyDataContext context)
{
_dataContext = context;
}

public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next)
{
_dataContext.Auditing.Add(
new AuditEntry() { Description = "New request", Path = httpContext.Request.Path }
);
await _dataContext.SaveChangesAsync();
await next(httpContext);
}
}
Y para registrarlo y añadirlo al pipeline, lo haríamos de forma similar a lo habitual, por ejemplo:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<AuditMiddleware>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMiddleware<AuditMiddleware>();
...
}
Como nota negativa, decir que los middlewares que implementan IMiddleware no permiten el envío de parámetros de inicialización personalizados. Es decir, un código como el siguiente fallará en tiempo de ejecución:
app.UseMiddleware<MyMiddleware>(new MyMiddlewareOptions { ... });
En definitiva, se trata de una fórmula adicional, denominada oficialmente factory-based middlewares, para implementar middlewares que soluciona algunos problemas propios del enfoque basado en convenciones. Aunque de momento limitado por la imposibilidad de enviar parámetros de inicialización, sin duda es una buena opción a tener en cuenta en casos en los que esto no es necesario.

Publicado en: www.variablenotfound.com.

Fixed Buffer: Herramientas de desarollo: Travis CI

$
0
0
netCoreTravis

Hace ya unos meses, hice mis primeras publicaciones sobre tecnología en el blog de un compañero de trabajo, y en ellas hablaba de una práctica que por desgracia, sigue siendo una gran desconocida cuando hablo con gente que se dedica al software. Me estoy refiriendo a la integración continua (CI).

Si bien es cierto, que las pruebas unitarias tienen una funcionalidad clara, no valen de nada si no las ejecutas. Esto puede pasar por muchas razones, pero una de ellas (al menos la que más me suele afectar a mi), es que trabajas sobre una funcionalidad que hay que pasar a la rama principal urgentemente y no hay tiempo de lanzar todas las pruebas unitarias. Durante el desarrollo de esa característica, compruebas que todo funciona bien, y subes tus cambios tranquilo, hasta que de repente… ¡¡Algo se ha roto en otro sitio!!

Gracias a herramientas como AppVeyor , Travis o Azure Pipelines, (los 3 son gratis si nuestro repositorio es público) podemos conseguir automatizar este trabajo, de modo que no tengamos que preocuparnos de compilar y ejecutar las pruebas. Ademas, estos sistemas nos aportan otra ventaja adicional, podemos compilar y ejecutar pruebas en diferentes entornos, como veremos a lo largo de esta entrada sobre Travis, y la siguiente que será sobre Azure Pipelines (y como se puede ver en mi colaboración en MascandoBits sobre AppVeyor).

Antes de continuar, es un buen recordar la entrada sobre Mocking (¡o leerla sin aun no lo has hecho!), ya ahora vamos a ver las ventajas de no depender de recursos externos para ejecutar las pruebas unitarias. De hecho, vamos a utilizar ese mismo repositorio para incluir la integración continua con Travis y Azure Pipelines al ser un proyecto NetCore (podemos ejecutarlo en Windows, Linux y MacOS) y tener pruebas unitarias.

Funcionamiento de los servicios CI

En primer lugar, podemos preguntarnos como funcionan estos sistemas de CI. Si utilizas GitHub es muy simple, están totalmente integrados, y con unos pocos clicks podemos hacer que cada vez que hagamos un push al repositorio (o nos manden un Pull Request), automáticamente se lance la compilación y ejecución de las pruebas del código, sin tener que preocuparnos de nada más. Ademas, estos servicios suelen trabajar usando un fichero de configuración “.yml”, en el cual definimos todos los pasos que queremos ejecutar, de modo que es fácil reutilizar las configuraciones en nuestros diferentes proyectos con unos cambios mínimos. Una vez terminen, veremos en el historial de commits un indicador de si la integración ha ido bien o no, al igual que en los PR:

Historial Commits
Prueba PR

Ademas, estos servicios suelen permitirnos utilizar badges que podemos añadir a nuestro readme.md para saber siempre a simple vista el estado del proyecto:

Readme.md

Añadiendo Travis CI a nuestro repositorio

Para añadir nuestro repositorio a los trabajo de integración de Travis, en primer lugar, vamos a ir a su web y vamos a pulsar en el botón de registrarnos con GitHub. Nos lanzará una ventana para confirmar que autorizamos la consulta de datos desde GitHub, y una vez aceptemos, nos mostrará una ventana (¿vacía?) con nuestros proyectos. Lo que vamos a hacer, es pulsar sobre el botón “+” para añadir un nuevo repositorio:

Add Project

Sincronizamos nuestra cuenta para que se nos muestren los repositorios, y simplemente, activamos el repositorio que nos interesa:

Add Repo

Con esto, ya hemos indicado a Travis que queremos que lance el CI cada vez que detecte cambios en el repositorio, y Travis se encarga de configurarlo todo en GitHub para que le notifique esos cambios. A partir de este momento, cada vez que hagamos un push al remoto, se iniciara una compilación. En este momento, fallará al no tener el “.yml”, asi que vamos a añadirlo.

Fichero .travis.yml

Vamos a crear un fichero en el repositorio que se llamará .travis.yml (el primer “.” delante de travis hay que ponerlo también). En el vamos a poner lo siguiente:

 
# Lenguaje que vamos a usar
language: csharp
dist: trusty
mono: none
# Version del SDK de NetCore que queremos utilizar
dotnet: 2.1.301
# Matriz de sistemas operativos sobre los que queremos lanzar el CI
os:
  - linux
  - osx
# Comando que queremos ejecutar ANTES de compilar  
install:
- dotnet restore
# Script de compilacion
script:
  # Compilamos el proyecto
- dotnet build
  # Ejecutamos las pruebas unitarias
- dotnet test PruebasUnitarias/PruebasUnitarias.csproj
# No queremos que nos notifique por email los resultados
-notifications:
-  email: false

Si nos fijamos, simplemente le estamos indicando al servicio que es lo que queremos que haga. Le indicamos el lenguaje, le indicamos la versión del SDK, y los sistemas operativos sobre los que trabajar.
En el script de instalación (previo a compilar), ejecutamos un restore, para descargar los paquetes Nuget necesarios (sino, vamos a tener un fallo de compilación), y después indicamos que queremos lanzar dotnet build para compilar, y dotnet test “proyecto de pruebas” para ejecutar los test. Os dejo el enlace a la documentación para que podáis consultarla si queréis hacer cosas más concretas.

Una vez que hemos añadido el fichero, commit y para el repositorio, con lo cual, se iniciará automáticamente el trabajo. Si todo va bien, una vez que termine, si vamos a travis, veremos algo como esto:

CI End

Como comentaba antes, Travis nos permite generar badges para saber el estado del repo, esto se hace tan fácilmente como pulsando sobre el badge, y nos mostrara una ventana para seleccionar donde lo queremos poner. Simplemente, tenemos que seleccionar donde lo vamos a colocar, y nos dará el código copy-paste.

badge url

Con esto, ya tenemos una primera aproximación a la Integración Continua. En las siguientes entradas de esta serie sobre CI/CD, veremos Azure Pipelines, y hablaremos sobre el despliegue continuo (CD), que es tan facil como el CI, y nos permite por ejemplo, automatizar la publicación de paquetes nuget, webs, subir los binarios a un FTP, etc…

**La entrada Herramientas de desarollo: Travis CI se publicó primero en Fixed Buffer.**

Blog Bitix: 9º aniversario del blog

$
0
0
Hugo

Ya han pasado nueve años desde que el sábado 6 de febrero del 2010 cree un blog, El blog de pico.dev, que fue el precedente de Blog Bitix cambio motivado por pasar de Blogger a Octopress con GitHub Pages y posteriormente con Hugo.

Dicen que las bitácoras o blogs están en decadencia por las redes sociales como Twitter u otras formas de compartir contenido como los podcast que son más cálidos por la voz humana que los fríos blogs y vídeos de YouTube dinámicos más útiles en ciertos casos que los estáticos blogs, otras formas son a través de LinkedIn o Facebook. Sin embargo, y a pesar de todo creo que los blogs son una de las mejores formas de compartir contenido simplemente por el hecho de que es un contenido indexable por los buscadores cosa que ni los podcast ni los vídeos de YouTube cumplen en la misma medida a los cuales se llega por recomendación y no por búsqueda.

En Twitter últimamente se ha puesto de moda escribir mensajes encadenados e hilos, algunos son un contenido muy bueno pero personalmente me gustan poco estos hilos por el simple hecho de la misma naturaleza de Twitter de transmitir la información, no son indexables por los buscadores, tienen un momento de gloria cuando son compartidos con retwets pero al cabo del tiempo se pierden, una pena. LinkedIn pide registrarse para acceder a su contenido y Facebook ¡no, gracias! por motivos de privacidad. Por ello no he tenido hasta el momento anhelos de compartir el contenido que elaboro en cualquier otro de estos otros formatos.

También han surgido los canales de Telegram para grupos de usuarios por ejemplo para Arch Linux, GNOME, Linux, … que ofrecen ayuda sobre estos temas, vamos lo que son los antiguos foros web pero en un formato mucho peor, es muy probable que recurrentemente se pregunte cada cierto tiempo la misma pregunta y las contestaciones no quedan disponibles para otros usuarios en el futuro. Las redes sociales ofrecen inmediatez, mayor disponibilidad con los dispositivos móviles y contacto directo pero como formatos para contenido me parecen un atraso con respecto a la web y los blogs.

Desistid de compartir contenido que debiera perdurar y ser encontrado en las anteriores formas, su éxito se debe a que en ellas están los usuarios, que solo es necesario una cuenta en esos servicios y se obtiene respuesta rápidamente, pero para mí ¡larga vida a la web, HTML, los blogs y a los bloggers!.

Lo que sí podría intentar hacer es escribir artículos en inglés, alguno ya escribí a modo de prueba y es que el público potencial sería todo el mundo en vez de solo los hispanohablantes. Sería una buena forma de que practicase el inglés pero quizá lo determinante es que me requeriría mayor tiempo para escribir el artículo. El público sería mayor pero también hay mucha más competencia al haber más blogs en ese idioma.

Cada letra, cada tilde, cada coma y punto, cada imagen de un artículo está ahí y en la posición en la que está porque así alguien lo quiso e hizo con cada pulsación de tecla y clic de ratón. Uso varias herramientas para publicar el contenido pero no hay ninguna que me ayude a escribir el contenido de los artículos y crear los ejemplos, es todo manual y lleva horas hacerlo, este artículo entre tres y cuatro horas y solo contiene unos pocos párrafos, más de cinco horas los que llevan ejemplo y más aún si tengo que investigar para hacerlo. Múltiplica eso por 380 artículos escritos hasta el momento y 566 sumando los de El blog de pico.dev y da una buena cantidad de horas que no son parte mi trabajo sino simplemente en mi caso un entretenimiento.

En los artículos que escribo no suelo expresar habitualmente mis sentimientos o temas personales cosa que algunos otros bloggers hacen, puede crear un vínculo emocional entre blogger y lectores pero simplemente no es mi forma de escribir contenido. Podría también escribir menos artículos pero más elaborados o de mayor aporte, en buena medida nada de lo que escribo es nada que no se encuentre sin mucha dificultad en otro lado, eso sí si muestro algo de código suelo incluir en GitHub el código fuente del ejemplo completo cosa que ya no es tan habitual encontrar.

He llegado al noveno año y con los artículos que tengo escritos en borrador y pendientes de publicar creo que llegaré a la cifra más redonda de la década. El artículo anterior, el #379 de Blog Bitix lo tenía escrito en borrador desde el año 2016, casi dos años, es un ejemplo del tiempo que tengo algunos sin publicar, si los contabilizo para ser más exacto son 70 artículos que tengo escritos en borrador y pendientes de publicar que es lo que más me exige. Por lo que sí creo que llegaré al año 10 publicando artículos.

Bitácora de Javier Gutiérrez Chamorro (Guti): Retrospectiva al resaltado de sintaxis

Blog Bitix: Servidor Oauth, gateway y servicio REST utilizando tokens JWT con Spring

$
0
0
Spring
Java

Hace unos días encontré un articulo del blog técnido de los desarrolladores de Idealista. En él comentaban que tenían una API para realizar simulaciones hipotecarias usando Spring como framework, Spring Security Oauth como forma de autenticación y autorización y JWT como forma de codificar el token que otorga el servidor Oauth y contiene la información necesaria para que el servidor de recursos permita o no el acceso al recurso que aloja.

Ya había oído mencionar JWT pero este artículo me ha permitido conocer su utilidad, y no es poca. Como se menciona en el artículo JWT tiene la ventaja de que que no es necesario persistirlo en una base de datos y contiene toda la información que el servidor de recursos necesita para realizar la autorización ya que es capaz de cargar con información arbitraria que el servicio desee en el momento de la emisión, la autenticación y comprobación de que ha sido emitido por el servidor Oauth la realiza sabiendo que el token está firmado.

Los tokens son una serie de caracteres aparentemente sin sentido al estar hasheados y firmados con una clave compartida entre servidor Oauth y el servidor de recurso o para mayor seguridad mediante clave privada en el servidor Oauth y su clave pública asociada en el servidor de recursos, con la firma el servidor de recursos el capaz de comprobar la autenticidad del token sin necesidad de comunicarse con él. Los tokens de Oauth son más cortos, los tokens JWT con más largos ya que contienen información adicional. Se componen de tres partes separadas por un punto, una cabecera con el algoritmo hash utilizado y tipo de token, un documento JSON con datos y una firma de verificación.

El hecho de que los tokens JWT no sea necesario persistirlos en base de datos elimina la necesidad de tener su infraestructura, como desventaja es que no es tan fácil de revocar el acceso a un token JWT y por ello se les concede un tiempo de expiración corto. En el articulo se analizaba su infraestructura y hay varios elementos configurables de diferentes formas, son:

  • El servidor Oauth que proporciona los tokens, realiza la autenticación y proporciona las autorizaciones.
  • El servidor del recurso al que se le envía el token, en base a las autorizaciones otorgadas por el servidor Oauth al token y las autorizaciones necesarias para acceder al recurso concedo o no acceso al recurso.
  • En el caso de múltiples servicios con múltiples recursos es conveniente un gateway para que sea el punto de entrada de todos los servicios, de esta forma los clientes solo necesitarán conocer el gateway en vez de los múltiples servicios individuales. El gateway se encarga de hacer de proxy en base a información en la petición como ruta, host, parámetros, cabeceras, … de redirigir la petición al servicio encargado de atenderla y devolver la respuesta. Un ejemplo de gateway es Zuul como ya he mostrado en el artículo Proxy para microservicios con Spring Cloud Netflix y Zuul.

Puede haber más elementos en la infraestructura y quizá sea el caso de un sistema real como sería un servidor de descubrimiento con Eureka o un servidor de configuración con Spring Cloud Config, en la serie de artículos sobre Spring Cloud los muestro. Para este ejemplo obvio estos otros servidores y me centro en los más relacionados con el artículo. Aunque lógicamente son diferentes servicios se puede crear uno que proporcione varios de ellos al mismo tiempo, por ejemplo, un servicio que haga al mismo tiempo de servidor de Oauth y de gateway que es una de las posibles cambios que dejan al final en el artículo de Idealista.

Spring ha creado su propio proyecto de gateway para sustituir a Zuul, Spring Cloud Gateway y será el que use en este artículo. Soporta Spring Boot 2, Spring Framework 5, coincidencia por cualquier parámetro de la petición, filtros y transformaciones o predicados, el patrón circuit breaker, limitación de peticiones y reescritura de rutas.

Los servicios los mantengo separados ya que al combinarlos pueden surgir problemas de integración al usar diferentes versiones de librerías de Spring aún cuando todos los proyectos son de Spring. Por ejemplo, Spring Cloud Gateway utiliza Spring WebFlux que puede ser diferente del lo que utilice Spring Security Oauth y la integración puede no estar exenta de problemas.

OauthJWT

Servidor Oauth

Empezando por el servidor Oauth y las dependencias que necesita, son spring-security-oauth2 y para generar tokens JWT spring-security-jwt, el resto son dependencias necesarias de Spring Boot

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
plugins{id'application'id'org.springframework.boot'version'2.0.8.RELEASE'}mainClassName='io.github.picodotdev.blogbitix.springoauth.oauth.Main'dependencies{implementationplatform('org.springframework.boot:spring-boot-dependencies:2.0.8.RELEASE')implementationplatform('org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2')defexcludeSpringBootStarterLogging={exclude(group:'org.springframework.boot',module:'spring-boot-starter-logging')}compile('org.springframework.boot:spring-boot-starter',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-web',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-security',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-log4j2',excludeSpringBootStarterLogging)compile('org.springframework.security.oauth:spring-security-oauth2:2.3.4.RELEASE',excludeSpringBootStarterLogging)compile('org.springframework.security:spring-security-jwt:1.0.10.RELEASE',excludeSpringBootStarterLogging)runtime('com.google.code.gson:gson:2.8.5')runtime('com.fasterxml.jackson.core:jackson-databind:2.9.6')runtime('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6')runtime('javax.xml.bind:jaxb-api:2.3.0')runtime('com.sun.xml.bind:jaxb-impl:2.3.0')runtime('org.glassfish.jaxb:jaxb-runtime:2.3.0')runtime('javax.activation:activation:1.1.1')}

La clase principal de Spring Boot y que inicia la aplicación no tiene nada especial salvo la necesaria anotación @EnableAuthorizationServer para habilitar el servidor Oauth.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
packageio.github.picodotdev.blogbitix.springoauth.oauth;...@SpringBootApplication@EnableAuthorizationServerpublicclassMain{publicstaticvoidmain(String[]args){SpringApplication.run(Main.class,args);}}

La parte importante está en la clase de configuración. La clase JwtAccessTokenConverter se encarga de codificar el token, la clase TokenStore de generarlos, DefaultTokenServices contiene referencias a ambos, los métodos heredados configure() configuran diferentes aspectos del servicio como los requisitos para acceder a los endpoint para ver el contenido de un token o los clientes Oauth que reconoce. Para cada cliente se necesita proporcionar el identificativo del cliente, su clave privada o secret, identificativo del recurso, que tipos de concesiones, grants, formas o flujos de obtener el token, que autoridades y ámbitos o scopes se le asigna al token.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
packageio.github.picodotdev.blogbitix.springoauth.oauth;...@Configuration@EnableAuthorizationServerpublicclassAuthorizationServerConfigurationextendsAuthorizationServerConfigurerAdapter{@AutowiredprivateClientDetailsServiceclientDetailsService;@AutowiredprivateJwtAccessTokenConvertertokenConverter;@AutowiredprivateTokenStoretokenStore;@AutowiredprivateDefaultTokenServicestokenServices;@BeanpublicJwtAccessTokenConvertertokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();converter.setSigningKey("1234567890");returnconverter;}@BeanpublicTokenStoretokenStore(JwtAccessTokenConvertertokenConverter){returnnewJwtTokenStore(tokenConverter);}@BeanDefaultTokenServicestokenServices(TokenStoretokenStore,JwtAccessTokenConvertertokenConverter){DefaultTokenServicestokenServices=newDefaultTokenServices();tokenServices.setTokenStore(tokenStore);tokenServices.setTokenEnhancer(tokenConverter);returntokenServices;}@BeanpublicTokenStoreUserApprovalHandleruserApprovalHandler(TokenStoretokenStore,ClientDetailsServiceclientDetailsService){TokenStoreUserApprovalHandlerhandler=newTokenStoreUserApprovalHandler();handler.setTokenStore(tokenStore);handler.setRequestFactory(newDefaultOAuth2RequestFactory(clientDetailsService));handler.setClientDetailsService(clientDetailsService);returnhandler;}@BeanpublicApprovalStoreapprovalStore(TokenStoretokenStore)throwsException{TokenApprovalStorestore=newTokenApprovalStore();store.setTokenStore(tokenStore);returnstore;}@Overridepublicvoidconfigure(AuthorizationServerSecurityConfigurersecurity)throwsException{security.allowFormAuthenticationForClients().tokenKeyAccess("isAuthenticated()").checkTokenAccess("isAuthenticated()");}@Overridepublicvoidconfigure(AuthorizationServerEndpointsConfigurerendpoints)throwsException{endpoints.tokenServices(tokenServices);}@Overridepublicvoidconfigure(ClientDetailsServiceConfigurerclients)throwsException{clients.inMemory().withClient("client").secret("{noop}1234567890").resourceIds("service").authorizedGrantTypes("client_credentials").authorities("CLIENT").scopes("read");}}

El servidor Oauth de ejemplo se inicia con el comando ./gradlew oauth:run. Para obtener un token se realiza con las siguientes peticiones. Por defecto, se solicita autenticación basic pero la invocación al método allowFormAuthenticationForClients() hace que los parámetros de las credenciales se puedan indicar por parámetros.

Con el endpoint/oauth/check_token se decodifica el token. En la página de JWT hay una herramienta para decodificar el token y verificar de la firma introduciendo clave de firma en la casilla.

1
2
3
4
5
6
7
$ curl -X POST -u "client:1234567890" -d "grant_type=client_credentials""http://localhost:8095/oauth/token"{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ0MSwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEwMzE0NTk4LTRjZDctNDRmNi1hMmM4LTNjYjA5MGE1MjUxZSIsImNsaWVudF9pZCI6ImNsaWVudCJ9.n8Dwcd8YTms2Hl0YgTho9QdBWD1hAnOEmkcS-Wefy6c","token_type":"bearer","expires_in":43199,"scope":"read","jti":"10314598-4cd7-44f6-a2c8-3cb090a5251e"}
$ curl -X POST "http://localhost:8095/oauth/token?grant_type=client_credentials&client_id=client&client_secret=1234567890"{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEzYjM1M2Q2LTQwODUtNDdiMS1hYzkyLTRiZDJhNDg3MzFhOCIsImNsaWVudF9pZCI6ImNsaWVudCJ9.CueMcwrD7pTp3pj37_BzzcUODG7PcjCacSa14-l5_Hw","token_type":"bearer","expires_in":43199,"scope":"read","jti":"13b353d6-4085-47b1-ac92-4bd2a48731a8"}
$ curl -X POST -u "client:1234567890" -d "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEzYjM1M2Q2LTQwODUtNDdiMS1hYzkyLTRiZDJhNDg3MzFhOCIsImNsaWVudF9pZCI6ImNsaWVudCJ9.CueMcwrD7pTp3pj37_BzzcUODG7PcjCacSa14-l5_Hw" http://localhost:8095/oauth/check_token
{"aud":["service"],"scope":["read"],"active":true,"exp":1549692458,"authorities":["CLIENT"],"jti":"13b353d6-4085-47b1-ac92-4bd2a48731a8","client_id":"client"}
Token JWT codificado y decodificado

Servidor Gateway

El servidor gateway en realidad no interviene en la lógica de Oauth porque la autorización se delega en cada servicio que contiene el recurso. Como se indicaba en Idealista estaría bien que el gateway librase de la responsabilidad de autorización a los servicios de los recursos para hacerlos más sencillos, creo que Spring Security en el momento del artículo no está soportado en Spring WebFlux que utiliza el gateway.

Lo único necesario par definir el gateway son las dependencias del proyecto, poco más que spring-cloud-starter-gateway, y la configuración de enrutado que matchea peticiones según el parámetro predicates, reescribe la URL hacia el servicio según el filtro RewritePath y finalmente redirige la petición a la ubicación del servicio indicada en uri. Se inicia con ./gradlew gateway:run.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
plugins{id'application'id'org.springframework.boot'version'2.0.8.RELEASE'}mainClassName='io.github.picodotdev.blogbitix.springoauth.gateway.Main'dependencies{implementationplatform('org.springframework.boot:spring-boot-dependencies:2.0.8.RELEASE')implementationplatform('org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2')defexcludeSpringBootStarterLogging={exclude(group:'org.springframework.boot',module:'spring-boot-starter-logging')}compile('org.springframework.boot:spring-boot-starter',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-log4j2',excludeSpringBootStarterLogging)compile('org.springframework.cloud:spring-cloud-starter-gateway',excludeSpringBootStarterLogging)runtime('com.google.code.gson:gson:2.8.5')runtime('com.fasterxml.jackson.core:jackson-databind:2.9.6')runtime('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6')}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server.port:8090spring:cloud:gateway:routes:-id:path_routeuri:http://localhost:8080/predicates:-Path=/service/filters:-RewritePath=/service/,/

Servicio, servidor de recurso

Dado que el servicio interpreta los tokens JWT y aplica reglas de seguridad necesita las mismas dependencias que utiliza el servidor Oauth.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
plugins{id'application'id'org.springframework.boot'version'2.0.8.RELEASE'}mainClassName='io.github.picodotdev.blogbitix.springoauth.service.Main'dependencies{implementationplatform('org.springframework.boot:spring-boot-dependencies:2.0.8.RELEASE')implementationplatform('org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2')defexcludeSpringBootStarterLogging={exclude(group:'org.springframework.boot',module:'spring-boot-starter-logging')}compile('org.springframework.boot:spring-boot-starter',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-web',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-log4j2',excludeSpringBootStarterLogging)compile('org.springframework.boot:spring-boot-starter-security',excludeSpringBootStarterLogging)compile('org.springframework.cloud:spring-cloud-starter-oauth2',excludeSpringBootStarterLogging)compile('org.springframework.security.oauth:spring-security-oauth2:2.3.4.RELEASE',excludeSpringBootStarterLogging)compile('org.springframework.security:spring-security-jwt:1.0.10.RELEASE',excludeSpringBootStarterLogging)runtime('com.google.code.gson:gson:2.8.5')runtime('com.fasterxml.jackson.core:jackson-databind:2.9.6')runtime('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6')runtime('javax.xml.bind:jaxb-api:2.3.0')runtime('com.sun.xml.bind:jaxb-impl:2.3.0')runtime('org.glassfish.jaxb:jaxb-runtime:2.3.0')runtime('javax.activation:activation:1.1.1')}

El recurso es muy simple, solo devuelve un mensaje.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
packageio.github.picodotdev.blogbitix.springoauth.service;...@RestControllerpublicclassDefaultController{privateRandomrandom;publicDefaultController(){this.random=newRandom();}@RequestMapping("/")publicStringhome(HttpServletRequestrequest)throwsException{returnString.format("Hello world (%s)",request.getRequestURL());}}

El servicio comparte configuración similar al servidor de Ouath par el JwtAccessTokenConverter, TokenStore y DefaultTokenServices. En el método configure se define que el endpoint/ requiere el rol CLIENT que se obtiene del token JWT enviado. Hay que utilizar la anotación @EnableResourceServer, se inicia con el comando ./gradlew service:run.

Hay que recalcar que el servicio para verificar el token y comprobar la autorización no necesita comunicarse con el servidor Oauth toda la información que necesita está en el token.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
packageio.github.picodotdev.blogbitix.springoauth.service;...@Configuration@EnableResourceServerpublicclassResourceServerConfigurationextendsResourceServerConfigurerAdapter{@AutowiredprivateJwtAccessTokenConvertertokenConverter;@AutowiredprivateTokenStoretokenStore;@AutowiredprivateDefaultTokenServicestokenServices;@BeanpublicJwtAccessTokenConvertertokenConverter(){JwtAccessTokenConverterconverter=newJwtAccessTokenConverter();converter.setSigningKey("1234567890");returnconverter;}@BeanpublicTokenStoretokenStore(JwtAccessTokenConvertertokenConverter){returnnewJwtTokenStore(tokenConverter);}@BeanDefaultTokenServicestokenServices(TokenStoretokenStore,JwtAccessTokenConvertertokenConverter){DefaultTokenServicestokenServices=newDefaultTokenServices();tokenServices.setTokenStore(tokenStore);tokenServices.setTokenEnhancer(tokenConverter);returntokenServices;}@Overridepublicvoidconfigure(ResourceServerSecurityConfigurerresources)throwsException{resources.tokenServices(tokenServices).resourceId("service");}@Overridepublicvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().antMatchers("/").hasAuthority("CLIENT");}}

Si no se envía el token JWT se produce un error de autenticación con código de error 401 Unauthorized, si se envía un token correcto y la autoridad requerida del recurso la petición se devuelve el mensaje u el código de estado 200 OK, si se envía un token JWT con una autoridad que no corresponde con la necesaria para el recurso, en el ejemplo una autoridad DUMMY, se devuelve un código de estado 403 Forbbiden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
$ curl -v http://localhost:8090/service/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8090(#0)> GET /service/ HTTP/1.1
> Host: localhost:8090
> User-Agent: curl/7.63.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< transfer-encoding: chunked
< Cache-Control: no-store
< Pragma: no-cache
< WWW-Authenticate: Bearer realm="service", error="unauthorized", error_description="Full authentication is required to access this resource"< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1;mode=block
< X-Frame-Options: DENY
< Content-Type: application/json;charset=UTF-8
< Date: Fri, 08 Feb 201918:58:03 GMT
<
* Connection #0 to host localhost left intact{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl0sImp0aSI6IjEzYjM1M2Q2LTQwODUtNDdiMS1hYzkyLTRiZDJhNDg3MzFhOCIsImNsaWVudF9pZCI6ImNsaWVudCJ9.CueMcwrD7pTp3pj37_BzzcUODG7PcjCacSa14-l5_Hw" http://localhost:8090/service/
Hello world (http://localhost:8080/)
$ curl -X POST -u "client:1234567890" -d "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiRFVNTVkiXSwianRpIjoiMTNiMzUzZDYtNDA4NS00N2IxLWFjOTItNGJkMmE0ODczMWE4IiwiY2xpZW50X2lkIjoiY2xpZW50In0.RaeQYdukn8Xr8S9ld5Vy2UnYboUjPyMkutNgyfVN-Bc" http://localhost:8095/oauth/check_token
{"aud":["service"],"scope":["read"],"active":true,"exp":1549692458,"authorities":["DUMMY"],"jti":"13b353d6-4085-47b1-ac92-4bd2a48731a8","client_id":"client"}
$ curl -v -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiRFVNTVkiXSwianRpIjoiMTNiMzUzZDYtNDA4NS00N2IxLWFjOTItNGJkMmE0ODczMWE4IiwiY2xpZW50X2lkIjoiY2xpZW50In0.RaeQYdukn8Xr8S9ld5Vy2UnYboUjPyMkutNgyfVN-Bc" http://localhost:8090/service/
{"error":"access_denied","error_description":"Access is denied"}
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8090(#0)> GET /service/ HTTP/1.1
> Host: localhost:8090
> User-Agent: curl/7.63.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2VydmljZSJdLCJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU0OTY5MjQ1OCwiYXV0aG9yaXRpZXMiOlsiRFVNTVkiXSwianRpIjoiMTNiMzUzZDYtNDA4NS00N2IxLWFjOTItNGJkMmE0ODczMWE4IiwiY2xpZW50X2lkIjoiY2x
pZW50In0.RaeQYdukn8Xr8S9ld5Vy2UnYboUjPyMkutNgyfVN-Bc
>
< HTTP/1.1 403 Forbidden
< transfer-encoding: chunked
< Cache-Control: no-store
< Pragma: no-cache
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1;mode=block
< X-Frame-Options: DENY
< Content-Type: application/json;charset=UTF-8
< Date: Fri, 08 Feb 201919:02:14 GMT
<
* Connection #0 to host localhost left intact{"error":"access_denied","error_description":"Access is denied"}

Los tokens JWT además de firmar se pueden cifrar, en el ejemplo se usa una conexión no segura con el protocolo HTTP usando una conexión segura HTTPS ya se proporcionaría confidencialidad para los tokens y es lo recomendado.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew oauth:run, ./gradlew gateway:run, ./gradlew service:run.

Blog Bitix: Ofuscar datos sensibles en las trazas con Log4j

$
0
0
Java

Los archivos de trazas o logs contienen información de lo que ha realizado la aplicación. Estos registros de información contienen los datos que el desarrollador considera de utilidad en caso de necesitar su consulta. Algunos datos son especialmente sensibles ya que su obtención permiten acceder a cuentas de usuario, obtener datos como tarjetas de crédito o cuentas bancarias, contraseñas o bearer tokens de peticiones HTTP que autorizan el acceso. Proteger las contraseñas hasheandolas aún con salt y cifrar información por motivos seguridad y privacidad es inútil si luego esta información está presente en los archivos de log en texto plano.

Log4j es una de las librerías más utilizadas para añadir la funcionalidad de las trazas en una aplicación Java. Proteger algunos datos sensibles se puede hacer de varias formas. Una de ellas es hacer que sea la aplicación la que se encargue de no emitir estos datos en las trazas u ofuscarla enmascarándola al toda o parte. Para este caso se pueden utilizar objetos Message que adaptan los objetos de la aplicación a los datos a emitir en las trazas pero requiere modificar en todos los puntos de la aplicación.

En el siguiente ejemplo se hace uso de lookahead como se detalla en la clase Pattern de Java para añadir la funcionalidad de que los últimos caracteres queden visibles y la clase SecuredMessage aplica expresiones regulares al mensaje, en caso de encontrar una coincidencia realiza la ofuscación.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
packageio.github.picodotdev.blogbitix.log4j;...publicclassMain{privatestaticfinalLoggerlogger=LogManager.getLogger(Main.class);publicstaticvoidmain(String[]args){...logger.info(newSecuredMessage("Tarjeta de crédito: 1111 1111 1111 1111, DNI: 11111111A",Arrays.asList("(\\d{4} \\d{4} \\d{4} \\d{1})(?=\\d{3})","(\\d{6})(?=\\d{2}[A-Z])")));logger.info("Tarjeta de crédito: 1111 1111 1111 1111, DNI: 11111111A");}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
packageio.github.picodotdev.blogbitix.log4j;importorg.apache.logging.log4j.message.Message;importjava.util.Collection;importjava.util.regex.Matcher;importjava.util.regex.Pattern;importjava.util.stream.Collectors;publicclassSecuredMessageimplementsMessage{privatestaticfinalintUNMASKED_CHARACTERS=3;privateMessagemessage;privateStringstring;privatePatternpattern;publicSecuredMessage(Messagemessage,Collection<String>patterns){this.message=message;this.pattern=compilePatterns(patterns);}publicSecuredMessage(Stringstring,Collection<String>patterns){this.string=string;this.pattern=compilePatterns(patterns);}...@OverridepublicStringgetFormattedMessage(){returnsecuredMessage();}privateStringsecuredMessage(){if(message!=null){returnsecuredMessage(message);}elseif(string!=null){returnsecuredString(string);}return"";}privatePatterncompilePatterns(Collection<String>patterns){returnPattern.compile(patterns.stream().map(it->"("+it+")").collect(Collectors.joining("|")));}privateStringsecuredMessage(Messagemessage){returnsecuredString(message.getFormattedMessage());}privateStringsecuredString(Stringstring){Stringresult=string;Matchermatcher=pattern.matcher(string);while(matcher.find()){Stringmatch=matcher.group();Stringmask=mask(match);result=result.replaceFirst(match,mask);}returnresult;}privateStringmask(Stringstring){returnstring.replaceAll(".","*");}}

Utilizar una clase que implemente la interfaz Message para realizar el reemplazo requiere modificar todos los puntos de la aplicación que emitan información sensible, para evitar posibles omisiones este aspecto de la aplicación se puede delegar en Log4j y ser aplicado de forma global.

Con los parámetros de configuración replace, regex y replacement el reemplazo los hace la clase PatterLayout utilizando una expresión similar regular que en el caso de SecuredMessage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
configuration:status:warnappenders:console:name:STDOUTpatternLayout:pattern:"%d{DEFAULT} %X{uuid} %-5level %60.60logger %msg%n"replace:regex:"(\\d{4} \\d{4} \\d{4} \\d{1})(?=\\d{3})|(\\d{6})(?=\\d{2}[A-Z])"replacement:"**********"loggers:root:level:infoappenderRef:ref:STDOUT

En la salida del ejemplo la primera traza corresponde al uso de la clase SecurdMessage y la segunda al PatternLayout.

1
2
3
...
2019-02-10 11:22:47,652 INFO io.github.picodotdev.blogbitix.log4j.Main Tarjeta de crédito: ****************111, DNI: ******11A
2019-02-10 11:22:47,653 INFO io.github.picodotdev.blogbitix.log4j.Main Tarjeta de crédito: **********111, DNI: **********11A

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.

Variable not found: Enlaces interesantes 349

$
0
0
Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros


Publicado en: www.variablenotfound.com.

Variable not found: Registro y obtención de múltiples implementaciones de servicios en ASP.NET Core, y un caso práctico

$
0
0
ASP.NET CoreComo sabemos, ASP.NET Core incluye un sistema de inyección de dependencias que, aunque es algo simple comparado con otras alternativas más veteranas, cubre la mayoría de necesidades habituales. Por ejemplo, un aspecto que no es muy conocido y que puede ser útil en muchas ocasiones es su capacidad para registrar y recuperar múltiples implementaciones de una misma interfaz.

En este post vamos a ver cómo conseguirlo, y un caso práctico de uso de esta técnica en un escenario muy frecuente.

Registro de múltiples implementaciones de la misma interfaz

Como se puede intuir, el registro de distintas implementaciones de la misma interfaz se realiza… pues eso, registrándolas ;) No hay ningún misterio aquí, simplemente añadimos el par <TService, TImplementation> en la clase Startup de ASP.NET Core, usando el lifetime apropiado (singleton, en el ejemplo de abajo):
publicclass Startup
{
publicvoidConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IImageFileProcessor, JpegFileProcessor>();
services.AddSingleton<IImageFileProcessor, HeavyFileProcessor>();
... // Add more IImageFileProcessor implementations
}
...
}

Obtención de múltiples implementaciones registradas

Aquí viene la parte que es menos intuitiva, aunque tampoco anda muy lejos de lo que podríamos esperar del sistema de inyección de dependencias de ASP.NET Core. Para reclamar al contenedor las instancias de tipos asociados a la interfaz TService, simplemente añadimos un parámetro de tipo IEnumerable<TService> en el constructor:
publicclass ImageController: Controller
{
privatereadonly IEnumerable<IImageFileProcessor> _imageProcessors;

publicImageController(IEnumerable<IImageFileProcessor> imageProcessors)
{
_imageProcessors = imageProcessors;
}
...
}
Esta colección de instancias de TService que ya tenemos en memoria podemos utilizarla para implementar las funcionalidades que necesitemos para nuestra aplicación.

Pero mejor, veámoslo todo sobre un caso práctico…

Un caso práctico: SOLIDificar código

Imaginad un código como el siguiente, donde se muestra un método Process() que lo único que hacemos es llamar a otros métodos que procesan objetos de tipo ImageFile bajo determinadas circunstancias:
publicclass ImageFileProcessor
{
public ImageFile Process(ImageFile imageFile)
{
if (imageFile.Type == "JPG")
imageFile = ProcessJpeg(imageFile);

if (imageFile.Type == "PNG")
imageFile = ProcessPng(imageFile);

if (imageFile.Size > 100_000)
imageFile = ProcessHeavyFile(imageFile);

if (imageFile.Width > 1024 || imageFile.Height > 768)
imageFile = ProcessBigFile(imageFile);

[...] // Call to another processors when needed

return f;
}

private ImageFile ProcessJpeg(ImageFile f) { ... }
private ImageFile ProcessPng(ImageFile f) { ... }
private ImageFile ProcessHeavyFile(ImageFile f) { ... }
private ImageFile ProcessBigFile(ImageFile f) { .. }
[...] // Other processors
}
Entre otros, este código atenta directamente contra Principio de Responsabilidad Única, dando lugar a una clase que a todas luces tendrá a ser muy extensa y difícilmente mantenible al tener demasiadas responsabilidades. Nada que no podamos romper usando algo de Estrategia ;)

El primer paso para mejorar esta implementación sería detectar que en nuestra aplicación existen procesadores de archivos de imagen que se aplican en función de determinados criterios. Con esta información podríamos crear la siguiente abstracción:
publicinterface IImageFileProcessor
{
bool CanProcess(ImageFile imageFile);
ImageFile Process(ImageFile imageFile);
}
En nuestra aplicación existirán tantas implementaciones de IImageProcessor como formas de procesar los ficheros de imagen, y cada uno de estos componentes incluirá tanto la lógica para decidir si puede procesarlas como el proceso en sí mismo:
publicclass JpegFileProcessor : IImageFileProcessor
{
publicboolCanProcess(ImageFile imageFile) => imageFile.Type == "JPG";

public ImageFile Process(ImageFile imageFile)
{
// Process here the JPEG file
}
}

publicclass HeavyFileProcessor : IImageFileProcessor
{
publicboolCanProcess(ImageFile imageFile) => imageFile.Size > 100_000;

public ImageFile Process(ImageFile imageFile)
{
// Process here the heavy file
}
}

...
De momento, esta estructura ya nos permitiría simplificar la clase inicial, liberándola de responsabilidades que no le corresponden. Serán los componentes procesadores, las distintas implementaciones de IImageFileProcessor, los que llevarán ese peso.

Aplicando la técnica que hemos visto anteriormente, podríamos simplificar la clase inicial ImageFileProcessor suministrándole mediante inyección de dependencias la colección de procesadores, y haciendo que su método Process() simplemente los recorra y ejecute de forma secuencial:
publicclass ImageFileProcessor
{
privatereadonly IEnumerable<IImageFileProcessor> _imageProcessors;

publicImageFileProcessor(IEnumerable<IImageFileProcessor> imageProcessors)
{
_imageProcessors = imageProcessors;
}

public ImageFile Process(ImageFile imageFile)
{
foreach (var processor in _imageProcessors)
{
if (processor.CanProcess(imageFile))
{
imageFile = processor.Process(imageFile);
}
}
return imageFile;
}
}
Para que el componente ImageFileProcessor esté disponible en todos los puntos de nuestra aplicación deberíamos registrarlo en el contenedor de dependencias. Asimismo, ahí registraríamos todos los procesadores de imágenes que hayamos implementado:
publicclass Startup
{
publicvoidConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<ImageFileProcessor>();
services.AddSingleton<IImageFileProcessor, JpegFileProcessor>();
services.AddSingleton<IImageFileProcessor, HeavyFileProcessor>();
... // Add more IImageFileProcessor implementations
}
...
}
Ojo, aunque en este caso utilizamos un registro de tipo singleton, esto puede variar en cada escenario en función de las necesidades.
Fijaos que si quisiéramos añadir un nuevo procesador de imágenes, por ejemplo un GifProcessor no sería necesario tocar la clase ImageFileProcessor, ni ninguno de los procesadores existentes. Simplemente crearíamos la clase correspondiente implementando IImageFileProcessor y la registraríamos en el contenedor… and that’s all, folks!
...
services.AddSingleton<IImageFileProcessor, GifFileProcessor>();
En resumen, en este post hemos visto cómo utilizar el sistema de inyección de dependencias de ASP.NET Core para registrar múltiples implementaciones de la misma interfaz y cómo recuperarlas más adelante para implementar soluciones muy limpias a problemas frecuentes.

Espero que podáis aplicarlo a vuestros propios desarrollos ;)

Publicado en Variable not found.

Mascando Bits: Configuración de entorno Python2 y Python3 para uso simultáneo en Debian

$
0
0

Si eres desarrollador de Python o usuario de su ecosistema, te habrás dado cuenta de la brecha existente entre ambos, significando una ruptura de compatibilidad el salto de Python2 a Python3.

Pese a que Python2 tiene fecha de caducidad todavía existen aplicaciones y librerías que bien porque no han sido migradas a Python3, o porque tienen dependencias con librerías que aún no se han migrado a Python3, siguen funcionado exclusivamente sobre Python2. Por eso se hace necesario mantener los dos interpretes de Python con su respectivo respectivo gestor de paquetes pip.

Quizás a la hora de abordar esta situación, la plataforma de Windows tenga la aproximación más razonable e interesante. En la instalación de Windows pueden convivir ambos intérpretes, siendo Python3 el que prevalece por defecto si ambos existen, quedando vinculadas las palabras python y pip al intérprete y gestor de paquetes de Python3 respectivamente.

Si queremos diferenciar la ejecución entre ambos, tenemos py2 para referirnos al intérprete de Python2 y py3 para el de Python3. De la misma manera, si queremos referirnos al gestor de paquetes de Python2 lo haremos con pip2 y para referirnos al de Python3 lo haremos con pip3.

Esta estupenda idea no se da en sistemas Debian y derivados como Ubuntu. A pesar de todo Debian 8 (Jessie) trae Python 3.4 y Debian 9 (Stretch) Python 3.5, en ambos casos junto con Python 2.7, siendo Python 2.7 el intérprete Python por defecto. Para tener ambos entornos vamos a realizar las una serie de configuraciones.

Para ellos vamos a crear los siguientes alias:

alias py2='/usr/bin/python2.7'
alias py3='/usr/bin/python3.4'  # Para Debian 8 Jessie
alias py3='/usr/bin/python3.5'  # Para Debian 9 Stretch

Con esto si ejecutamos py2 o py3 tendremos configurado ambos intérpretes para usarlos según nos convenga.

Ahora tenemos que instalar los gestores de paquetes pip para Python2 y Python3, para ello ejecutamos los siguientes comandos:

apt-get install python-pip  # python2
apt-get install python3-pip  # python3

Ahora generamos los alias:

alias pip2='/usr/bin/pip'  # python2
alias pip3='/usr/bin/pip3'  # python3

Si deseamos listar los alias registrar podemos hacerlo escribiendo “alias” o “alias -p“. Si deseamos eliminar un alias sería “unalias nombre_del_alias” o “unalias -a” si queremos borrar todos.

Hasta aquí ya tenemos nuestros dos entornos para funcionar simultáneamente, pero cuidado:

⚠ Los alias se pierden al cerrar la sesión

Para arreglarlo sólo tenemos que poner los alias que queramos tener en la sesión en el archivo ~/.bash_aliases:

alias py2='/usr/bin/python2.7'
alias py3='/usr/bin/python3.5'  # Para Debian 8 Jessie
alias py3='/usr/bin/python3.5'  # Para Debian 9 Stretch
alias pip2='/usr/bin/pip'
alias pip3='/usr/bin/pip3

Para que se carguen al iniciar sesión tenemos que asegurarnos que en el archivo ~/.bashrc existan la siguientes líneas:

if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi

En caso de no encontrarlas, puedes añadirlas al final del fichero. En el caso de estar usando el usuario root es muy probable que tengas que añadir las líneas.

Para abrir y editar los archivos puedes usar nano:

nano ~/.bashrc
nano ~/.bash_aliases

Llegados a este punto ya tenemos los entornos de Python2 y Python3 completamente configurados pudiendo elegir con qué ejecutar nuestras aplicaciones y pudiendo lanzar ejecuciones con ambos intérpretes de manera simultánea. 😉

 

Blog Bitix: Las clases anidadas inner, anónimas y locales en Java

$
0
0
Java

Por regla general en Java cada clase se define en su propio archivo de código fuente, pdor ejemplo, una clase de nombre Order se ha de definir en el archivo Order.java. Sin embargo, esta no es la única forma de definir clases, es posible definir clases inner y anónimas que evita tener que crear un nuevo archivo de código fuente.

Las clases inner se definen dentro de otra clase cuando esa clase inner tiene alta cohesión (su lógica está muy relacionada con la clase que la contiene), en algunos casos se emplean para ocultar los tipos que implementan la lógica. Dependiendo de si la clase inner debe acceder a los datos de la clase que la contiene o no la clase inner se define como no estática o como estática con la palabra reservada static. Las clases inner estáticas no necesitan una referencia a la clase que la contiene y por ello son algo más eficientes y el método preferido de definirlas, si la clase inner debe acceder a los miembros de la clase contenedora hay que definirla como no estática. Para desambiguar la referencia this y miembros con el mismo nombre de la clase inner con la de la clase contenedora se puede utilizar en el ejemplo Order.this.products quitando los static de las clases.

Las clases anónimas pueden definirse en la misma línea de código donde se declara su referencia, se denominan anónimas porque no se les asigna un nombre como en el ejemplo es el caso de la clase calculadora de precio para el descuento del más barato gratis.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
packageio.github.picodotdev.blogbitix.javainnerclasses;importjava.math.BigDecimal;publicclassProductimplementsComparable<Product>{privateBigDecimalprice;publicProduct(BigDecimalprice){this.price=price;}publicBigDecimalgetPrice(){returnprice;}@OverridepublicintcompareTo(Producto){returnprice.compareTo(o.getPrice());}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
packageio.github.picodotdev.blogbitix.javainnerclasses;importjava.util.Collection;importjava.math.BigDecimal;importjava.util.stream.Collectors;publicclassOrder{Collection<Product>products;// Inner
publicenumDiscount{NORMAL,DISCOUNT_10,CHEAPEST_FREE}publicOrder(Collection<Product>products){this.products=products;}publicBigDecimalcalculatePrice(Discountdiscount){returnnewPriceCalculatorFactory().getInstance(discount).calculate(products);}// Inner static
privatestaticclassPriceCalculatorFactory{PriceCalculatorgetInstance(Discountdiscount){switch(discount){caseDISCOUNT_10:returnnewDiscountCalculator(newBigDecimal("0.90"));caseCHEAPEST_FREE:// Anonymous
returnnewNormalCalculator(){@OverrideBigDecimalcalculate(Collection<Product>products){Collection<Product>paid=products.stream().sorted().skip(1).collect(Collectors.toList());returnsuper.calculate(paid);}};caseNORMAL:
 default:returnnewNormalCalculator();}}}// Inner static
privatestaticabstractclassPriceCalculator{abstractBigDecimalcalculate(Collection<Product>products);}// Inner static
privatestaticclassNormalCalculatorextendsPriceCalculator{@OverrideBigDecimalcalculate(Collection<Product>products){returnproducts.stream().map(i->i.getPrice()).reduce(newBigDecimal("0.00"),(a,b)->{returna.add(b);});}}// Inner static
privatestaticclassDiscountCalculatorextendsPriceCalculator{privateBigDecimaldiscount;publicDiscountCalculator(BigDecimaldiscount){this.discount=discount;}@OverrideBigDecimalcalculate(Collection<Product>products){PriceCalculatorcalculator=newNormalCalculator();returncalculator.calculate(products).multiply(discount);}}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
packageio.github.picodotdev.blogbitix.javainnerclasses;importjava.util.Collection;importjava.util.ArrayList;importjava.math.BigDecimal;importjava.text.DecimalFormat;publicclassMain{publicstaticvoidmain(String[]args){Collection<Product>products=newArrayList<>();products.add(newProduct(newBigDecimal("5.0")));products.add(newProduct(newBigDecimal("10.0")));products.add(newProduct(newBigDecimal("15.0")));Orderorder=newOrder(products);DecimalFormatdf=newDecimalFormat("#,###.00");System.out.printf("Price (normal): %s%n",df.format(order.calculatePrice(Order.Discount.NORMAL)));System.out.printf("Price (discount 10%%): %s%n",df.format(order.calculatePrice(Order.Discount.DISCOUNT_10)));System.out.printf("Price (chapest free): %s%n",df.format(order.calculatePrice(Order.Discount.CHEAPEST_FREE)));}}
1
2
3
Price (normal): 30,00
Price (discount 10%): 27,00
Price (chapest free): 25,00

Para los programadores en Java seguramente esto de las clases inner y anónimas no es nada nuevo pero ¿conoces las clases locales? Dentro de un método también se puede definir una clase, llamada por ello local. Las clases locales no son habituales y para su uso su funcionalidad ha de estar altamente cohesionado con el método, un posible uso es para realizar validaciones o formateos que sean un poco complejos. El siguiente ejemplo de clase localPhoneNumber muestra su uso.

En la sección de clases anidadas o nested classes del tutorial sobre clases y objetos se explica más detalladamente estas capacidades del lenguaje Java.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.

Blog Bitix: Lookahead y lookbehind en expresiones regulares con Java

$
0
0
Java

Las expresiones regulares son un gran invento y muy útil para comprobar que una cadena cumple el patrón definido en la expresión regular, hay coincidencias en partes de la cadena y para reemplazar coincidencias. Son muy potentes para realizar estas tareas pero al mismo tiempo pueden volverse en cierta medida complicadas.

Una de las funcionalidades que soportan las cadenas es búsqueda hacia delante o lookahead y búsqueda hacia atrás o lookbehind. La primera permite examinar los siguientes caracteres de la cadena analizada y la segunda los caracteres pasados.

Hay diferentes formas de lookahead, en Java la construcción que permite hacer lookahead es (?=X) donde X es la expresión siguiente, se puede negar la expresión en el caso de querer que no se cumpla X con (?!X). También existe lookbehind con la construcción (?<=X) para negar que no se cumpla X se ha de emplear (?<!X), como su nombre sugiere en vez de mirar hacia adelante mira hacia atrás en los caracteres ya analizados.

Una aplicación práctica en la que usar lookahead es para ocultar los números de una tarjeta de crédito, una cuenta bancaria o un bearer token de un API REST excepto los cuatro últimos caracteres, este podría ser el caso de que esta información sea incluida en los archivos de log de una aplicación que por seguridad es recomendable ocultar. En el artículo Ofuscar datos sensibles en las trazas con Log4j comento varias formas de hacerlo.

Una tarjeta de crédito está formada por 4 grupos de 4 dígitos separados por espacios cumpliendo la expresión regular \d{4} \d{4} \d{4} \d{4} y un bearer token puede seguir la expresión regular Bearer \w+. Para ocultar la información de estas cadenas excepto los cuatro últimos caracteres hay que comprobar que los primeros complen el patrón añadiéndolos en un grupo de captura para su reemplazo posterior y mirar los cuatro siguientes si también lo cumplen fuera del grupo de captura. En el caso de la tarjeta de crédito se mira que la expresión cumple los primeros números de una tarjeta de crédito y le siguen los restantes, la primera parte se incluye en un grupo de captura con los paréntesis.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
packageio.github.picodotdev.blogbitix.log4j;...publicclassMain{privatestaticfinalLoggerlogger=LogManager.getLogger(Main.class);publicstaticvoidmain(String[]args){...logger.info(newSecuredMessage("Tarjeta de crédito: 1111 1111 1111 1111, DNI: 11111111A",Arrays.asList("(\\d{4} \\d{4} \\d{4} \\d{1})(?=\\d{3})","(\\d{6})(?=\\d{2}[A-Z])")));logger.info("Tarjeta de crédito: 1111 1111 1111 1111, DNI: 11111111A");}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
packageio.github.picodotdev.blogbitix.log4j;importorg.apache.logging.log4j.message.Message;importjava.util.Collection;importjava.util.regex.Matcher;importjava.util.regex.Pattern;importjava.util.stream.Collectors;publicclassSecuredMessageimplementsMessage{privatestaticfinalintUNMASKED_CHARACTERS=3;privateMessagemessage;privateStringstring;privatePatternpattern;publicSecuredMessage(Messagemessage,Collection<String>patterns){this.message=message;this.pattern=compilePatterns(patterns);}publicSecuredMessage(Stringstring,Collection<String>patterns){this.string=string;this.pattern=compilePatterns(patterns);}...@OverridepublicStringgetFormattedMessage(){returnsecuredMessage();}privateStringsecuredMessage(){if(message!=null){returnsecuredMessage(message);}elseif(string!=null){returnsecuredString(string);}return"";}privatePatterncompilePatterns(Collection<String>patterns){returnPattern.compile(patterns.stream().map(it->"("+it+")").collect(Collectors.joining("|")));}privateStringsecuredMessage(Messagemessage){returnsecuredString(message.getFormattedMessage());}privateStringsecuredString(Stringstring){Stringresult=string;Matchermatcher=pattern.matcher(string);while(matcher.find()){Stringmatch=matcher.group();Stringmask=mask(match);result=result.replaceFirst(match,mask);}returnresult;}privateStringmask(Stringstring){returnstring.replaceAll(".","*");}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
configuration:status:warnappenders:console:name:STDOUTpatternLayout:pattern:"%d{DEFAULT} %X{uuid} %-5level %60.60logger %msg%n"replace:regex:"(\\d{4} \\d{4} \\d{4} \\d{1})(?=\\d{3})|(\\d{6})(?=\\d{2}[A-Z])"replacement:"**********"loggers:root:level:infoappenderRef:ref:STDOUT

El resultado es el siguiente:

1
2
3
...
2019-02-10 11:22:47,652 INFO io.github.picodotdev.blogbitix.log4j.Main Tarjeta de crédito: ****************111, DNI: ******11A
2019-02-10 11:22:47,653 INFO io.github.picodotdev.blogbitix.log4j.Main Tarjeta de crédito: **********111, DNI: **********11A

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.

Koalite: Clases estáticas como alternativa a inyección de dependencias

$
0
0

En las aplicaciones orientadas a objetos es frecuente que en algún momento necesites tener varias implementaciones de un mismo contrato. Esto no es ningún problema y puedes aprovechar el uso de interfaces o clases abstractas para definir el contrato y luego crear distintas implementaciones del mismo.

Al tener varias implementaciones, ahora toca buscar una forma de decidir qué implementación hay que utilizar en cada caso. Para esto hay varias fórmulas. Desde el clásico patrón factoría para delegar la creación de la implementación concreta a otra clase, hasta el uso de contenedores de inversión de control e inyección de dependencias. Hoy en día parece que las buenas prácticas te llevan a usar estos últimos, pero quizá andar inyectando dependencias no siempre sea la mejor opción.

Siguiendo con las “buenas prácticas”, otra de las cosas que parece que deberíamos hacer es evitar a toda costa los métodos estáticos porque son menos flexibles y limitan el polimorfismo. Sin embargo, hay escenarios en los que un uso razonable de clases estáticas pueden ayudarnos a conseguir esa parte de comportamiento dinámico que necesitamos sin necesidad de introducir grandes cambios en el diseño de la aplicación, algo que es especialmente útil cuando estamos tratando con código legacy.

En este post vamos a partir de un ejemplo (basado en hechos reales) para ver cómo poco a poco podemos modificar una aplicación que parte de un diseño completamente rígido para dotarla de cierta flexibilidad sin asumir demasiados riesgos y sin añadir excesiva complejidad al diseño.

El problema

Partimos de una aplicación que realiza un proceso (que nos da más o menos igual), durante el cual se van ejecutando acciones modeladas en distintas clases. Esta aplicación es ejecutada manualmente por un usuario que va viendo en pantalla el estado de las operaciones y al que se le muestran de vez en cuando algunos mensajes.

El código podría ser de este estilo:

public class UserConfigurator {
  public void AddUser() {
    ProgressView.ShowWhile("Adding users...", () => {
      // ... 
    });

    MessageBox.ShowInfo("Users added");
  }
}

public class FileDeployer {
  public void DeployFiles() {
    if (!CanDeployFiles()) {
      MessageBox.ShowError("Could not deploy files");
      var helpView = new HelpView();
      helpView.LoadDocument("deploy-files.html");
      helpView.Show();
    }
    // ...
  }
}

Como éstas, habría unas cuantas clases más cada una encargada de realizar parte del proceso, comunicar al usuario el estado. Supongamos ahora que necesitamos modificar esta aplicación para que se pueda ejecutar en modo silencioso, sin mostrar ningún tipo de mensaje al usuario, para ser ejecutada de forma desatendida.

Siempre podríamos ir revisando el código con cariño e ir añadiendo ifs para sacar o no sacar ventanas, pero no parece una cosa muy agradable de hacer (ni de mantener).

Existen muchas alternativas, pero en general partimos de una situación relativamente incómoda, porque tenemos bastante código dedicado a mostrar cosas en pantalla, repartido por bastantes sitios de la aplicación, y el diseño original no usa inyección de dependencias o cualquiera de las técnicas típicas para resolver esto.

Ocultando el polvo

Para intentar mejorar la situación, lo primero que podemos hacer es intentar encapsular todo el subsistema de interfaz de usuario detrás de una fachada. En lugar de tener acciones a través del método ProcessView.ShowWhile, MessageBox.ShowInfo y MessageBox.ShowError, podemos construir algo así:

public static class UI {
  public static void ShowWhile(string message, Action action) {
    ProgressView.ShowWhile(message, action);
  }
  public static void ShowError(string message) {
    MessageBox.ShowError(messge);
  }
  public static void ShowInfo(string message) {
    MessageBox.ShowInfo(messge);
  }
  public static void ShowHelpDocument(string file) {
     var view = new HelpView();
     view.LoadDocument(file);
     view.Show(); 
  }
}

En la fachada encapsulamos todas las operaciones relativas a interfaz de usuario. Además, las vamos a encapsular como métodos estáticos. Esto nos permite evitar tener que gestionar el ciclo de vida de la fachada. Si fueran métodos de instancia y la fachada tuviera estado (algo que vamos a necesitar en un rato), tendríamos que andar garantizando que el estado de la fachada es el adecuado en todas las instancias y la cosa se complica. Es uno de esos casos en los que si puedes evitar clases y limitarte a módulos (que es lo que realmente es la clase estática), te ahorrarás complicaciones.

Con la fachada montada así, actualizar el código del resto de la aplicación es trivial:

public class UserConfigurator {
  public void AddUser() {
    UI.ShowWhile("Adding users...", () => {
      // ... 
    });

    UI.ShowInfo("Users added");
  }
}

public class FileDeployer {
  public void DeployFiles() {
    if (!CanDeployFiles()) {
      UI.ShowError("Could not deploy files");
      UI.ShowHelpDocument("deploy-files.html");
    }
    // ...
  }
}

Por el camino, al haber pasado algo de lógica a la fachada (como el caso del ShowHelpDocument), simplificamos algo el código de los clientes y gracias a la fachada tenemos encapsulado todo el código relativo a la comunicación con el usuario sacando ventanas.

Introduciendo polimorfismo

Ahora sólo necesitamos tener dos implementaciones de fachada, una que muestre mensajes al usuario y otra que se limite a escribir esos mensajes en un log en lugar de andar sacando ventanas. Para ello, vamos a empaquetar toda la lógica actual de la fachada en una clase y haremos que la fachada delegue en ella todas las operaciones:

public static class UI {

  private class WindowsUI {
    public void ShowWhile(string message, Action action) {
      ProgressView.ShowWhile(message, action);
    }
    public void ShowError(string message) {
      MessageBox.ShowError(messge);
    }
    public void ShowInfo(string message) {
      MessageBox.ShowInfo(messge);
    }
    public ShowHelpDocument(string file) {
       var view = new HelpView();
       view.LoadDocument(file);
       view.Show(); 
    }
  }

  private static readonly _ui = new WindowsUI();

  public static void ShowWhile(string message, Action action) {
    _ui.ShowWhile(message, action);
  }
  public static void ShowError(string message) {
    _ui.ShowError(messge);
  }
  public static void ShowInfo(string message) {
    _ui.ShowInfo(messge);
  }
  public static void ShowHelpDocument(string file) {
     _ui.ShowHelpDocument(file);
  }
}

Nuevamente, una refactorización trivial. He dejado la clase WindowsUI como privada a la fachada porque realmente no tiene sentido fuera de ella, pero si ten ponen nervioso las clases grandes, te lo puedes llevar fuera.

Ahora ya está bastante claro, sólo nos falta extraer un interface con las operaciones de WindowsUI para poder crear nuestra implementación silenciosa:

public static class UI {

  private interface IUI {
    void ShowWhile(string message, Action action);
    void ShowError(string message);
    void ShowInfo(string message);
    void ShowHelpDocument(string file);
  }

  private class WindowsUI: IUI {
    // Implementación que hemos visto antes
  }

  private class SilentUI : IUI {
    public void ShowWhile(string message, Action action) {
      Log.Info(message);
      action();
    }
    public void ShowError(string message) {
      Log.Error(message);
    }
    public void ShowInfo(string message) {
      Log.Info(message);
    }
    public void ShowHelpDocument(string file) {
      // Nada que hacer aquí
    }
  }

  private static readonly _ui = new WindowsUI();

  public static void EnableSilentMode() {
    _ui = new SilentUI();
  }

  public static void ShowWhile(string message, Action action) {
    _ui.ShowWhile(message, action);
  }
  public static void ShowError(string message) {
    _ui.ShowError(messge);
  }
  public static void ShowInfo(string message) {
    _ui.ShowInfo(messge);
  }
  public static void ShowHelpDocument(string file) {
    _ui.ShowHelpDocument(file);
  }
}

Sólo nos falta activar el modo silencioso al arrancar la aplicación en base a algún parámetro de línea de comandos:

static void Main(string[] args) {
  if (args.Contains("--silent")) {
    UI.EnableSilentMode();
  }
  // ...
}

Con esto ya podemos configurar de forma transparente a toda la aplicación si queremos que nuestro interfaz de usuario muestre ventanas o sea completamente silencioso y se limite a escribir en un fichero.

Resumen

Tendemos a guiarnos demasiado por las “buenas prácticas” y eso nos lleva a utilizar siempre las mismas soluciones para determinados problemas, aunque podamos encontrar soluciones alternativas más simples.

El ejemplo que hemos visto en este post es sólo eso, un ejemplo, y no pretendo en ningún caso decir que reniegues de tus contenedores de inyección de dependencias para usar sólo fachadas estáticas, entre otras cosas porque esta aproximación también tiene sus pegas (como la dificultad para seguir las dependencias de cada clase).

Sin embargo, cuando te encuentras con una aplicación que no está usando un contenedor, es bueno conocer alternativas que te permiten modificar la aplicación e introducir cierto comportamiento polimórfico sin tener que reescribir media aplicación para usar inyección de dependencias.

Posts relacionados:

  1. Las ventajas de NO usar inyección de dependencias
  2. Inyección de dependencias en Javascript (y otros lenguajes dinámicos)
  3. Fachadas estáticas para tratar con estado global

Variable not found: Enlaces interesantes 350

$
0
0
Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

ASP.NET Core developer roadmap

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA / Bots

Machine learning

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

Publicado en: www.variablenotfound.com.

Variable not found: Invalidar el contenido cacheado por el tag helper "cache" de ASP.NET Core MVC

$
0
0
ASP.NET Core MVCUna gestión apropiada de la caché es muchas veces el secreto para los sistemas de alto rendimiento o que tienen que atender a una gran carga de peticiones por segundo, y el framework ASP.NET Core MVC incluye numerosas herramientas para ello.

Entre otras, el tag helper<cache> es especialmente útil para almacenar en la memoria del servidor el resultado de procesar una porción de página o vista, de forma que pueda ser reutilizada en peticiones siguientes. Un ejemplo bastante básico, pero que deja bastante clara su utilidad y forma de uso, podría ser el siguiente:
<h1>Atomic clock</h1>
<cache expires-after="@TimeSpan.FromSeconds(30)">
@{
await Task.Delay(3000);
}
Current time: @DateTime.UtcNow.ToLongTimeString()
</cache>
Al acceder a una vista con el código anterior por primera vez, se producirá un retardo forzado de tres segundos, se renderizará el interior del tag <cache> mostrando la hora actual, y el resultado será almacenado en memoria durante 30 segundos.

Si volvemos a acceder a la misma vista durante esos 30 segundos, el resultado será mostrado inmediatamente (sin esperar los tres segundos) y el cliente recibirá el contenido que se cacheó durante la primera visita. Al transcurrir este plazo, en la siguiente petición se volverá a procesar el contenido del tag helper, ejecutándose la espera y volviendo a generar y cachear el resultado enviado al cliente.

Pero podemos tener ejemplos algo más complejos, como el siguiente. En el interior del tag helper hemos insertado un view component que podría mostrar las últimas noticias obtenidas de una base de datos, en lo que podría ser la página de inicio de un servicio de información on-line:
<section>
<h1>Latest news</h1>
<cache expires-after="@TimeSpan.FromSeconds(30)">
<vc:latest-news></vc:latest-news>
<p>
Updated at: @DateTime.UtcNow.ToLongTimeString()
</p>
</cache>
</section>
Fijaos que en este caso el tiempo de invalidación del contenido almacenado en caché no tendría sentido que fuera periódico: ¿para qué invalidar la caché y volver a obtener las noticias de la base de datos cada X segundos si quizás no se ha introducido ninguna nueva desde la última vez que se renderizó la página? ¿No sería mejor recargar la caché sólo cuando se hayan modificado las noticias?

Invalidar el contenido de la caché

Cuando nos encontramos con escenarios como ese, donde queremos invalidar el contenido de la caché ante determinados eventos, quizás nos venga bien conocer el atributo vary-by del tag helper<cache>.

Su forma de uso es muy sencilla: basta con indicar con ese atributo una expresión de cadena cuya modificación implicará la invalidación automática del contenido de la caché. Por ejemplo, en el siguiente código forzaremos a que esto ocurra cuando cambie el valor de una propiedad, en este caso estática, a la que accedemos mediante la expresión LatestNewsCache.Timestamp:
<cache vary-by="@LatestNewsCache.Timestamp">
<vc:latest-news></vc:latest-news>
<p>
Updated at: @DateTime.UtcNow.ToLongTimeString()
</p>
</cache>
La clase LatestNewCache podría ser algo similar a la mostrada a continuación, donde exponemos un método Invalidate() que forzaría la modificación del timestamp y, por tanto, la invalidación de la caché de noticias:
public class LatestNewsCache
{
public static string Timestamp { get; private set; }
public void Invalidate()
{
Timestamp = DateTime.UtcNow.Ticks.ToString();
}
}
Esta clase podríamos registrarla en los servicios de ASP.NET Core y usarla desde los métodos de gestión de noticias para forzar la recarga del caché en las siguientes peticiones cuando la colección haya sido modificada:
public class NewsServices: INewsServices
{
[...]
private readonly LatestNewsCache _latestNewsCache;

public NewsController(LatestNewsCache latestNewsCache, ...)
{
[...]
_latestNewsCache = latestNewsCache;
}

public async Task Create(NewsDto newsDto)
{
[...]
// Everything was ok, so let's invalidate the cache
_latestNewsCache.Invalidate();
}
}
¡Y esto es todo! Espero que os haya resultado interesante y aplicable para en vuestros proyectos :)

Publicado en: www.variablenotfound.com.

Fixed Buffer: Herramientas de desarrollo: Azure Pipelines

$
0
0
azure pipelines

Hace un par de semanas, empezamos esta serie sobre Integración Continua y Despliegue Continuo (CI/CD) con Travis CI y una pequeña explicación sobre que es la integración continua. Como comentábamos, a lo largo de estas entradas, vamos a hablar de los principales servicios de CI/CD, y hoy es el turno de Azure Pipelines, herramienta que forma parte de Azure DevOps. Para quien no lo conozca, es un conjunto de herramientas para trabajo colaborativo de Microsoft, que además es gratuito para fines Open-Source.

Creando el proyecto en Azure DevOps

Para poder utilizar esta herramienta, vamos a la web de Azure DevOps (si no nos hemos registrado, tendremos que hacerlo) y vamos a crear un proyecto:

Create

Esto nos lanza una segunda ventana, donde tendremos que elegir el nombre del proyecto y si queremos que sea público o privado, en este caso, lo elegimos público y pulsamos en “Create”:

Nombre Azure DevOps

Con esto, hemos creado un repositorio en Azure DevOps, el cual nos permite gestionar nuestro código como lo podrían hacer unos servicios como GitHub o GitLab, pero la parte que nos interesa es la de crear un pipeline, así que seleccionamos sobre el:

Vamos a crear nuestro pipeline

Crear Azure Pipelines

Simplemente pulsamos en “New pipeline”

Boton New

Esto nos va a mostrar un sencillo asistente, en el cual, primero seleccionamos desde donde va a coger el código, en nuestro caso, GitHub.

Azure Pipelines GitHub

Esto nos mostrará los repositorios disponibles si ya nos hemos conectado, y si no, nos mostrará un botón para autorizar.

Autorizar

Una vez autorizado, simplemente elegimos nuestro proyecto, y esto nos muestra un control donde tenemos que escribir nuestro .yml (igual que hacíamos en los demás CI), en nuestro caso, hemos utilizado el mismo proyecto en el que hablamos sobre mocking, por lo tanto, al ser NetCore, podemos compilarlo y ejecutar sus pruebas en Windows, Linux y MacOS (si fuese .Net Framework, solo podríamos con Windows y los comandos serían ligeramente diferentes), así que nuestro .yml podría ser algo como esto:

# Matriz de máquinas que vamos a utilizar
strategy:
  matrix:
    linux:
      imageName: 'ubuntu-16.04'
    mac:
      imageName: 'macos-10.13'
    windows:
      imageName: 'vs2017-win2016'

# Configuramos el pool con la imagen
pool:
  vmImage: $(imageName)

# Variables que vamos a necesitar (podemos definir tantas como queramos)
variables:
    solution: '**/*.sln'
    testProject: '**/PruebasUnitarias.csproj'
    buildConfiguration: 'Release'
    netCoreVersion: '2.2.103'

# Pasos a ejecutar
steps:

# Instalar Nuget
- task: NuGetToolInstaller@0

# Instalar la versión de NetCore que hemos definido en las variables
- task: DotNetCoreInstaller@0
  inputs:
    version: '$(netCoreVersion)'

# Ejecutar un 'nuget restore'
- task: NuGetCommand@2
  inputs:
    restoreSolution: '$(solution)'

# Lanzamos el comando de compilación
- task: DotNetCoreCLI@2
  inputs:
    command: build
    projects: '$(solution)'
    arguments: '--configuration $(buildConfiguration)' 

# Lanzamos el comando de test
- task: DotNetCoreCLI@2
  inputs:
    command: test
    projects: '$(testProject)'
    arguments: '--configuration $(buildConfiguration)' 

En caso de que esto no sea suficiente, dejo también un enlace hacia la documentación oficial.

Con esto, si todo ha ido bien, deberíamos poder ir a la pestaña “Builds” y en ella ver nuestra integración en ejecución.

Azure Pipelines Build

Si pulsamos sobre la compilación, nos mostrará el resultado en el que podemos ver la ejecución de nuestros 3 jobs (uno en cada sistema operativo), con los resultados de la ejecución de cada una de las instrucciones que le pusimos en el “.yml”.

Resultados de Azure Pipelines

Por último, solo nos queda ya poder añadir esa badge tan chula a nuestro proyecto, para que todo el mundo sepa que todo está bien, para eso (lo han puesto un poco escondido…), vamos a los 3 puntos que tenemos arriba a la derecha.

Boton para Badge

Pinchamos sobre Status Badge, y nos muestra una nueva ventana con el código listo para copiarlo y pegarlo en nuestro Markdown.

Código Badge

Con esto, hemos conseguido que nuestro repositorio ejecute la integración continua, de modo que, cada vez que hagamos un “push”, se lance automáticamente la integración en Azure Pipelines.

Igual que con Travis CI, cuando añadimos esto, se añade a nuestro repositorio de GitHub los checks en cada “push” que indican si la integración ha ido bien:

Historial Commits

Además de lanzarse (al igual que con Travis CI) en cada Pull Request.

Prueba PR

Como último dato, se puede utilizar más de un servicio de integración continua sin ningún tipo de problema al mismo repositorio (como se puede ver en la imagen de arriba en la que están Travis CI, AppVeyor y Azure Pipelines).

En las siguientes entradas de esta serie, veremos cómo podemos utilizar estos mismos sistemas de integración continua, para hacer el despliegue de nuestro proyecto, por ejemplo a Nuget.org o Azure WebApp, consiguiendo así automatizar todo el proceso de manera cómoda.

**La entrada Herramientas de desarrollo: Azure Pipelines se publicó primero en Fixed Buffer.**


Arragonán: Maven multimodule y attached tests

$
0
0

En mi vuelta al mundo java, una de las cosas que he retomado es el utilizar de forma habitual proyectos maven multimodule, lo que facilita controlar mejor las dependencias y segregar el empaquetado.

Así podemos hacer separación de responsabilidades dependiendo de nuestros intereses. Por ejemplo, algunos escenarios por los que podríamos querer usarlos serían:

  • Partir en vertical una aplicación por temas funcionales.
  • Publicar de forma independiente una librería que sale de nuestro proyecto para reutilización de terceros.
  • Partición horizontal de una aplicación o servicio: core, diferentes APIs, persistencia…

El último escenario es el que he empezado a utilizar de forma habitual, separando el modelo de dominio de los detalles de implementación de infraestructura y mecanismo de entrega. Así evitamos en lo posible tener dependencias en los módulos core a través aproximaciones de arquitectura hexagonal.

El problema que surge frente a tenerlo en un sólo módulo maven es lo respectivo a la duplicidad en cuanto código de test.

Un ejemplo es reutilizar algunos helpers de tests. En nuestro caso, implementaciones de patrones que nos facilitan la mantenibilidad como el builder o el object mother. Tener objetos fake (puede que incluso algún dummy) para usarlos como colaboradores y desacoplarnos de las implementaciones reales. O tener especificaciones en lenguaje gherkin desde el módulo core, para que sean compartidos con los módulos responsables de tener implementados los tests de cucumber.

Gracias a Maven JAR Plugin podemos evitar estas duplicidades, ya que nos permite empaquetar los tests de un módulo en un JAR sin tener que mezclarlo en los paquetes de código de producción.

Por ejemplo, para compartir los tests del módulo core deberíamos configurar la construcción del JAR de test de este modo:

<project>
  ...
  <build>
   <plugins>
     ...
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-jar-plugin</artifactId>
       <version>3.1.1</version>
       <executions>
         <execution>
           <goals>
             <goal>test-jar</goal>
           </goals>
         </execution>
       </executions>
     </plugin>
   </plugins>
  </build>
</project>

Mientras que en los módulos que queramos usar el JAR de los tests deberemos incluir la dependencia así:

<project>
  <dependencies>
  ...
    <dependency>
      <groupId>com.danilat.killerapp</groupId>
      <artifactId>core</artifactId>
      <version>X.Y.Z</version>
      <type>test-jar</type>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Con esto ya podemos reutilizar los helpers de test o especificaciones de cucumber en diferentes módulos de maven.

Coding Potions: Animate css - Cómo crear animaciones CSS con poco esfuerzo

$
0
0
Introducción ¿Tienes una web y no sabes cómo crear animaciones para darle ese toque diferenciador? Pues sigue leyendo, porque en este artículo veremos cómo crear animaciones con CSS con poco esfuerzo. Si no tienes conocimientos previos de CSS no te preocupes porque en este articulo veremos una manera de tener animaciones sin tener que tocar nada de CSS ¿mola no? Lo primero que vamos a ver es una librería CSS que nos da animaciones ya creadas, las típicas, por ejemplo, fade, slide, bounce, etc

Coding Potions: Posts

Coding Potions: ¿Sabes cuáles son las mejores librerías de componentes para Angular?

$
0
0
Introducción Como en todos los frameworks, alrededor de Angular también existe un ecosistema de librerías creadas por la comunidad, lo más habitual es librerías de componentes. En la lista que ofrezco a continuación voy a enseñar las que a mí, personalmente, me han parecido más interesantes. La lista no sigue ningún orden en particular. Yo recomiendo usar el mínimo de librerías que hagan realmente falta, ya que pierdes el control de los componentes de tu aplicación, y dependes del desarrollador de la librería para mantener esos componentes.

Bitácora de Javier Gutiérrez Chamorro (Guti): GEOS NewBasic

$
0
0

Había oído hablar del NewBasic que venía con NewDeal Office 3.2 en 1999. Me quedé impresionado con él. Un entorno de desarrollo basado en lenguaje BASIC, que adoptaba la aproximación basada en objetos de Visual BASIC. NewBasic o NBASIC fue creado por NewDeal Inc y se incluyó por primera vez en NewDeal Office 98, la […]

La entrada GEOS NewBasic aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

Viewing all 2713 articles
Browse latest View live