Estrategia de migración de la API REST a GraphQL - trabajo preparatorio ( Ruby on Rails )

Sommaire
Resumen
Este artículo continúa al anterior, sobre el tema de la migración de una API REST a GraphQL.
En el artículo anterior, repasamos el plan de acción implementado para realizar esta migración.
En esta entrada veremos la fase de preparación con más detalle. Primero, un pequeño recordatorio :
Trabajo preparatorio
- Listar los controladores & acciones que estarán presentes en la versión GraphQL de la API.
- Extraer las acciones de los controladores y transformarlas en objetos (patrón de diseño command).
- Opcional (¡pero muy recomendable !) : actualizar su cobertura de pruebas.
Listar los controladores & acciones que estarán presentes en la versión GraphQL de la API
Para esta fase, solo usted puede enumerar qué acciones son utilizadas o no por los consumidores de su API.
Para la metodología, es necesario un repaso exhaustivo de todos los controladores.
También puede aprovechar para tomar notas sobre los trabajos de refactorización previstos, las pruebas unitarias que escribir... Esto le permitirá empezar a estimar con más precisión el tiempo a dedicar a las etapas futuras.
Extraer las acciones de los controladores y transformarlas en objetos
Le aconsejo en primer lugar que se familiarice con el patrón de diseño Command (o Service Object )
El objetivo de esta etapa es extraer cada acción de sus controladores en objetos. Al proceder de esta manera, se revelan varias ventajas :
- Podrá identificar las dependencias de su código ;
- Podrá probar la lógica del código más fácilmente, independientemente del contexto del controlador ;
- Podrá reutilizar su código en varios lugares ;
En el siguiente ejemplo, extraeremos la lógica de la acción create en el controlador posts_controller.rb.
Antes del refactor :
## app/api/v1/posts_controller.rb
module Api
module V1
class PostsController < ::Api::ApplicationController
def create
## Logique métier
## ...
## ...
if post_repository.save(post)
redirect_to post_path(post)
else
render "new"
end
end
def post_repository
@post_repository ||= ::Repositories::Post.new
end
end
end
end
Después del refactor :
## lib/command/post/create.rb
module Command
module Post
class Create
def self.exec({ attrs, callbacks, repositories })
new(callbacks, repositories).exec(attrs)
end
def exec(attrs)
## Logique métier
## ...
## ...
if @repositories[:post].save(post)
@callbacks[:success].call(post: post)
else
@callbacks[:failure].call(post: post)
end
end
private
def initialize(callbacks, repositories)
@callbacks = callbacks
@repositories = repositories
end
end
end
end
## app/api/v1/posts_controller.rb
module Api
module V1
class PostsController < ::Api::ApplicationController
def create
::Command::Post::Create.exec(
{
attrs: params,
callbacks: {
success: ->(args) {
@post = args[:post]
redirect_to post_path(@post)
},
failure: ->(args) {
@post = args[:post]
render "new"
},
},
repositories: {
post: ::Repositories::Post.new,
}
}
)
end
end
end
end
Aquí hay algunos enlaces que le pueden ser útiles para profundizar :
- https://www.codeproject.com/Articles/12263/The-Command-Pattern-and-MVC-Architecture
- https://blog.slava.dev/thin-controllers-fat-models-one-of-the-base-principles-of-mvc-in-ruby-on-rails-but-time-goes-by-a5a044124207
- https://www.honeybadger.io/blog/refactor-ruby-rails-service-object/
- https://www.cloudbees.com/blog/refactoring-legacy-rails-controllers
Actualizar la cobertura de pruebas
Antes de cualquier modificación o refactorización, le aconsejo escribir y actualizar sus pruebas. ¡Esto le permitirá "romperlo todo" con tranquilidad!
No voy a detenerme en la implementación de las pruebas unitarias, pero voy a presentar un ejemplo adaptado a los cambios.
Si utiliza rspec, el siguiente ejemplo le puede servir de plantilla para probar sus Objetos command.
require 'rails_helper'
RSpec.describe ::Command::Post::Create do
let (:callbacks) {
{
success: -> (*args) { :success },
failure: -> (*args) { :failure }
}
}
before do
post_repository = double('::Repositories::Post')
allow(post_repository).to receive(:save).with(an_instance_of(::Post)).and_return(true)
@repositories = {
post: post_repository,
}
end
let (:command_post_create) { ::Command::Post::Create }
describe "create" do
context "the record is created" do
let (:attrs) {
{
title: "Test"
}
}
it "calls success callback" do
result = command_post_create.exec(
attrs: attrs,
callbacks: callbacks,
repositories: @repositories
)
expect(result).to eq :success
end
end
end
end
Aquí hay algunos enlaces que le pueden ser útiles para profundizar :
Conclusión
Después de este trabajo, debería haber separado la lógica de negocio de las acciones de los controladores en objetos específicos. Estos, con sus dependencias bien identificadas, podrán ser probados correctamente.
Ahora puede usar estos objetos en cualquier lugar de su proyecto, lo que evitará duplicar el código entre sus dos versiones de API.
¡Por lo tanto, desde ahora dispone de un buen valor añadido en términos de claridad y estructuración!
Comentarios
Cargando...