Al desarrollar una aplicación de Ruby on Rails, es posible que encuentre tareas de aplicación que se deben realizar de forma asíncrona. El procesamiento de datos, el envío de correos electrónicos por lotes o la interacción con API externas son ejemplos de trabajo que se pueden realizar de forma asincrónica con tareas en segundo plano. Con tareas en segundo plano, puede mejorar el rendimiento de su aplicación descargando tareas que pueden demandar gran cantidad de tiempo a una cola de procesamiento en segundo plano y, así, liberar el ciclo de solicitud o respuesta original
Sidekiq es uno de los marcos de tareas en segundo plano más utilizados que se pueden implementar en una aplicación de Rails. Cuenta con el respaldo de Redis, un sistema de almacenamiento de clave-valor en memoria, conocido por su flexibilidad y rendimiento. Sidekiq utiliza Redis como almacén de administración de tareas para procesar miles de tareas por segundo.
En este tutorial, agregaremos Redis y Sidekiq a una aplicación de Rails existente. Crearemos un conjunto de clases y métodos de trabajadores de Sidekiq para gestionar lo siguiente:
Cuando haya terminado, tendrá una aplicación de muestra que utilizará trabajadores y tareas para procesar tareas de forma asíncrona. Esta será una buena base para añadir trabajadores y trabajos a su aplicación utilizando este tutorial como punto de partida.
Para este tutorial, necesitará lo siguiente:
ufw
. Para hallar instrucciones sobre cómo configurar esto, consulte nuestro tutorial de Configuración inicial del servidor con el sistema Ubuntu 18.04.Nuestro primer paso será clonar el repositorio de rails-bootstrap de la cuenta de GitHub comunitaria de DigitalOcean. En este repositorio se incluye el código de la configuración descrita en el artículo Cómo agregar Bootstrap a una aplicación de Ruby on Rails, en el que se explica la forma de añadir Bootstrap a un proyecto de Rails 5 existente.
Clone el repositorio en un directorio llamado rails-sidekiq
:
- git clone https://github.com/do-community/rails-bootstrap.git rails-sidekiq
Diríjase al directorio rails-sidekiq
:
- cd rails-sidekiq
Para trabajar con el código, primero deberá instalar las dependencias del proyecto que se enumeran en el Gemfile de este. También deberá añadir la gema sidekiq al proyecto para trabajar con Sidekiq y Redis.
Abra el archivo de Gemfile del proyecto para editarlo con nano
o su editor favorito:
- nano Gemfile
Añada la gema en cualquier punto de las dependencias del proyecto principal (encima de las dependencias de desarrollo):
. . .
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false
gem 'sidekiq', '~>6.0.0'
group :development, :test do
. . .
Guarde y cierre el archivo cuando termine de añadir la gema.
Utilice el siguiente comando para instalar las gemas:
- bundle install
Veremos en el resultado que la gema de redis
también se instala como requisito para sidekiq
.
Luego, instalará sus dependencias de Yarn. Debido a que este proyecto de Rails 5 se modificó para proporcionar recursos con webpack, Yarn ahora gestiona sus dependencias de JavaScript. Esto significa que es necesario instalar y verificar las dependencias que se enumeran en el archivo package.json
del proyecto.
Ejecute yarn install
para instalar estas dependencias:
- yarn install
Luego, ejecute las migraciones de su base de datos:
- rails db:migrate
Una vez que hayan terminado sus migraciones, puede probar la aplicación para asegurarse de que funcione como se espera. Inicie su servidor en el contexto de su paquete local con el siguiente comando si trabaja a nivel local:
- bundle exec rails s
Si trabaja en un servidor de desarrollo, puede iniciar la aplicación con lo siguiente:
- bundle exec rails s --binding=your_server_ip
Diríjase a localhost:3000
o http://your_server_ip:3000
. Visualizará la siguiente página de destino:
Para crear un nuevo tiburón, haga clic en el botón Get Shark Info, que lo dirigirá a la ruta sharks/index
:
Para verificar que la aplicación funcione, podemos añadirle información de prueba. Haga clic en New Shark. Gracias a la configuración de autenticación del proyecto, se le solicitará un nombre de usuario (sammy) y una contraseña (tiburón).
En la página New Shark, ingrese “Gran tiburón blanco” en el campo Name y “Terrorífico” en el campo Facts.
Haga clic en el botón Create Shark para crear el tiburón. Una vez que haya creado el tiburón, podrá detener el servidor con CTRL+C
.
Con esto, habrá instalado las dependencias necesarias para su proyecto y comprobado que funciona. Luego, puede realizar algunos cambios en la aplicación de Rails para trabajar con sus recursos relacionados con los tiburones en peligro.
Para trabajar con nuestros recursos relacionados con tiburones en peligro, añadiremos un nuevo modelo a la aplicación y un controlador que regulará la manera en que se presente a los usuarios la información sobre tiburones en peligro. Nuestro objetivo final es permitir que los usuarios carguen un gran lote de información sobre tiburones en peligro, sin bloquear la función general de nuestra aplicación, y eliminen dicha información cuando ya no la necesiten.
Primero, crearemos un modelo Endangered
para nuestros tiburones en peligro. Incluiremos un campo de cadena a la tabla de nuestra base de datos para el nombre del tiburón y otro campo de cadena para las categorías de la Unión Internacional para la Conservación de la Naturaleza (UICN) que determinan el grado de riesgo en el que se encuentra cada tiburón.
En última instancia, la estructura de nuestro modelo coincidirá con las columnas del archivo CSV que usaremos para crear nuestra carga en lote. Este archivo se encuentra en el directorio db
y puede verificar su contenido con el siguiente comando:
- cat db/sharks.csv
El archivo contiene una lista de 73 tiburones en peligro y sus situaciones según la IUCN: vu significa vulnerable, en significa en peligro y cr significa en peligro crítico.
Nuestro modelo Endangered
se correlacionará con estos datos, lo que nos permitirá crear nuevas instancias de Endangered
desde este archivo CSV. Cree el modelo con el siguiente comando:
- rails generate model Endangered name:string iucn:string
Luego, genere un controlador Endangered
con una acción index
:
- rails generate controller endangered index
Esto nos proporcionará un punto de partida para crear la función de nuestra aplicación, aunque también tendremos que añadir métodos personalizados al archivo del controlador que Rails generó para nosotros.
Abra el archivo ahora:
- nano app/controllers/endangered_controller.rb
Rails nos proporciona un esquema de borrador que podemos comenzar a completar.
Primero, debemos determinar las rutas que necesitamos para trabajar con nuestros datos. Gracias al comando generate controller
, contamos con un mé todo
index para comenzar. Esto se correlacionará con una vista de index
, en la que presentaremos a los usuarios la opción para cargar tiburones en peligro.
Sin embargo, también nos convendrá tratar los casos en los que los usuarios ya hayan cargado tiburones; no necesitarán una opción de carga en este caso. De alguna forma, tendremos que evaluar la cantidad existente de casos de la clase Endangered
, puesto que más de una indica que ya se produjo la carga del lote.
Empecemos creando un método set_endangered
private
que tomará cada instancia de nuestra clase Endangered
de la base de datos. Añada el siguiente código al archivo:
class EndangeredController < ApplicationController
before_action :set_endangered, only: [:index, :data]
def index
end
private
def set_endangered
@endangered = Endangered.all
end
end
Tenga en cuenta que el filtro before_action
garantizará que el valor de @endangered
solo esté configurado para las rutas index
y data
, donde gestionaremos los datos de tiburones en peligro.
Después, añada el siguiente código al método index
a fin de determinar la ruta correcta para los usuarios que visitan esta parte de la aplicación:
class EndangeredController < ApplicationController
before_action :set_endangered, only: [:index, :data]
def index
if @endangered.length > 0
redirect_to endangered_data_path
else
render 'index'
end
end
. . .
Si hay más de 0 instancias de nuestra clase Endangered
, redirigiremos a los usuarios a la ruta data
, donde podrán ver información sobre los tiburones que crearon. De lo contrario, verán la vista de index
.
Luego, debajo del método index
, añada un método data
, que se correlacionará con una vista de data
:
. . .
def index
if @endangered.length > 0
redirect_to endangered_data_path
else
render 'index'
end
end
def data
end
. . .
A continuación, añadiremos un método para gestionar la carga de datos. Llamaremos a este método upload
e invocará una clase y método de trabajador Sidekiq para realizar la carga de datos desde el archivo CSV. En el siguiente paso, crearemos la definición de esta clase de trabajador: AddEndangeredWorker
.
Por ahora, añada el siguiente código al archivo para invocar al trabajador Sidekiq y realizar la carga:
. . .
def data
end
def upload
csv_file = File.join Rails.root, 'db', 'sharks.csv'
AddEndangeredWorker.perform_async(csv_file)
redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
end
. . .
Al invocar el método perform_async
en la clase AddEndangeredWorker
usando el archivo CSV como argumento, este código garantiza que los datos de tiburones y la tarea de carga se transfieran a Redis. Los trabajadores Sidekiq que configuraremos monitorean la cola de trabajo y responderán cuando surjan nuevos trabajo.
Después de invocar perform_async
, nuestro método upload
aplica redireccionamiento a la ruta data
, en la que los usuarios podrán ver los tiburones cargados.
Luego, añadiremos un método destroy
para destruir los datos. Añada el siguiente código que está debajo del método upload
:
. . .
def upload
csv_file = File.join Rails.root, 'db', 'sharks.csv'
AddEndangeredWorker.perform_async(csv_file)
redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
end
def destroy
RemoveEndangeredWorker.perform_async
redirect_to root_path
end
. . .
Al igual que nuestro método upload
, nuestro método destroy
incluye la invocación de perform_async
en una clase RemoveEndangeredWorker
: el otro trabajador Sidekiq que crearemos. Después de invocar este método, redirecciona a los usuarios a la ruta de la aplicación root.
El archivo terminado tendrá este aspecto:
class EndangeredController < ApplicationController
before_action :set_endangered, only: [:index, :data]
def index
if @endangered.length > 0
redirect_to endangered_data_path
else
render 'index'
end
end
def data
end
def upload
csv_file = File.join Rails.root, 'db', 'sharks.csv'
AddEndangeredWorker.perform_async(csv_file)
redirect_to endangered_data_path, notice: 'Endangered sharks have been uploaded!'
end
def destroy
RemoveEndangeredWorker.perform_async
redirect_to root_path
end
private
def set_endangered
@endangered = Endangered.all
end
end
Guarde y cierre el archivo cuando concluya la edición.
Como paso final para afianzar las rutas de nuestra aplicación, modificaremos el código de config/routes.rb
, el archivo en el que residen nuestras instrucciones de ruta.
Abra el archivo ahora:
- nano config/routes.rb
El archivo actualmente tiene este aspecto:
Rails.application.routes.draw do
get 'endangered/index'
get 'home/index'
resources :sharks do
resources :posts
end
root 'home#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Será necesario actualizar el archivo para incluir las rutas que definimos en nuestro controlador: data
, upload
y destroy
. Nuestra ruta data
coincidirá con una solicitud GET para obtener los datos de tiburones, mientras que nuestras rutas upload
y destroy
se asignarán a las solicitudes POST que cargan y destruyen esos datos.
Añada el siguiente código al archivo para definir estas rutas:
Rails.application.routes.draw do
get 'endangered/index'
get 'endangered/data', to: 'endangered#data'
post 'endangered/upload', to: 'endangered#upload'
post 'endangered/destroy', to: 'endangered#destroy'
get 'home/index'
resources :sharks do
resources :posts
end
root 'home#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
Guarde y cierre el archivo cuando concluya la edición.
Con su modelo y controlador Endangered
, ahora puede proceder a definir sus clases de trabajadores Sidekiq.
Invocamos los métodos perform_async
en nuestros trabajadores Sidekiq de nuestro controlador, pero aún debemos crear los trabajadores.
Primero, cree un directorio workers
para los trabajadores:
- mkdir app/workers
Abra un archivo para el trabajador AddEndangeredWorker
:
- nano app/workers/add_endangered_worker.rb
En este archivo, añadiremos el código que nos permitirá trabajar con los datos de nuestro archivo CSV. Primero, añada el código al archivo que creará la clase, incluya la biblioteca CSV Ruby y asegúrese que esta clase funcione como trabajador Sidekiq:
class AddEndangeredWorker
require 'csv'
include Sidekiq::Worker
sidekiq_options retry: false
end
También incluiremos la opción retry:false
para garantizar que Sidekiq no vuelva a intentar realizar la carga ante una falla.
Luego, añada el código para la función perform
:
class AddEndangeredWorker
require 'csv'
include Sidekiq::Worker
sidekiq_options retry: false
def perform(csv_file)
CSV.foreach(csv_file, headers: true) do |shark|
Endangered.create(name: shark[0], iucn: shark[1])
end
end
end
El método perform
recibe los argumentos del método perform_async
definido en el controlador, por lo que es importante que los valores de argumento estén nivelados. Aquí, pasamos en csv_file
, la variable que definimos en el controlador y usamos el método foreach
de la biblioteca CSV para leer los valores del archivo. Configurar headers:true
para este bucle garantiza que se trate a la primera fila del archivo como a una fila de encabezados.
Luego, el bloque luego lee los valores del archivo en las columnas que configuramos para nuestro modelo Endangered
: name
e iucn
. Ejecutar este bucle creará instancias de Endangered
para cada una de las entradas de nuestro archivo CSV.
Una vez que finalice la edición, guarde y cierre el archivo.
Luego, crearemos un trabajador para que elimine estos datos. Abra un archivo para la clase RemoveEndangeredWorker
:
- nano app/workers/remove_endangered_worker.rb
Añada el código para definir la clase y para garantizar que utilice la biblioteca CSV y las funciones como un trabajador Sidekiq:
class RemoveEndangeredWorker
include Sidekiq::Worker
sidekiq_options retry: false
end
Luego, añada un método perform
para gestionar la destrucción de los datos de tiburones en peligro:
class RemoveEndangeredWorker
include Sidekiq::Worker
sidekiq_options retry: false
def perform
Endangered.destroy_all
end
end
El método perform
invoca a destroy_all
en la clase Endangered
, que eliminará todas las instancias de esa clase de la base de datos.
Guarde y cierre el archivo cuando concluya la edición.
Una vez que sus trabajadores estén activos, podrá proceder a crear un diseño para sus vistas de endangered
, así como plantillas para sus vistas de index
y data
, para que los usuarios puedan cargar y ver tiburones en peligro.
Para que los usuarios disfruten de su propia información de tiburones en peligro, tendremos que abordar dos aspectos: el diseño de las vistas definidas en nuestro controlador endangered
y las plantillas de vista de las vistas de index
y data
.
Actualmente, nuestra aplicación utiliza un diseño integral, ubicado en app/views/layouts/application.html.erb
, uno parcial de navegación y uno para las vistas de sharks
. El diseño de la aplicación busca un bloque de contenido, que nos permite cargar diferentes diseños basados en la parte de la aplicación con la que nuestro usuario interactúa: para la página home
index
, este verá un diseño y para cualquier vista que se relacione con tiburones individuales verá otro.
Podemos reutilizar el diseño de sharks
para nuestras vistas de endangered
, ya que este formato también funcionará para presentar datos de tiburones de forma masiva.
Copie el archivo de diseño de sharks
para crear un diseño de endangered
:
- cp app/views/layouts/sharks.html.erb app/views/layouts/endangered.html.erb
Luego, trabajaremos en crear las plantillas de vista para nuestras vistas de index
y data
.
Abra la plantilla index
primero:
- nano app/views/endangered/index.html.erb
Elimine el código estándar y añada en su lugar el siguiente código, que proporcionará a los usuarios información general sobre las categorías en peligro y les dará la opción de cargar información sobre tiburones en peligro:
<p id="notice"><%= notice %></p>
<h1>Endangered Sharks</h1>
<p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
<br>
<%= form_tag endangered_upload_path do %>
<%= submit_tag "Import Endangered Sharks" %>
<% end %>
<br>
<%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
Un form_tag
hace posible la carga de datos al apuntar una acción posterior a endangered_upload_path
, la ruta que definimos para nuestras cargas. El botón de envío, creado con submit_tag
, solicita a los usuarios a “Import Endangered Sharks”
.
Además de este código, incluimos información general sobre códigos de ICUN, para que los usuarios puedan interpretar los datos que verán.
Guarde y cierre el archivo cuando concluya la edición.
Luego, abra un archivo para la vista de data
:
- nano app/views/endangered/data.html.erb
Añada el siguiente código, que agregará una tabla con los datos de tiburones en peligro:
<p id="notice"><%= notice %></p>
<h1>Endangered Sharks</h1>
<p>International Union for Conservation of Nature (ICUN) statuses: <b>vu:</b> Vulnerable, <b>en:</b> Endangered, <b>cr:</b> Critically Endangered </p>
<div class="table-responsive">
<table class="table table-striped table-dark">
<thead>
<tr>
<th>Name</th>
<th>IUCN Status</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @endangered.each do |shark| %>
<tr>
<td><%= shark.name %></td>
<td><%= shark.iucn %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<br>
<%= form_tag endangered_destroy_path do %>
<%= submit_tag "Delete Endangered Sharks" %>
<% end %>
<br>
<%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
Este código incluye de nuevo los códigos de estado de la ICUN y una tabla de Bootstrap para los datos emitidos. Al recorrer nuestra variable @endangered
, mostramos el nombre y la situación de cada tiburón según la ICUN en la tabla.
Debajo de la tabla, tenemos otro conjunto de form_tags
y submit_tags
, que se publican a la ruta destroy
ofreciendo a los usuarios la opción de "Delete Endangered Sharks”
.
Guarde y cierre el archivo cuando concluya la edición.
La última modificación que aplicaremos a nuestras vistas se hará en la vista de index
asociada con nuestro controlador home
. Como podrá recordar, esta vista se configura como root de la aplicación en config/routes.rb
.
Abra este archivo para editarlo:
- nano app/views/home/index.html.erb
Encuentre la columna en la fila Sharks are ancient
:
. . .
<div class="col-lg-6">
<h3>Sharks are ancient</h3>
<p>There is evidence to suggest that sharks lived up to 400 million years ago.
</p>
</div>
</div>
</div>
Añada el siguiente código al archivo:
. . .
<div class="col-lg-6">
<h3>Sharks are ancient and SOME are in danger</h3>
<p>There is evidence to suggest that sharks lived up to 400 million years ago. Without our help, some could disappear soon.</p>
<p><%= button_to 'Which Sharks Are in Danger?', endangered_index_path, :method => :get, :class => "btn btn-primary btn-sm"%>
</p>
</div>
</div>
</div>
Incluimos un llamado a la acción para que los usuarios aprendan más sobre tiburones en peligro compartiendo, primero, un mensaje fuerte y, luego, añadiendo un asistente button_to
que envía una solicitud GET a nuestra ruta index
endangered
y permite que los usuarios accedan a esa parte de la aplicación. Desde allí, podrán cargar y ver información de tiburones en peligro.
Guarde y cierre el archivo cuando concluya la edición.
Una vez que esté listo su código, estará preparado para iniciar la aplicación y cargar algunos tiburones.
Antes de iniciar la aplicación, necesitaremos ejecutar migraciones en nuestra base de datos e iniciar Sidekiq para habilitar nuestros trabajadores. Redis ya se debería estar ejecutando en el servidor, pero podemos verificarlo para estar seguros. Una vez que hagamos todo esto, estaremos listos para probar la aplicación.
Primero, verifique que Redis esté en ejecución:
- systemctl status redis
Debería ver un resultado como el siguiente:
Output● redis-server.service - Advanced key-value store
Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2019-11-12 20:37:13 UTC; 1 weeks 0 days ago
Luego, ejecute las migraciones de su base de datos:
- rails db:migrate
Ahora puede iniciar Sidekiq en el contexto de su paquete de proyectos actual usando el comando bundle exec sidekiq
:
- bundle exec sidekiq
Verá un resultado como este, que indica que Sidekiq está listo para procesar tareas:
Output
m,
`$b
.ss, $$: .,d$
`$$P,d$P' .,md$P"'
,$$$$$b/md$$$P^'
.d$$$$$$/$$$P'
$$^' `"/$$$' ____ _ _ _ _
$: ,$$: / ___|(_) __| | ___| | _(_) __ _
`b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
$$: ___) | | (_| | __/ <| | (_| |
$$ |____/|_|\__,_|\___|_|\_\_|\__, |
.d$$ |_|
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Running in ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: See LICENSE and the LGPL-3.0 for licensing details.
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
2019-11-19T21:43:00.540Z pid=17621 tid=gpiqiesdl INFO: Booting Sidekiq 6.0.3 with redis options {:id=>"Sidekiq-server-PID-17621", :url=>nil}
2019-11-19T21:43:00.543Z pid=17621 tid=gpiqiesdl INFO: Starting processing, hit Ctrl-C to stop
Abra una segunda ventana de terminal, diríjase al directorio rails-sidekiq
e inicie el servidor de aplicaciones.
Si ejecuta la aplicación a nivel local, utilice el siguiente comando:
- bundle exec rails s
Si trabaja con un servidor de desarrollo, ejecute lo siguiente:
- bundle exec rails s --binding=your_server_ip
Diríjase a localhost:3000
o a http://your_server_ip:3000
en el navegador. Visualizará la siguiente página de destino:
Haga clic en el botón** ¿Which Sharks Are In Danger?** . Debido a que no cargó ningún tiburón en peligro, esto lo dirigirá a la vista de endangered
index
:
Haga clic en Import Endangered Sharks para importar los tiburones. Verá un mensaje de estado que le indicará que los tiburones se importaron:
También verá el comienzo de la importación. Actualice su página para ver la tabla entera:
Gracias a Sidekiq, realizamos la carga del gran lote de tiburones en peligro sin cerrar el navegador ni interferir con otras funciones de la aplicación.
Haga clic en el botón Home en la parte inferior de la página, que lo llevará de vuelta a la página principal de la aplicación:
Aquí, haga clic en Which Sharks Are in Danger? nuevamente. Esto lo llevará directamente a la vista de data
, porque ya había cargado los tiburones.
Para probar la función de eliminación, haga clic en el botón Delete Endangered Sharks que se encuentra debajo de la tabla. Una vez más, debería ser redireccionado a la página de inicio de la aplicación. Si have clic en Which Sharks Are in Danger? una última vez, lo llevará de regreso a la vista de index
, en la que tendrá la opción de cargar tiburones de nuevo:
Su aplicación ahora se está ejecutando con los trabajadores de Sidekiq, que están listos para procesar trabajos y garantizar que los usuarios tengan una buena experiencia trabajando con su aplicación.
Ahora dispone de una aplicación Rails en funcionamiento con Sidekiq habilitado, que le permitirá descargar operaciones complejas a una cola de trabajo administrada por Sidekiq y respaldada por Redis. Esto le permitirá mejorar la velocidad y la funcionalidad de su sitio a medida que realice el desarrollo.
Si desea obtener más información sobre Sidekiq, se recomienda comenzar consultando los docs.
Para obtener más información sobre Redis, consulte nuestra biblioteca de recursos de Redis. También puede obtener más información sobre cómo ejecutar un clúster de Redis administrado en DigitalOcean consultando la documentación del producto.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Tutorial muy bien explicado