Índice de Contenidos
1. Entorno
Este tutorial se ha redactado utilizando el siguiente entorno para el código de ejemplo:
- MackBook Pro Retina 15″
- 2,2Ghz Intel Core i7
- 16GB RAM
- macOS Catalina 10.15.7
- Micronaut 2.1.2, AWS SDK S3 1.11.899
2. Introducción
Micronaut es un framework JVM muy polivalente y con muchas funcionalidades. Entre ellas se encuentra la integración con servicios cloud como AWS, Netflix Eureka o Azure.
En este tutorial se propone la implementación de una API Rest y poder con ella conectarnos a un servicio de almacenamiento S3 de Amazon Web Service para poder subir y descargar ficheros, muy similar a un servicio FTP y además complementar así una serie de tutoriales de AWS donde se hace uso de Terraform en la automatización de todo el proceso de configuración donde usamos otros servicios de AWS como EC2 (al que además hacemos uso de SFTP) y Lambda para gestionar eventos de forma automática.
3. Creación de un bucket de AWS S3
Los objetos en Amazon S3 están organizados dentro buckets. Un bucket se puede entender como el directorio raíz a partir del cual van almacenados los diferentes recursos y Amazon proporciona para ello el conjunto de librerías de AWS SDK.
Para empezar creando un bucket, nos dirigimos a la página de la consola de AWS con nuestra cuenta previamente activa, hacemos login y seleccionamos S3.
Pulsamos en el botón para crear un bucket. A continuación especificamos el nombre que queremos darle sabiendo que éste va a ser único para la región seleccionada.
Dejamos todo por defecto y abajo del todo hacemos click en Crear Bucket. Con esto tenemos nuestro bucket listo para poder añadirle cualquier fichero.
4. Implementación de la API
Creamos un proyecto Micronaut con maven a través de línea de comandos con SDKMAN!:
mn create-app micronaut-s3 --build maven
Añadimos al POM la dependencia de la librería de AWS S3 con la versión más reciente:
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3 --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-s3</artifactId> <version>1.11.899</version> </dependency>
Lo primero de todo es crear una clase de configuración de S3 en la que especificamos la clave de acceso, la clave secreta, la region y el nombre del bucket. La información de las claves la podemos encontrar en nuestra página de Security Credentials de nuestra consola de Amazon. Si no la tenemos creada, la generamos pulsando sobre el botón correspondiente.
La clave secreta se debe apuntar o registrar en algún sitio porque no se volverá a poder consultar más. Sino, habría que volver a generar el par de claves de nuevo.
Una vez tenemos estas claves generadas, vamos a añadir la información necesaria de configuración en el fichero application.yml que se encuentra por defecto en la carpeta resources de nuestro proyecto para poder hacer uso de ello en nuestro cliente de S3.
aws: s3: accessKey: 'my access key' secretKey: 'my secret key' region: 'eu-west-2' bucket: 'ftp-storage-bucket'
Una vez hecho esto, vamos a crear una clase de configuración de S3 para poder hacer uso de estas propiedades en nuestras clases.
public interface S3Configuration { @Nonnull String getBucket(); @Nonnull String getRegion(); @Nonnull String getAccessKey(); @Nonnull String getSecretKey(); }
import io.micronaut.context.annotation.Property; import javax.annotation.Nonnull; import javax.inject.Singleton; @Singleton public class S3ConfigurationProperties implements S3Configuration { @Property(name="aws.s3.accessKey") private String accessKey; @Property(name="aws.s3.secretKey") private String secretKey; @Property(name="aws.s3.region") private String region; @Property(name="aws.s3.bucket") private String bucket; // Getters }
Como se puede ver, con la anotación @Property de Micronaut estamos inyectando un nombre específico de propiedad que tengamos en nuestro application.yml. La anotación @Singleton sobre la declaración de la clase nos va a permitir que se pueda inyectar el servicio en el resto de clases.
Procedemos a crear nuestra clase de servicio de s3.
public interface S3Service { void getObject(String fileNamePath, S3Configuration configuration); void putObject(String filePath, S3Configuration configuration); }
@Singleton public class S3ServiceImpl implements S3Service{ private AmazonS3 s3client; public static final String UPLOAD_FOLDER = "uploads/"; private void authentication(S3Configuration configuration) { AWSCredentials credentials = new BasicAWSCredentials( configuration.getAccessKey(), configuration.getSecretKey() ); s3client = AmazonS3ClientBuilder .standard() .withCredentials(new AWSStaticCredentialsProvider(credentials)) .withRegion(configuration.getRegion()) .build(); } @Override public void getObject(String fileNamePath, S3Configuration configuration){ authentication(configuration); S3Object s3object = s3client.getObject( configuration.getBucket(), fileNamePath); S3ObjectInputStream inputStream = s3object.getObjectContent(); try { FileUtils.copyInputStreamToFile(inputStream, new File(fileNamePath)); } catch (IOException e) { e.printStackTrace(); } } @Override public void putObject(String filePath, S3Configuration configuration) { authentication(configuration); try { s3client.putObject( configuration.getBucket(), UPLOAD_FOLDER + filePath, new File(filePath) ); System.out.println("The file " + filePath + " has been uploaded"); } catch (AmazonServiceException e) { System.err.println(e.getErrorMessage()); return; } } }
Para realizar cualquier operación sobre S3 debemos de autenticarnos y para ello hacemos uso de AWSCredentials. Una vez autenticados podemos inicializar nuestro cliente de la librería de amazon que nos va a permitir hacer getObject y putObject para la descarga y subida de un fichero respectivamente. En nuestro ejemplo utilizamos en todo momento el directorio raíz local desde el que se va a llamar a la instancia de Micronaut a modo de ejemplo.
Por último solo nos queda crear nuestro Controller con la implementación de los endpoints. Para esto la sintaxis es la que ya conocemos de Micronaut para definirlo y crear peticiones tipo GET o POST.
@Controller("/s3") public class S3Controller { @Inject S3Service client; @Inject S3Configuration configuration; @Get("/download/{name}") String download(String name) { client.getObject(name,configuration); return "File downloaded --> " + name; } @Get("/download/{name}") String upload(String name) { client.putObject(name, configuration); return "File uploadaded --> " + name; } }
Simplemente inyectamos nuestros dos servicios de cliente y de configuración de S3 y hacemos uso de ellos en las peticiones.
Si levantamos una instancia de Micronaut y hacemos la llamada GET para descargar un fichero alojado en nuestro bucket S3 pasando como parámetro la ruta del fichero, nos lo descargará en la carpeta desde donde hemos levantado nuestro servidor de Micronaut porque por defecto nos lo descarga en el mismo directorio.
Y si subimos nos crea la carpeta uploads con el fichero dentro:
Para la consulta de todo el código desarrollado en caso de necesidad, se puede encontrar en el siguiente repositorio de Github el ejemplo seguido con los mismos pasos que se han ido explicando anteriormente.
5. Conclusión
Como hemos visto Micronaut es un framework que se acopla perfectamente con servicios cloud como AWS y además disfrutamos de sus ventajas para desarrollo de microservicios. En este ejemplo hemos visto solo la conexión con un servicio de almacenamiento S3, y nos da la posibilidad de crear estructuras más complejas en infraestructura para nuestras aplicaciones, ya que AWS tiene un gran conjunto de servicios como EC2, RDS, etc.
Creo que hay una errata en el controlador, el upload no debería ser un POST o PUT?