<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Castells</title>
    <description>The latest articles on DEV Community by Alex Castells (@alextremp).</description>
    <link>https://dev.to/alextremp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F284280%2F02f807fe-88fa-46d8-be2e-5665ec4fc77d.jpg</url>
      <title>DEV Community: Alex Castells</title>
      <link>https://dev.to/alextremp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alextremp"/>
    <language>en</language>
    <item>
      <title>Consentimientos sobre cookies</title>
      <dc:creator>Alex Castells</dc:creator>
      <pubDate>Thu, 03 Jun 2021 08:38:22 +0000</pubDate>
      <link>https://dev.to/adevintaspain/consentimientos-sobre-cookies-2hmj</link>
      <guid>https://dev.to/adevintaspain/consentimientos-sobre-cookies-2hmj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;¿Sabes qué es un CMP?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seguro que en tu día a día navegando por internet has interactuado con más de uno 😉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Con Ivan Martinez &lt;a class="mentioned-user" href="https://dev.to/ivanmlaborda"&gt;@ivanmlaborda&lt;/a&gt;
 os contamos de dónde sale la necesidad de tener uno, qué es y cómo trabajamos en Adevinta Spain para implantar nuestro CMP para el tratamiento de consentimientos sobre cookies.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/N4s962p7bpU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;👋 &lt;a href="https://www.youtube.com/watch?v=N4s962p7bpU"&gt;Alex Castells&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👋 &lt;a href="https://www.youtube.com/watch?v=N4s962p7bpU&amp;amp;t=18s"&gt;Ivan Martinez&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🍪 &lt;a href="https://www.youtube.com/watch?v=N4s962p7bpU&amp;amp;t=30s"&gt;¿En qué consiste el proyecto de consentimientos sobre cookies?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;a href="https://www.youtube.com/watch?v=N4s962p7bpU&amp;amp;t=2m7s"&gt;¿Cómo ha impactado al equipo de producto de Ivan?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡 &lt;a href="https://www.youtube.com/watch?v=N4s962p7bpU&amp;amp;t=2m53s"&gt;¿Qué es el CMP?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔧 &lt;a href="https://www.youtube.com/watch?v=N4s962p7bpU&amp;amp;t=4m5s"&gt;¿Cúales fueron los principales retos técnicos?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📈 &lt;a href="https://www.youtube.com/watch?v=N4s962p7bpU&amp;amp;t=5m14s"&gt;¿Queda algo por hacer?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://careers.adevinta.es/ofertas/?search=frontend&amp;amp;stc=aff-blog%20dev.to-consentimientos%20sobre%20cookies"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Aa4ttqyy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9akue98pbtqin1jgjnaw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>cookies</category>
      <category>gdpr</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Usando AWS S3 en local con LocalStack</title>
      <dc:creator>Alex Castells</dc:creator>
      <pubDate>Thu, 11 Mar 2021 11:57:08 +0000</pubDate>
      <link>https://dev.to/adevintaspain/usando-aws-s3-en-local-con-localstack-4jjp</link>
      <guid>https://dev.to/adevintaspain/usando-aws-s3-en-local-con-localstack-4jjp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;En ocasiones nuestros servicios deben integrarse con infraestructuras difíciles de reproducir en local para el desarrollo o para ejecutar los tests, y AWS es un ejemplo de ello.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Para los tests, a veces con mocks puede bastar para sortear el problema.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pero, para probar nuestro servicio en local, deberíamos usar nuestras credenciales de AWS y conectarnos al entorno real? Y... los demás miembros del equipo?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;En este post veremos qué es LocalStack y lo usaremos para hacer funcionar en local un servicio para almacenar ficheros en S3.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/localstack/localstack" rel="noopener noreferrer"&gt;&lt;strong&gt;LocalStack&lt;/strong&gt;&lt;/a&gt; bajo el lema &lt;em&gt;"a fully functional local AWS cloud stack"&lt;/em&gt; puede ser la herramienta que necesitemos para algunos de nuestros desarrollos con los SDKs de AWS.&lt;/p&gt;

&lt;p&gt;Dispone de una versión gratuita que nos permitirá desarrollar contra servicios como S3, SQS, DynamoDb, ...&lt;br&gt;
Y otra versión de pago con servicios como CloudFront, Neptune, ...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/localstack/localstack#overview" rel="noopener noreferrer"&gt;ver detalle de servicios en cada versión...&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Por poner un ejemplo (y código 🤓), supongamos que necesitamos una API contra la que postear imágenes que luego vamos a usar para servirlas en un entorno web.&lt;/p&gt;

&lt;p&gt;Este es un caso de uso ideal para usar un &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;bucket de S3&lt;/a&gt; como sistema de almacenamiento y &lt;a href="https://aws.amazon.com/cloudfront/" rel="noopener noreferrer"&gt;CloudFront&lt;/a&gt; como sistema de recuperación.&lt;/p&gt;

&lt;p&gt;Así que... vamos a probar LocalStack 💻🚀&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Nota: CloudFront forma parte de los servicios de la distribución de pago de LocalStack, así que no lo podremos probar en local sin pagar... aunque... si sabemos cómo acaba linkado un bucket de S3 como origin de una distribución de CloudFront y cómo afecta a las URL para recuperar los ficheros almacenados en S3, algo se nos va a ocurrir :)&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;
  
  
  Ejemplo
&lt;/h1&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/alextremp" rel="noopener noreferrer"&gt;
        alextremp
      &lt;/a&gt; / &lt;a href="https://github.com/alextremp/s3-storage-service-demo" rel="noopener noreferrer"&gt;
        s3-storage-service-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AWS S3 storage en local con LocalStack&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Este proyecto es una demo para un artículo en &lt;a href="https://dev.to/alextremp" rel="nofollow"&gt;dev.to/alextremp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Servicio de almacenamiento de ficheros a bucket de S3&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un endpoint POST para enviar un fichero a un path específico.&lt;/li&gt;
&lt;li&gt;El servicio almacenará el fichero a un bucket de S3.&lt;/li&gt;
&lt;li&gt;Devolverá la URL de acceso del fichero, ya sea local, de S3 o de CloudFront si se dispone.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Uso&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;El endpoint de ejemplo estará expuesto para POST en &lt;code&gt;http://localhost:8080/storage&lt;/code&gt;
Acepta &lt;code&gt;form-data&lt;/code&gt; con:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;file&lt;/code&gt;: El fichero que queremos guardar en S3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;path&lt;/code&gt;: La ruta donde queremos guardar el fichero, relativa respecto el bucket&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Ejecución en local contra LocalStack&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;docker-compose up -d
./gradlew bootRun&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Ejecución en local contra bucket real&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; active profile set to production&lt;/span&gt;
PROFILE=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;--spring.profiles.active=pro&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; your AWS access key&lt;/span&gt;
ACCESS=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;--s3-storage.accessKey=&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; your AWS secret key&lt;/span&gt;
SECRET=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;--s3-storage.secretKey=&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; your AWS S3 bucket&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/alextremp/s3-storage-service-demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;Stack:&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

java, spring-boot, aws-sdk, testcontainers


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Levantando S3 con docker-compose
&lt;/h1&gt;

&lt;p&gt;Configuremos el servicio de localstack:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.5'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;s3-storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localstack/localstack:0.12.5&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# permite más servicios separados por comas&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SERVICES=s3&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DEBUG=1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DEFAULT_REGION=eu-west-1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_ACCESS_KEY_ID=test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;AWS_SECRET_ACCESS_KEY=test&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# localstack usa rango de puertos, para el ejemplo,&lt;/span&gt;
      &lt;span class="c1"&gt;# usaremos solo el de S3, mapeado en local a 14566 en un &lt;/span&gt;
      &lt;span class="c1"&gt;# fichero docker-compose.override.yml para permitir &lt;/span&gt;
      &lt;span class="c1"&gt;# tests con puerto dinámico&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;4566'&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# inicializaremos un bucket aquí&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./volumes/s3-storage/.init:/docker-entrypoint-initaws.d'&lt;/span&gt;
      &lt;span class="c1"&gt;# no versionado, localstack nos generará aquí el .pem &lt;/span&gt;
      &lt;span class="c1"&gt;# para nuestras claves de acceso fake&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./volumes/s3-storage/.localstack:/tmp/localstack'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Podemos generar un bucket al iniciar el docker-compose:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./volumes/s3-storage/.init/buckets.sh&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
aws &lt;span class="nt"&gt;--endpoint-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4566 s3 mb s3://com.github.alextremp.storage


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Al ejecutar &lt;code&gt;docker-compose up&lt;/code&gt; deberíamos ver que el cliente de AWS de la imagen de localstack ha generado el bucket que le hemos indicado en el script de inicialización:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fire48awxbjyrurfdeod3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fire48awxbjyrurfdeod3.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Interactuando con el bucket
&lt;/h1&gt;

&lt;p&gt;Para el artículo me interesa explicaros sólo unos puntos concretos del código de ejemplo, para que veamos cómo podemos hacerlo funcionar en local con LocalStack y en entorno productivo con AWS.&lt;/p&gt;

&lt;p&gt;Tomando como referencia el repositorio que usará el SDK de AWS para S3 de cara a guardar un fichero (en realidad el &lt;em&gt;InputStream&lt;/em&gt; de un recurso recibido en un POST multipart):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;S3ResourceRepository&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;S3Client&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                              &lt;span class="nc"&gt;S3ResourceRepositoryOptions&lt;/span&gt; &lt;span class="n"&gt;repositoryOptions&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                              &lt;span class="nc"&gt;DestinationFactory&lt;/span&gt; &lt;span class="n"&gt;destinationFactory&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;s3Client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;repositoryOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repositoryOptions&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;destinationFactory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;destinationFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Destination&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StreamableResource&lt;/span&gt; &lt;span class="n"&gt;streamableResource&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ResourceOptions&lt;/span&gt; &lt;span class="n"&gt;resourceOptions&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;PutObjectRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt; &lt;span class="n"&gt;requestBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PutObjectRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="c1"&gt;// the target bucket&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repositoryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBucket&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
          &lt;span class="c1"&gt;// the target path in the bucket&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPath&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// setting the content-type makes it web-friendly when being read&lt;/span&gt;
    &lt;span class="n"&gt;requestBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;URLConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;guessContentTypeFromName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPath&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="c1"&gt;// it's highly recommendable to specify the cache-control for presupposed repetitive reads, specially if it's combined with CloudFront&lt;/span&gt;
    &lt;span class="n"&gt;requestBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cacheControl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"public, max-age=%s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resourceOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMaxAge&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;repositoryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasOriginReference&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// when needed to be read directly from S3, no need if it's a bucket linked to CloudFront&lt;/span&gt;
      &lt;span class="n"&gt;requestBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ObjectCannedACL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PUBLIC_READ&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;PutObjectRequest&lt;/span&gt; &lt;span class="n"&gt;objectRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putObject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objectRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RequestBody&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;streamableResource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;streamableResource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentLength&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;destinationFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPath&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;En realidad, para que el servicio sea funcional en local igual que en producción (e incluso si tuviéramos un entorno intermedio de desarrollo o pre-producción en la nube), lo más interesante son las opciones que podemos necesitar modificar para el funcionamiento del repositorio en cada uno de los entornos.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Data&lt;/span&gt;
&lt;span class="nd"&gt;@FieldDefaults&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AccessLevel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PRIVATE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Accessors&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@NoArgsConstructor&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;S3ResourceRepositoryOptions&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;accessKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;secretKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;originFor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="nf"&gt;hasCustomEndpoint&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;StringUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="nf"&gt;hasOriginReference&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;StringUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isNotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originFor&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  S3 Endpoint Override
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# application-dev.yml&lt;/span&gt;
&lt;span class="na"&gt;s3-storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:14566&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Con respecto a la opción &lt;code&gt;hasCustomEndpoint&lt;/code&gt;, en local, tanto para podernos comunicar con S3 al guardar un fichero (por defecto, tipo &lt;code&gt;s3://BUCKET/PATH&lt;/code&gt;), como para luego poderlo recuperar vía HTTP (por defecto, tipo &lt;code&gt;https://s3-REGION.amazonaws.com/BUCKET/PATH&lt;/code&gt;), necesitamos modificar el endpoint que expone S3 en LocalStack y usarlo en el servicio para comunicarnos con él.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;En LocalStack ya lo hemos hecho en el script de inicialización en docker-compose mediante &lt;code&gt;aws --endpoint-url=http://localhost:4566 ...&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;En la creación del cliente de S3, debemos ligarlo mediante la opción &lt;code&gt;endpointOverride&lt;/code&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@ComponentScan&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.github.alextremp.storage.infrastructure.aws"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AwsConfiguration&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="nd"&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"s3-storage"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;S3ResourceRepositoryOptions&lt;/span&gt; &lt;span class="nf"&gt;s3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;S3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt; &lt;span class="nf"&gt;s3Client&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;S3ResourceRepositoryOptions&lt;/span&gt; &lt;span class="n"&gt;s3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;S3ClientBuilder&lt;/span&gt; &lt;span class="n"&gt;s3ClientBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;S3Client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;credentialsProvider&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StaticCredentialsProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AwsBasicCredentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;s3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAccessKey&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;s3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSecretKey&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;)))&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRegion&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasCustomEndpoint&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;s3ClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;endpointOverride&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s3ResourceRepositoryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEndpoint&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s3ClientBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Esto nos permitirá guardar correctamente ficheros en localhost:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b81ere8ks6vgmlv0bw3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2b81ere8ks6vgmlv0bw3.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Así como recuperarlos vía HTTP:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxoz0ysxca0f3az8g679.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxoz0ysxca0f3az8g679.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Habilitando salida en CloudFront para PROD
&lt;/h2&gt;

&lt;p&gt;Si usáramos este servicio para interactuar con un bucket real, sin CloudFront, p.ej.:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# application-pro.yml&lt;/span&gt;
&lt;span class="na"&gt;s3-storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a.dcdn.es&lt;/span&gt;

  &lt;span class="c1"&gt;# add the accessKey and secretKey config...&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;La URL que necesitaríamos generar para poder acceder vía HTTPS al mismo recurso en S3, sería algo así: &lt;br&gt;
&lt;a href="https://s3-eu-west-1.amazonaws.com/a.dcdn.es/demo/demo-image.png" rel="noopener noreferrer"&gt;https://s3-eu-west-1.amazonaws.com/a.dcdn.es/demo/demo-image.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xiu8jikyiet6uox57jz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xiu8jikyiet6uox57jz.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sin embargo, si tuviéramos ese bucket mapeado como origen en una distribución de CloudFront, p.ej.:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44rwpsqfjb6o1hnfy87x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44rwpsqfjb6o1hnfy87x.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Aunque no podamos probar CloudFront en LocalStack, podríamos configurar el servicio para que generara las URL necesarias para la salida via CloudFront:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# application-pro.yml&lt;/span&gt;
&lt;span class="na"&gt;s3-storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a.dcdn.es&lt;/span&gt;
  &lt;span class="na"&gt;originFor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a.dcdn.es&lt;/span&gt;

  &lt;span class="c1"&gt;# add the accessKey and secretKey config...&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far4oy3uhd57ezs80flai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Far4oy3uhd57ezs80flai.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para que esto sea así, y para terminar con el ejemplo de código, sólo necesitaremos que nuestro servicio sea capaz de generar las URL según las propiedades del storage:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;

&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AwsDestinationFactory&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;DestinationFactory&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;HTTPS_PROTOCOL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;AWS_S3_DOMAIN_TEMPLATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"s3-%s.amazonaws.com"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;S3ResourceRepositoryOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AwsDestinationFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;S3ResourceRepositoryOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="nd"&gt;@SneakyThrows&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Destination&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;rootBuilder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasOriginReference&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;rootBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;HTTPS_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOriginFor&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasCustomEndpoint&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;rootBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEndpoint&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Destination&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEPARATOR&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBucket&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;rootBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;HTTPS_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;AWS_S3_DOMAIN_TEMPLATE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRegion&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Destination&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SEPARATOR&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBucket&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Destination&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;🚀🚀&lt;/p&gt;
&lt;h1&gt;
  
  
  Conclusiones
&lt;/h1&gt;

&lt;p&gt;Desde mi punto de vista, cuando hay código en producción cuya ejecución no es reproducible en local / automatizable en tests, tenemos dos opciones: hacerlo reproducible en local, o cruzar los dedos 😬&lt;/p&gt;

&lt;p&gt;Pero como necesitamos los dedos para teclear, &lt;a href="https://github.com/localstack/localstack" rel="noopener noreferrer"&gt;LocalStack&lt;/a&gt;, junto con &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt; y &lt;a href="https://www.testcontainers.org/" rel="noopener noreferrer"&gt;Test Containers&lt;/a&gt; pueden ayudarnos a solventar los problemas de infraestructura para la ejecución local cuando trabajamos integrados con servicios de AWS, de modo que nosotros &lt;strong&gt;podamos focalizarnos en el código más que en el entorno de ejecución&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si algún día hacéis algo con S3, sois libres de copy-pastear lo que necesitéis :)&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/alextremp" rel="noopener noreferrer"&gt;
        alextremp
      &lt;/a&gt; / &lt;a href="https://github.com/alextremp/s3-storage-service-demo" rel="noopener noreferrer"&gt;
        s3-storage-service-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>backend</category>
      <category>aws</category>
      <category>microservices</category>
      <category>testing</category>
    </item>
    <item>
      <title>Inversión de Control en Arquitectura Frontend</title>
      <dc:creator>Alex Castells</dc:creator>
      <pubDate>Tue, 26 Jan 2021 13:39:41 +0000</pubDate>
      <link>https://dev.to/adevintaspain/inversion-de-control-en-arquitectura-frontend-4h5b</link>
      <guid>https://dev.to/adevintaspain/inversion-de-control-en-arquitectura-frontend-4h5b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;DDD, arquitectura hexagonal, SOLID, IoC...&lt;/p&gt;

&lt;p&gt;Backend? Frontend? O simplemente principios del software para la programación orientada a objetos?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;En este post daremos un paseo por la arquitectura del software, como método de diseño agnóstico al frontend y al backend para ver las similitudes entre ambos contextos.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eso sí, usaré la inversión de control como referencia, dado que siendo quizá el principio que más fácil de ver es cómo habilita el diseño de software modular, es a la vez el que más frameworks "estandarizados" tiene cuando hablamos de backend (p.ej. Spring), pero el que parece tener menos en frontend.&lt;/p&gt;

&lt;p&gt;También aprovecharé al final para introduciros a &lt;a href="https://github.com/alextremp/brusc" rel="noopener noreferrer"&gt;brusc&lt;/a&gt;, una pequeña librería que desarrollé hará poco más de un año para habilitar la inyección de dependencias en proyectos de ES6.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Introducción a clean architectures
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fr8iufg4qjc63uu8jzf6y.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fr8iufg4qjc63uu8jzf6y.jpg" alt="CleanArchitecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" rel="noopener noreferrer"&gt;Clean architectures&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Muchos de los conceptos a la hora de hablar de clean architectures, best practices, principios de diseño, ... se basan en solucionar lo mismo: cómo organizar los distintos componentes de nuestro software en capas para maximizar su cohesión y minimizar el acoplamiento.&lt;/p&gt;

&lt;p&gt;A la hora de representar el comportamiento de una aplicación, cómo se puede interactuar con ella, qué sucede con las interacciones y cómo navegan los datos, a mi personalmente me gusta hablar de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actores:&lt;/strong&gt; quién inicia las interacciones (usuario, tiempo, ...) y para qué.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interfaces de acceso:&lt;/strong&gt; de qué disponen los actores para interactuar (UI, CLI, ...).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infraestructura de acceso:&lt;/strong&gt; cómo debe habilitarse un acceso para una interfaz concreta (comandos, controladores, ...)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Casos de uso (o servicios de aplicación):&lt;/strong&gt; cómo permitimos la interacción del exterior hacia nuestro dominio para consultarlo o manipular su estado.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dominio:&lt;/strong&gt; donde reside la abstracción de nuestro negocio (entidades de negocio, definiciones de repositorios, ...) para que los casos de uso puedan llevar a cabo su cometido.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infraestructura de salida:&lt;/strong&gt; cómo debe habilitarse una salida concreta hacia otro sistema que nos permita la recuperación y almacenaje del estado de nuestro dominio (APIs HTTP, BBDD, ...)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hay muchas otras formas de expresarlo, pero la idea general de todas ellas es que desde la concreción de infraestructura hacia la abstracción de la lógica de negocio (dominio), hay una flecha unidireccional de acceso por las diferentes capas, para evitar que los componentes lógicos se vean afectados por cambios de infraestructura (&lt;a href="https://khalilstemmler.com/wiki/dependency-rule/" rel="noopener noreferrer"&gt;The Dependency Rule&lt;/a&gt;].&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por ejemplo, nuestro dominio debería ser el mismo, así como los casos de uso que se exponen para habilitar la interacción con él, independientemente de si sustituimos el acceso a la aplicación mediante línea de comandos por un acceso mediante UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Una forma de representar esto puede ser mediante &lt;a href="https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)" rel="noopener noreferrer"&gt;arquitectura hexagonal&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Frontend, Backend, puede ser lo mismo desde la perspectiva OOP
&lt;/h1&gt;

&lt;p&gt;Para empezar a hablar de estos conceptos aplicados al frontend, veamos una representación muy esquemática de arquitectura hexagonal para una aplicación "típica" de backend accesible vía API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs5qo0b5t7jys59oeqtfh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs5qo0b5t7jys59oeqtfh.png" alt="Captura de pantalla 2021-01-25 a las 20.50.31"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suponiendo que el servicio fuera para poder realizar búsquedas de libros, el "foco" del desarrollador sería:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Definir el dominio que represente la lógica esperada de este servicio (domain), p.ej: &lt;em&gt;Book&lt;/em&gt; como entidad, &lt;em&gt;BookRepository&lt;/em&gt; como representación de las operaciones necesarias para recuperarlo.&lt;/li&gt;
&lt;li&gt;Definir los casos de uso para exponer las interacciones sobre este dominio al exterior (application), p.ej: &lt;em&gt;SearchBooksUseCase&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Definir la recuperación o almacenaje concretos (infrastructure), p.ej: disponemos de una base de datos MySql y deberíamos implementar las operaciones de la abstracción de dominio &lt;em&gt;BookRepository&lt;/em&gt; como por ejemplo, &lt;em&gt;JdbcBookRepository&lt;/em&gt; o &lt;em&gt;MySqlBookRepository&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Definir los controladores HTTP del servicio para habilitar los accesos vía API (infrastructure), p.ej: &lt;em&gt;BookController&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y aquí ya saldría un problema si tenemos en cuenta la (&lt;a href="https://khalilstemmler.com/wiki/dependency-rule/" rel="noopener noreferrer"&gt;Dependency Rule&lt;/a&gt;]: &lt;strong&gt;¿Cómo puede el caso de uso recuperar los libros de la base de datos sin saber que el repositorio de libros debe acceder a una base de datos? ¿Cómo recibe la implementación concreta para MySql?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pues precisamente aquí es donde entra en juego la &lt;strong&gt;inversión de control&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si nuestro caso de uso depende de un repositorio para hacer su labor, siguiendo la &lt;strong&gt;D&lt;/strong&gt; de &lt;a href="https://en.wikipedia.org/wiki/SOLID" rel="noopener noreferrer"&gt;los principios SOLID&lt;/a&gt;, el caso de uso &lt;em&gt;SearchBooksUseCase&lt;/em&gt; debe depender de una abstracción (&lt;em&gt;BookRepository&lt;/em&gt;), no de una concreción (&lt;em&gt;MySqlBookRepository&lt;/em&gt;), dado que el caso de uso no debería verse afectado si el día de mañana cambiamos MySql por Oracle, o incluso si cambiamos el almacenaje de libros a una API de terceros accesible por HTTP en lugar de por JDBC.&lt;/p&gt;

&lt;p&gt;Podríamos representar la inversión de control de dependencias así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ftd6jzrscj2x07422x4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4ftd6jzrscj2x07422x4.png" alt="Captura de pantalla 2021-01-26 a las 0.19.01"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y para lograrlo, podríamos implementar esta inversión de control con el patrón de &lt;a href="https://en.wikipedia.org/wiki/Dependency_injection" rel="noopener noreferrer"&gt;Inyección de Dependencias&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Otra forma de lograrlo es mediante el patrón &lt;a href="https://en.wikipedia.org/wiki/Service_locator_pattern" rel="noopener noreferrer"&gt;Service Locator&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;La de inyección de dependencias basada en framework de infraestructura consiste en un contenedor de dependencias capaz de proveer de una implementación concreta a partir de una abstracción (o declaración) y un inyector de dependencias que usará esa funcionalidad del contenedor para proveer al cliente esas dependencias ocultándole la implementación.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;De forma esquemática, lo que acaba sucediendo es esto:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnjabjg1rwgl30vjupecw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fnjabjg1rwgl30vjupecw.png" alt="Captura de pantalla 2021-01-26 a las 1.01.58"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y con todo lo anterior en mente... xD, ahora sí: toca hablar de cómo aplica el mismo concepto en el desarrollo de frontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supongamos que queremos desarrollar la UI web de un sistema de gestión de libros.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Supongamos además que no es sólo la UI entendida como componentes HTML y CSS, sino que tenemos asociada una lógica de negocio y debemos desarrollar una serie de casos de uso que sólo aplican al entorno web. &lt;/p&gt;

&lt;p&gt;Si aplicáramos las mismas metodologías y terminología para el desarrollo de software al que hacía referencia cuando describía el sistema para ser accedido como API de backend, volveríamos a hablar de &lt;strong&gt;dominio&lt;/strong&gt;, &lt;strong&gt;casos de uso&lt;/strong&gt;, &lt;strong&gt;infraestructura de acceso&lt;/strong&gt;, &lt;strong&gt;infraestructura de salida&lt;/strong&gt;, ... por lo que esquematizando el mismo concepto con arquitectura hexagonal veríamos algo como:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqk8ykepme2av0gleht6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqk8ykepme2av0gleht6u.png" alt="Captura de pantalla 2021-01-25 a las 20.50.05"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sólo que en este caso, por ejemplo, veríamos que la infraestructura necesaria para poder recuperar los libros debería ser representada con un acceso vía HTTP a la API de backend, y podríamos representar el caso de uso de búsqueda de libros hasta su repositorio concreto así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F585p7qiyoz4napp5112b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F585p7qiyoz4napp5112b.png" alt="Captura de pantalla 2021-01-25 a las 20.50.45"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;De este modo, nuestra implementación de componentes de UI no tiene que saber nada sobre cómo está implementada internamente la aplicación JS de gestión de libros, sólo debe conocer qué accesos se le han habilitado para poder interactuar con ella, y esos accesos no sufrirán cambios si nuestra tecnología para accederlos es una UI en React, en Angular, en Vue, o un test.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Inversión de control en Javascript
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Ya sea en ES6 o en Typescript, el concepto es el mismo. &lt;br&gt;
Yo me centraré en ES6 porque es lo que suelo usar en el día a día, y para hablaros de &lt;em&gt;brusc&lt;/em&gt;, pero podéis echarle un ojo a &lt;a href="https://github.com/inversify" rel="noopener noreferrer"&gt;inversify&lt;/a&gt; que es también un framework muy potente orientado a proyectos Typescript. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para entender mejor la inversión de control, os pongo primero un ejemplo de lo que no es, para que veamos qué problemas supone y cómo lo evolucionamos a un mejor diseño, partiendo de la base de la librería para la gestión de libros.&lt;/p&gt;

&lt;p&gt;Supongamos que queremos cumplir este expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should find a book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;givenQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sin Noticias De Gurb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchBooks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;givenQuery&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;givenQuery&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podríamos implementar la solución así:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;simplificado a fichero único y sin entidad de dominio&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Books&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_searchBooksUseCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SearchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;searchBooks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchBooksUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bookRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpOpenLibraryBookRepository&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bookRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpOpenLibraryBookRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_libraryApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://openlibrary.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_libraryApi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/search.json?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Books&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aunque el test pasaría, esto tiene varias que me harían llorar:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cada clase se está responsabilizando de la construcción de sus dependencias.&lt;/li&gt;
&lt;li&gt;Todo depende de concreciones.&lt;/li&gt;
&lt;li&gt;No es posible sustituir una implementación por una extensión de la misma, ¿cómo testearíamos el caso de uso individualmente sin poder reemplazar la implementación HTTP del repositorio por p.ej. un stub?&lt;/li&gt;
&lt;li&gt;¿Y si tuviéramos que implementar un nuevo caso de uso que dependiera del mismo repositorio, lo inicializaríamos de nuevo? ¿Y si algún día quisiéramos cambiar OpenLibrary por otra API, en cuántos casos de uso deberíamos reemplazar el repositorio?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deberíamos iterar esta solución, aunque evidentemente es mejor que si usáramos directamente un &lt;em&gt;fetch&lt;/em&gt; desde un componente de UI, dado que a medida que el proyecto tuviera más necesidades, estos problemas se multiplicarían y cada vez se haría menos extensible y menos mantenible.&lt;/p&gt;

&lt;p&gt;Otra opción: &lt;strong&gt;Aplicando la inversión de control a mano&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Books&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_searchBooksUseCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;searchBooks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchBooksUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bookRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bookRepository&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bookRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpOpenLibraryBookRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_libraryApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://openlibrary.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_libraryApi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/search.json?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BooksInitializer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpOpenLibraryBookRepository&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SearchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Books&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BooksInitializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto ya empezaría a coger otra forma:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El caso de uso no conoce la implementación del repositorio.&lt;/li&gt;
&lt;li&gt;Esta implementación podría ser sustituida en un test unitario del caso de uso o por una implementación distinta en el "initializer", y el caso de uso no se vería afectado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aún así, si el proyecto empezara a crecer en casos de uso y repositorios, nos podríamos encontrar con los siguientes problemas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Todas las dependencias deben ser inicializadas en un orden específico, añadiendo complejidad a futuros cambios a medida que el proyecto crece.&lt;/li&gt;
&lt;li&gt;Si el caso de uso de repente necesitara una nueva dependencia, se debería sincronizar la inicialización también en el "initializer", y podría provocar un reordenamiento de otras dependencias.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y aquí podría entrar la inyección de dependencias mediante framework, como por ejemplo usando &lt;a href="https://github.com/alextremp/brusc" rel="noopener noreferrer"&gt;brusc&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;searchBooksUseCase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bookRepository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Books&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_searchBooksUseCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;searchBooks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchBooksUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;bookRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bookRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bookRepository&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_bookRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpOpenLibraryBookRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_libraryApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://openlibrary.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_libraryApi&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/search.json?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Brusc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brusc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BooksInitializer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Brusc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SearchBooksUseCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpOpenLibraryBookRepository&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Books&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BooksInitializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aunque la solución tampoco es perfecta debido a las limitaciones propias del lenguaje, que para &lt;em&gt;Brusc&lt;/em&gt; implica requerir la definición de una función &lt;code&gt;inject&lt;/code&gt; accesible para todos los componentes de la librería (y opcionalmente las claves para los tipos), igual que sucede con &lt;em&gt;Inversify&lt;/em&gt; y el uso de los decoradores para la inyección, usar una librería para como &lt;em&gt;Brusc&lt;/em&gt; nos ofrecerá varios beneficios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Facilidad de bootstrapping de la librería, sin necesidad de tener que pensar en el orden de inicialización de instancias (pueden ser agrupadas por capas, intención, ...)&lt;/li&gt;
&lt;li&gt;Protección frente a dependencias circulares (se lanzaría error de inicialización en lugar de quedarse en un bucle infinito)&lt;/li&gt;
&lt;li&gt;Declaración clara de instancias en el contenedor (singletons para instancias reusables, prototipos para instancias con estado)&lt;/li&gt;
&lt;li&gt;Posible instrumentalización de las instancias en el contenedor (&lt;a href="https://github.com/alextremp/brusc#adapter" rel="noopener noreferrer"&gt;ver adapters de Brusc&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y por último pero no menos importante, en el caso concreto de &lt;em&gt;Brusc&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pensado para &lt;a href="https://github.com/alextremp/brusc#integration-testing" rel="noopener noreferrer"&gt;facilitar la implementación de tests de integración&lt;/a&gt; usando los &lt;code&gt;inject.defaults&lt;/code&gt; para sustituir instancias del contenedor durante la ejecución de un test. &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Pros y contras
&lt;/h1&gt;

&lt;p&gt;Para terminar, teniendo en cuenta que las guías de diseño, principios, patrones y demás están ahí para darnos herramientas que nos faciliten tomar decisiones en el desarrollo, pero nunca hay una única ni mejor manera de implementar una aplicación, me gustaría comentar algunos pros y contras de aplicar arquitecturas limpias en frontend, para animaros a usarlas pero también para evitar desengaños xD&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contras&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;El tamaño final de la solución se verá incrementado: Si bien nos puede compensar la mantenibilidad, testabilidad, ... en proyectos grandes, introducir dependencias o hacer una separación muy granular de las capas, nos va a incrementar el tamaño del distribuible final, cosa que debemos contemplar cuando se trata de un fichero que terminará siendo descargado desde terminales móviles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Se debe escribir más código para poder representar cada entidad, repositorio, caso de uso, ... Más código ejecutable implica más código a mantener.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dependencia a frameworks/librerías, ya sea &lt;em&gt;Brusc&lt;/em&gt;, &lt;em&gt;inversify&lt;/em&gt; o cualquier otra, incluso privada, para implementar de otra manera la inversión de control.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Baja curva de aprendizaje (y mantenibilidad): aplicar una arquitectura homogénea a todos los proyectos posibles (incluso independientemente del contexto de ejecución front/back), permite a los desarrolladores adaptarse más rápidamente a cualquier proyecto OOP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testabilidad: se facilita la creación de tests unitarios y de integración.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extensibilidad: se pueden realizar cambios, reemplazar componentes, ... sin afectar a todo el código.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lo resumiría en simplicidad.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hasta aquí llega lo que quería compartir con vosotros, así que termino el tostón por hoy, si habéis llegado hasta aquí ya me hacéis feliz ;)&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>frontend</category>
      <category>architecture</category>
      <category>ioc</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Integrando TestContainers en el contexto de Spring en nuestros tests</title>
      <dc:creator>Alex Castells</dc:creator>
      <pubDate>Tue, 22 Dec 2020 08:20:35 +0000</pubDate>
      <link>https://dev.to/adevintaspain/integrando-testcontainers-en-el-contexto-de-spring-en-nuestros-tests-5b7d</link>
      <guid>https://dev.to/adevintaspain/integrando-testcontainers-en-el-contexto-de-spring-en-nuestros-tests-5b7d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Aún me acuerdo de cómo hace muuuuchos años, en "la edad de los hierros", configurar un entorno local para desarrollar partes de plataformas complejas era un drama, y cómo se acababan configurando hosts in-house para replicar esa infraestructura externa y poder trabajar en equipo sobre la misma base.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pero, a la hora de ejecutar tests en esas máquinas, a la que se necesitara algún cambio de versión mientras se seguía desarrollando sobre lo antiguo, o simplemente cambiar el tipo de dato de una columna de BBDD, drama otra vez, o replicarse esa máquina en local "temporalmente".&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En este post os daré una pincelada a cómo podemos integrar TestContainers en el flujo de ejecución de tests con &lt;a href="https://junit.org/junit5/docs/current/user-guide/" rel="noopener noreferrer"&gt;Junit5&lt;/a&gt; para un servicio desarrollado con &lt;a href="https://spring.io/projects/spring-boot" rel="noopener noreferrer"&gt;Spring Boot&lt;/a&gt;, dejando que sea Spring quien haga el trabajo por nosotros, aprovechándonos de las características del ciclo de vida del ApplicationContext durante la ejecución de los tests.&lt;/p&gt;

&lt;p&gt;Para ello, integraré &lt;code&gt;testcontainers&lt;/code&gt; con distintas soluciones sobre un proyecto demo base, y os expondré corner cases en los que esas soluciones pueden no ser operativas, teniendo en cuenta que para los tests, y entornos de CI, por lo general es [una mala práctica fijar puertos.], de modo que este post se focalizará en la ejecución de tests con infraestructuras externas levantadas en local en puertos dinámicos.&lt;br&gt;
(&lt;a href="https://www.testcontainers.org/features/networking/" rel="noopener noreferrer"&gt;https://www.testcontainers.org/features/networking/&lt;/a&gt;) &lt;code&gt;From the host's perspective Testcontainers actually exposes this on a random free port. This is by design, to avoid port collisions that may arise with locally running software or in between parallel test runs.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Para darle un poco de gracia al uso de contenedores tanto para el desarrollo local como para los tests, y entender por qué &lt;strong&gt;usándolos podemos lograr reducir al máximo el riesgo de subir código a un entorno real cuando los tests pasan y "en local funciona"&lt;/strong&gt;, os propongo un servicio que no expondrá una API, sino que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debe escuchar mensajes de texto emitidos por otro servicio a una cola de RabbitMQ&lt;/li&gt;
&lt;li&gt;debe grabar esos mensajes a una base de datos PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A priori, un servicio muy sencillo, pero &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cómo validaríamos que realmente funciona?&lt;/li&gt;
&lt;li&gt;cómo automatizaríamos esas validaciones?&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Como base, usaré este repositorio que implementa el servicio:&lt;/p&gt;

&lt;p&gt;😑 &lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo" rel="noopener noreferrer"&gt;Rama base, sin TestContainers&lt;/a&gt;, pero podremos probarlo haciendo hecho un &lt;code&gt;docker-compose up&lt;/code&gt; previamente.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;También, como referencia a las pruebas que comentaré por puntos al final del post, os dejo estas PR para poder ver cómo cambiaría el código base, o descargaros la rama para jugar.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;😕 &lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo/pull/3/files" rel="noopener noreferrer"&gt;TestContainers integrados con JUnit&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
🚀 &lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo/pull/2/files" rel="noopener noreferrer"&gt;TestContainers singleton gestionados manualmente&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
😍 &lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo/pull/1/files" rel="noopener noreferrer"&gt;TestContainers gestionados por Spring&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pero antes de empezar, una breve intro a &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt; y &lt;a href="https://www.testcontainers.org/" rel="noopener noreferrer"&gt;TestContainers&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Docker Compose
&lt;/h1&gt;

&lt;p&gt;Hoy en día, &lt;a href="https://www.docker.com/get-started" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; facilita y estandariza la forma en cómo los servicios de nuestras plataformas se despliegan en la nube, y &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt; nos facilita la vida en el entorno local, habilitándonos esos servicios de infraestructuras (bases de datos, sistemas de mensajería, ...) externas de un modo fiable.&lt;/p&gt;

&lt;p&gt;Seguramente habréis visto en la raíz de gran cantidad de repositorios un fichero &lt;code&gt;docker-compose.yml&lt;/code&gt;, por lo general muy fáciles de entender a primera vista, como por ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.5'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="c1"&gt;# base de datos PostgreSQL&lt;/span&gt;
  &lt;span class="na"&gt;message-store-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# https://hub.docker.com/_/postgres&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:13.0-alpine&lt;/span&gt;
    &lt;span class="c1"&gt;# mapeo de puertos (puerto_accesible_desde_localhost:puerto_del_servicio_en_el_contenedor)&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="c1"&gt;# configuración de la imagen de docker&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo"&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p4ssw0rd"&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messagestore"&lt;/span&gt;
    &lt;span class="c1"&gt;# inicialización de la base de datos &lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./.init/message-store-db/init.sql:/docker-entrypoint-initdb.d/init.sql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con instrucciones para levantar el repositorio en local, y que con un simple comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tenemos la infraestructura externa, en este caso una base de datos, lista en nuestra máquina para poder operar con ella desde nuestro servicio levantado en local, o incluso en este ejemplo, conectándonos directamente a PostgreSQL con algún visor de bases de datos.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Esto nos permite validar que nuestro proyecto, que usará un driver específico para la base de datos, ejecutando sentencias o configurando opciones que quizá no están disponibles para todas las versiones de ese sistema de base de datos, funciona.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Y como podremos levantar en local la misma versión de base de datos que tengamos en entorno real, el riesgo de que no funcione ahí se reduce a mínimos.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Esto no pasaría implementando los accesos a base de datos de los tests con por ejemplo, una base de datos en memoria como &lt;a href="https://www.h2database.com/html/main.html" rel="noopener noreferrer"&gt;H2&lt;/a&gt;, que por defecto no soporta PL/SQL.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pero, si desarrollamos en local con esos servicios, mirando que nuestro servicio opera correctamente con ellos, &lt;strong&gt;¿por qué no automatizar los tests de integración usando el mismo sistema y así poder ejecutar los tests tanto en nuestra máquina como en nuestro sistema de CI?&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  TestContainers
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.testcontainers.org/" rel="noopener noreferrer"&gt;TestContainers&lt;/a&gt; es una librería Java diseñada para controlar las instancias de servicios dockerizados, enfocada a ser integrada con JUnit/Spock/... para nuestros tests de integración.&lt;/p&gt;

&lt;p&gt;Como ejemplo de uso, la misma PostgreSQL creada con la API de TestContainers quedaría así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;PostgreSQLContainer&lt;/span&gt; &lt;span class="n"&gt;messageStoreDbContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostgreSQLContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"postgres:13.0-alpine"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withDatabaseName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"message-store-db"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"demo"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p4ssw0rd"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withInitScript&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/.init/message-store-db/init.sql"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;messageStoreDbContainer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setPortBindings&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5432:5432"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// podemos arrancar el servicio con&lt;/span&gt;
&lt;span class="n"&gt;messageStoreDbContainer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// y pararlo con&lt;/span&gt;
&lt;span class="n"&gt;messageStoreDbContainer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otro componente que nos ofrece, para permitirnos simular los tests con exactamente los mismos servicios que levantaríamos con el &lt;code&gt;docker-compose.yml&lt;/code&gt;, es &lt;a href="https://www.testcontainers.org/modules/docker_compose/" rel="noopener noreferrer"&gt;DockerComposeContainer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un ejemplo de uso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;WaitStrategy&lt;/span&gt; &lt;span class="n"&gt;postgresWaitStrategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WaitAllStrategy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;WITH_INDIVIDUAL_TIMEOUTS_ONLY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withStrategy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Wait&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forListeningPort&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withStrategy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Wait&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forLogMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".*database system is ready to accept connections.*"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="nc"&gt;DockerComposeContainer&lt;/span&gt; &lt;span class="n"&gt;dockerComposeContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DockerComposeContainer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"docker-compose.yml"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withLocalCompose&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitingFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"message-store-db"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postgresWaitStrategy&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;dockerComposeContainer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Veréis que aquí se usa una funcionaliad opcional de la API de DockerComposeContainer para poder definir cómo debe esperarse el componente para decir que está ready hasta que el servicio "message-store-db" del "docker-compose.yml" esté ready.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Por defecto, se esperaría 60 segundos a que el servicio tuviera el puerto expuesto y listo para aceptar conexiones.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ahora le estamos indicando que a parte de esperar al puerto, se espere también a que PostgreSQL, en su proceso de inicialización, haya sacado esa traza de log indicando que está completamente ready&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para el caso propuesto, dado que los tests deben validar la integración con los servicios definidos en el &lt;code&gt;docker-composer&lt;/code&gt;, usaré &lt;code&gt;DockerComposeContainer&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Y en realidad, para la mayoría de casos es el único que vamos a necesitar.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Así que al lío :)&lt;/p&gt;

&lt;h1&gt;
  
  
  ¿Cómo lo integramos en nuestros tests?
&lt;/h1&gt;

&lt;p&gt;Nos tenemos que preguntar: ¿Los servicios de infraestructura externa deben cambiar para distintos escenarios de test, independientemente del contexto de Spring?&lt;/p&gt;

&lt;p&gt;O por lo contrario, ¿Los servicios de infraestructura externa deben tener el mismo ciclo de vida que el contexto de Spring?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Para ejemplificar las pruebas de integración de TestContainers en JUnit y SpringBoot, encontraréis este test sencillo en la rama &lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo" rel="noopener noreferrer"&gt;master&lt;/a&gt;&lt;/em&gt; del proyecto demo.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="nd"&gt;@Sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/fixtures/message-store-db/clean.sql"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApplicationTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;RabbitTemplate&lt;/span&gt; &lt;span class="n"&gt;rabbitTemplate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;MessageMapper&lt;/span&gt; &lt;span class="n"&gt;messageMapper&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${mq-server.routing.exchange}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;demoExchange&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${mq-server.routing.message.queue.dispatched}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;messageDispatchedQueue&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@Test&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;shouldSaveEmittedMessage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;givenText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"hello world"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;rabbitTemplate&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;convertAndSend&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;demoExchange&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messageDispatchedQueue&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;givenText&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;atMost&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ONE_SECOND&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"message [%s] has been saved"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;givenText&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;until&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;messageMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selectOneByText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;givenText&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isPresent&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Nota: para los que no la conozcáis, &lt;code&gt;await&lt;/code&gt; es un operador de &lt;a href="https://github.com/awaitility/awaitility" rel="noopener noreferrer"&gt;awaitility&lt;/a&gt;, muy útil para validación de resultados en procesos asíncronos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ciclo de vida controlado por JUnit
&lt;/h2&gt;

&lt;p&gt;Empecemos con la chicha :)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo/pull/3/files" rel="noopener noreferrer"&gt;código de referencia de este punto&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si miramos la &lt;a href="https://www.testcontainers.org/test_framework_integration/junit_5" rel="noopener noreferrer"&gt;documentación para integrar TestContainers con JUnit&lt;/a&gt;, la integración básica es muy sencilla: mediante anotaciones de la propia librería, se habilita la gestión de arranque y parada de los servicios dockerizados, y éstos pueden exponer los puertos que ha podido coger por estar libres.&lt;/p&gt;

&lt;p&gt;⚠️ Pero ojo, nosotros necesitamos ciertas propiedades en el contexto de Spring, o dicho de otra manera, ¿cómo asignaremos los puertos designados a nuestras conexiones de base de datos y MQ?&lt;/p&gt;

&lt;p&gt;En este caso, usaremos la capacidad de Spring de recoger propiedades de &lt;code&gt;System&lt;/code&gt;, pudiendo configurar nuestros tests así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="nd"&gt;@Testcontainers&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractIntegrationTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"message-store-db"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SPRING_MESSAGE_STORE_DB_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"message-store-db.port"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mq-server"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5672&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SPRING_MQ_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mq-server.port"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;


  &lt;span class="nd"&gt;@Container&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;DockerComposeContainer&lt;/span&gt; &lt;span class="n"&gt;dockerServices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DockerComposeContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"docker-compose.yml"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withLocalCompose&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withExposedService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withExposedService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@BeforeAll&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SPRING_MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getServicePort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SPRING_MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getServicePort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este caso, sólo que nuestro &lt;code&gt;ApplicationTest&lt;/code&gt; extienda esta abstracción, al ejecutarse, dispondrá de todo lo necesario para funcionar porque:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Antes de la ejecución de los tests, TestContainers mediante los eventos de JUnit habrá levantado el DockerComposeContainer.&lt;/li&gt;
&lt;li&gt;Después, JUnit procesará el &lt;code&gt;@BeforeAll&lt;/code&gt;, donde hemos seteado las propiedades necesarias para el contexto de Spring, como &lt;code&gt;message-store-db.port&lt;/code&gt;, con el valor que dispondrá cada servicio ya esperando conexiones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y el post terminaría aquí, si no fuera porque: &lt;br&gt;
&lt;strong&gt;¿Funcionaría este approach si &lt;code&gt;AbstractIntegrationTest&lt;/code&gt; fuera extendida por más de una implementación?&lt;/strong&gt; &lt;br&gt;
Correcto, en lugar de usar el ejemplo de TestContainers lo he puesto en una abstracción, porque como deduciréis, la respuesta es NO.&lt;/p&gt;

&lt;p&gt;Si ejecutáramos sólo 1 clase de tests extendiendo &lt;code&gt;AbstractIntegrationTest&lt;/code&gt;, ya veríamos una cosa que nos debería chirriar en los logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fovr3qhp3v1txkna64ogb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fovr3qhp3v1txkna64ogb.png" alt="docker-compose-down"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El test pasa, y al terminar, TestContainers intercepta el final de ejecución de los tests de la clase mediante JUnit y detiene el contenedor, antes de que se cierre el contexto de Spring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Por qué y por qué puede ser un problema?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eso pasa porque Spring, va a mantener el mismo &lt;code&gt;ApplicationContext&lt;/code&gt; para la ejecución de todos los tests de todas las clases que no estén marcadas con &lt;code&gt;@DirtiesContext&lt;/code&gt;, para evitar el coste de levantar el contexto para cada clase anotada con &lt;code&gt;@SpringBootTest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Y eso puede ser un problema si nos interesa hacer grupos de tests (por ejemplo, una clase de test para cada &lt;code&gt;@RestController&lt;/code&gt; si estamos desarrollando APIs), ya que &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;los servicios dockerizados, al volverse a levantar, van a tener otro puerto libre designado para conectarnos a ellos.&lt;/li&gt;
&lt;li&gt;aunque se ejecute el &lt;code&gt;@BeforeAll&lt;/code&gt; asignando nuevos puertos a &lt;code&gt;System&lt;/code&gt;, el contexto de Spring no los va a usar dado que no se va a refrescar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y como seguramente no nos va a interesar que los servicios dockerizados se apaguen y levanten de nuevo para cada conjunto de tests, por el coste en tiempo, esto nos lleva a la gestión manual de los contenedores, ya que &lt;code&gt;There is no special support for this use case provided by the Testcontainers extension&lt;/code&gt; tal como indica la sección &lt;a href="https://www.testcontainers.org/test_framework_integration/manual_lifecycle_control/" rel="noopener noreferrer"&gt;Singleton containers&lt;/a&gt; de la documentación.&lt;/p&gt;

&lt;p&gt;Pero no preocuparse, trabajamos con Spring por lo que los singletons no nos dan miedo, ¿no? ;)&lt;/p&gt;
&lt;h2&gt;
  
  
  TestContainers Singleton gestionados manualmente, con ciclo de vida controlado por JUnit
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo/pull/2/files" rel="noopener noreferrer"&gt;código de referencia de este punto&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;JUnit5 tiene una característica que nos permite anidar en una única clase, mediante &lt;code&gt;@Nested&lt;/code&gt;, conjuntos de clases (test cases) que a su vez definen los tests a ejecutar.&lt;/p&gt;

&lt;p&gt;Así que podemos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tener una clase única, anotada con &lt;code&gt;@SpringBootTest&lt;/code&gt; para levantar el contexto de Spring, en la que el ciclo de de vida de JUnit (&lt;code&gt;@BeforeAll&lt;/code&gt; -&amp;gt; ejecución de los tests de cada test case -&amp;gt; &lt;code&gt;@AfterAll&lt;/code&gt;) va a ser el mismo que el de Spring.&lt;/li&gt;
&lt;li&gt;Definir test cases independientes con su grupo de &lt;code&gt;@Test&lt;/code&gt; a ejecutar.&lt;/li&gt;
&lt;li&gt;Anidar a la clase única estos test cases con &lt;code&gt;@Nested&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Algo tal que así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IntegrationTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;DockerizedInfrastructure&lt;/span&gt; &lt;span class="no"&gt;DOCKERIZED_INFRASTRUCTURE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DockerizedInfrastructure&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="nd"&gt;@BeforeAll&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;dockerComposeUp&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;DOCKERIZED_INFRASTRUCTURE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@AfterAll&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;dockerComposeDown&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;DOCKERIZED_INFRASTRUCTURE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@Nested&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ApplicationTestCase&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sólo necesitaremos que en este caso, &lt;code&gt;ApplicationTest&lt;/code&gt; de la rama master, sea abstracta (y para este ejemplo, renombrada a &lt;code&gt;ApplicationTestCase&lt;/code&gt; que define mejor su intención).&lt;/p&gt;

&lt;p&gt;A parte, para hacer legible el ejemplo y evitar una mezcla de responsabilidades entre la coordinación de los tests que hará &lt;code&gt;IntegrationTest&lt;/code&gt; y la coordinación de los servicios dockerizados, aquí sólo queda el singleton &lt;code&gt;DOCKERIZED_INFRASTRUCTURE&lt;/code&gt; para poderlo gestionar al inicio y final de la ejecución de test cases, pero su implementación se ha separado a:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DockerizedInfrastructure.java&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DockerizedInfrastructure&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"message-store-db"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SPRING_MESSAGE_STORE_DB_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"message-store-db.port"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mq-server"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5672&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SPRING_MQ_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mq-server.port"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;DockerComposeContainer&lt;/span&gt; &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DockerizedInfrastructure&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Inicializamos los servicios de docker-compose&lt;/span&gt;
    &lt;span class="n"&gt;dockerServices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DockerComposeContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"docker-compose.yml"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withLocalCompose&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withExposedService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withExposedService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arrancamos los servicios dockerizados&lt;/span&gt;
    &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Seteamos System properties para propiedades del contexto de Spring&lt;/span&gt;
    &lt;span class="n"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SPRING_MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getServicePort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;setProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SPRING_MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getServicePort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Apagamos los servicios dockerizados&lt;/span&gt;
    &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y llegados a este punto, que ahora sí funcionará tal como esperamos a medida que el proyecto vaya creciendo y necesitemos agrupar los tests en distintos test cases, otra vez daría el post por cerrado, si no fuera que &lt;strong&gt;¿realmente es necesario que gestionemos manualmente un singleton y su ciclo de vida con JUnit para coordinarlo con el ciclo de vida del ApplicationContext de Spring?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Y aunque no suponga un problema hacerlo, otra vez la respuesta es NO, dado que Spring, como ya comenté, va a mantener el &lt;code&gt;ApplicationContext&lt;/code&gt; durante la ejecución de los tests y dispone de una serie de características que nos permitirán jugar con la gestión de los servicios dockerizados, las propiedades para el ApplicationContext (en este caso los puertos de los servicios),...&lt;/p&gt;

&lt;p&gt;Además, con esta gestión manual, nos podríamos encontrar con los siguientes inconvenientes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Añadir un nuevo test case requiere modificación en la base &lt;code&gt;IntegrationTest&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ya sea por reagrupación de test cases o para configuraciones distintas por grupos de test cases, no podríamos tener 2 &lt;code&gt;IntegrationTest&lt;/code&gt; distintos anidando distintos test cases, sin más. Por ejemplo:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Supongamos que replicamos el &lt;code&gt;ApplicationTestCase&lt;/code&gt; varias veces y los anidamos en también una réplica de &lt;code&gt;IntegrationTest&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9oo634dypw7g3js1d8ix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9oo634dypw7g3js1d8ix.png" alt="breaking-in-junit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Al ser réplicas, en los &lt;code&gt;IntegrationTestN&lt;/code&gt;, &lt;code&gt;@BeforeAll&lt;/code&gt; y &lt;code&gt;@AfterAll&lt;/code&gt; reinician los contenedores pero de nuevo, nos encontramos que el &lt;code&gt;ApplicationContext&lt;/code&gt; para los &lt;code&gt;ApplicationTestCaseN...&lt;/code&gt; es el mismo y por tanto, sólo iniciará la conexión a las infraestructuras la primera vez que además, JUnit no va a ejecutar en un mismo orden, por lo que el segundo bloque de tests ejecutado va a fallar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fylxu1zdh5vwo5pvxtutt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fylxu1zdh5vwo5pvxtutt.png" alt="tests-failed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para resolver este problema, tendríamos que limpiar el contexto de Spring para cada &lt;code&gt;ApplicationTestN&lt;/code&gt;, de manera que cada test case creara un nuevo &lt;code&gt;ApplicationContext&lt;/code&gt; con configuración fresca de los puertos activados, marcándolos todos ellos con dirties context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="nd"&gt;@DirtiesContext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto de nuevo, aunque lógicamente es un corner case forzado para validar posibles flaquezas de la solución, nos lleva a pensar que realmente, lo ideal sería que la gestión del ciclo de vida de los servicios dockerizados fueran gestionados directamente con Spring.&lt;/p&gt;

&lt;p&gt;Así, que como guindilla del pastel, continuamos :)&lt;/p&gt;

&lt;h2&gt;
  
  
  TestContainers sólo gestionados por Spring
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/alextremp/testcontainers-springboot-demo/pull/1/files" rel="noopener noreferrer"&gt;código de referencia de este punto&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Llegados a este punto, sabemos que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Los test de integración marcados con &lt;code&gt;@SpringBootTest&lt;/code&gt; comparten el &lt;code&gt;ApplicationContext&lt;/code&gt; a no ser que sean marcados con &lt;code&gt;@DirtiesContext&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DockerizedInfrastructure&lt;/code&gt; debería ser tratado como un singleton (&lt;code&gt;@Component&lt;/code&gt;) dentro del contexto de Spring para que en todo momento, el &lt;code&gt;ApplicationContext&lt;/code&gt; pueda accederlo o regenerarlo en dirties.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DockerizedInfrastructure&lt;/code&gt; debe reconfigurar las propiedades de conexión de la infraestructura.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ pero, si hacemos un &lt;code&gt;@Component&lt;/code&gt;, que Spring inicializa, ya no podemos setear las propiedades en &lt;code&gt;System&lt;/code&gt;, dado que la lectura de beans (y propiedades) es anterior a la instanciación.&lt;/p&gt;

&lt;p&gt;💡 pero por otro lado es Spring, y siempre tiene una solución a cómo gestionar estos casos: podemos usar el &lt;a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ConfigurableApplicationContext.html" rel="noopener noreferrer"&gt;&lt;code&gt;ConfigurableApplicationContext&lt;/code&gt;&lt;/a&gt; para refrescar el contexto con las propiedades iniciales tal como nos va a interesar.&lt;/p&gt;

&lt;p&gt;En definitiva, para que sea gestionado completamente por Spring, podríamos modificar &lt;code&gt;DockerizedInfrastructure.java&lt;/code&gt; así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DockerizedInfrastructure&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"message-store-db"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SPRING_MESSAGE_STORE_DB_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"message-store-db.port"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mq-server"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5672&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SPRING_MQ_SERVER_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mq-server.port"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;DockerComposeContainer&lt;/span&gt; &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nc"&gt;DockerizedInfrastructure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConfigurableApplicationContext&lt;/span&gt; &lt;span class="n"&gt;configurableApplicationContext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Inicializamos los servicios de docker-compose&lt;/span&gt;
    &lt;span class="n"&gt;dockerServices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DockerComposeContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"docker-compose.yml"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withLocalCompose&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withExposedService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withExposedService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Arrancamos los servicios dockerizados&lt;/span&gt;
    &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Refrescamos el contexto de Spring con los puertos designados&lt;/span&gt;
    &lt;span class="nc"&gt;TestPropertyValues&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s=%s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="no"&gt;SPRING_MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getServicePort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MESSAGE_STORE_DB_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MESSAGE_STORE_DB_PORT&lt;/span&gt;&lt;span class="o"&gt;)),&lt;/span&gt;
          &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s=%s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="no"&gt;SPRING_MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getServicePort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MQ_SERVER_SERVICE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;MQ_SERVER_PORT&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;applyTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configurableApplicationContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEnvironment&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@PreDestroy&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;preDestroy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Apagamos los servicios dockerizados&lt;/span&gt;
    &lt;span class="n"&gt;dockerServices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fijaros que aquí, &lt;code&gt;DockerizedInfrastructure&lt;/code&gt; no tiene ni el constructor ni ningún método público, dado que ahora no es necesario que ninguna clase de test acceda a ella para manipular su estado. &lt;em&gt;Sí lo es la declaración de la clase, luego veremos por qué :)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Con el &lt;code&gt;@PreDestroy&lt;/code&gt; nos aseguramos de apagar los servicios dockerizados cuando el &lt;code&gt;ApplicationContext&lt;/code&gt; emita la señal de que está siendo destruido, que será o cuando hayan terminado los tests, o cuando un test esté marcado con &lt;code&gt;@DirtiesContext&lt;/code&gt;, antes de la recreación del contexto.&lt;/p&gt;

&lt;p&gt;Ésta sería una manera muy elegante de integrar esta &lt;code&gt;DockerizedInfrastructure&lt;/code&gt; dado que no haría falta modificar nada en nuestra configuración de los tests, pero &lt;strong&gt;¿qué pasaría si algún componente del servicio necesitara acceder a la base de datos durante la creación del contexto de Spring?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para forzar el caso, os pongo este ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventBreaker&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;MessageMapper&lt;/span&gt; &lt;span class="n"&gt;messageMapper&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;InventBreaker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MessageMapper&lt;/span&gt; &lt;span class="n"&gt;messageMapper&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;messageMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messageMapper&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@PostConstruct&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;tryToBreakTheTestContext&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;messageMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selectOneByText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"applicationContext lanzará excepción si no hay conectividad a la BBDD"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supongamos que &lt;code&gt;InventBreaker&lt;/code&gt;, por lo que sea, necesita inicializarse con un valor de la BBDD, y si no hacemos nada, va a pasar esto:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzan9kmieugyve8ftkxwq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzan9kmieugyve8ftkxwq.png" alt="breaking-the-invent"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spring, la única instrucción a la que va a hacer caso para que &lt;code&gt;InventBreaker&lt;/code&gt; se espere a que &lt;code&gt;DockerizedInfrastructure&lt;/code&gt; haya hecho la magia es con &lt;code&gt;@DependsOn("dockerizedInfrastructure")&lt;/code&gt;, &lt;strong&gt;pero wait!&lt;/strong&gt; Si &lt;code&gt;InventBreaker&lt;/code&gt; forma parte del código del servicio en lugar del código de test, no podemos hacer esto.&lt;/p&gt;

&lt;p&gt;💡 pero de nuevo, Spring ya contempla casi todos los corner cases que nos podamos imaginar, y nos va a permitir sincronizar la creación del contexto requerido para test con el contexto requerido para la aplicación modificando sólo una línea en la declaración de &lt;code&gt;@SpringBootTest&lt;/code&gt;, dejando nuestra abstracción de tests de integración así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;DockerizedInfrastructure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y listo! Ahora todo se inicializará y se apagará según el orden esperado.&lt;/p&gt;

&lt;p&gt;Además, podríamos abstraer esta definición para ser usada en cualquier otra clase de test así:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AbstractIntegrationTest.java&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;DockerizedInfrastructure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AbstractIntegrationTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y todas las clases de test con un &lt;code&gt;extends AbstractIntegrationTest&lt;/code&gt; dispondrán de los servicios dockerizados para operar con ellos, sin necesidad de que ninguno esté marcado con &lt;code&gt;@DirtiesContext&lt;/code&gt;, aunque marcándolo también funcionaría (pagando el coste de reiniciar el contexto, junto con los servicios dockerizados).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2kip4aysdtc6gi3o8krh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2kip4aysdtc6gi3o8krh.png" alt="all-green"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;TestContainers es una herramienta muy potente para evitar que perdamos tiempo simulando / mockeando lo que debería hacer nuestra capa de integración con infraestructura a la hora de desarrollar los tests y es bueno aprovecharse de ello!&lt;/p&gt;

&lt;p&gt;Aún así, no siempre hay una manera de implementar las cosas y es mejor adaptarse a la que mejor nos convenga en cada caso con conocimiento de las herramientas que estamos usando, y cuando usamos un framework como Spring, éste dispone para nosotros muchas facilidades para agilizar los desarrollos, tanto de la aplicación, como en este caso de los tests.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Y llegados a este punto, no queda mucho más que decir por mi parte, espero que os haya gustado este viaje a las entrañas de Spring en el contexto de testing, así que si es el caso o si gestionáis el ciclo de vida de los contenedores de test de alguna otra forma y queréis compartirla dejad un comentario, que me gustará leeros.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Y ahora sí, fin :)&lt;/p&gt;

</description>
      <category>testing</category>
      <category>microservices</category>
      <category>springboot</category>
      <category>docker</category>
    </item>
  </channel>
</rss>
