Strategie de migration API REST vers GraphQL - travail préparatoire ( Ruby on Rails )

Strategie de migration API REST vers GraphQL - travail préparatoire ( Ruby on Rails )
<blockquote class="post-summary"> <span class="summary">Sommaire</span> <ol> <li> <a href="#part-1">Travail préparatoire</a> </li> <li> <a href="#part-2">Répertorier les controllers & actions qui seront présent sur la version GraphQL de l’API</a> </li> <li> <a href="#part-3">Extraire les actions des controllers et les transformer en objet</a> </li> <li> <a href="#part-4">Mettre à jour sa couverture de test</a> </li> <li> <a href="#part-5">Conclusion</a> </li> </ol> </blockquote>

Cet article fait suite au précédent, sur le thème de la migration d’une API REST vers GraphQL.

Dans l’article précédent, nous avons survolé le plan d’action mis en place pour faire cette migration.

Nous allons voir dans ce billet la phase de préparation plus en détails. Tout d’abord, un petit rappel :

Travail préparatoire

  • Répertorier les controllers & actions qui seront présent sur la version GraphQL de l’API.
  • Extraire les actions des controllers et les transformer en objet (design pattern command).
  • Optionnel (mais vivement conseillé !) : mettre à jour sa couverture de test.

Répertorier les controllers & actions qui seront présent sur la version GraphQL de l’API

Pour cette phase, vous seul êtes à même de répertorier quelles actions sont utilisées ou non par les consommateurs de votre API.

Pour la méthodologie, une passe exhaustive sur l’ensemble des controllers est nécessaire.

Vous pouvez également en profiter pour prendre des notes sur les travaux de refactors à prévoir, les tests unitaires à écrire... Ceci vous permettra de commencer à chiffrer plus précisément le temps à passer pour les futures étapes.

Extraire les actions des controllers et les transformer en objet

Je vous conseille tout d’abord de prendre connaissance du design pattern Command (ou Service Object )

Le but de cette étape est d’extraire chaque action de vos controllers en objet. En procédant de cette manière, plusieurs avantages se dévoilent :

  • Vous pourrez identifier les dépendances de votre code ;
  • Vous pourrez tester la logique du code plus facilement, indépendamment du contexte du controller ;
  • Vous pourrez ré-utiliser votre code à plusieurs endroits ;

Sur l’exemple suivant, on va extraire la logique de l’action create dans le controller posts_controller.rb.

Avant 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

Après 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

Voici quelques liens qui pourront vous êtes utile pour aller plus loin :

Mettre à jour sa couverture de test

Avant toute modification ou refactor, je vous conseille d’écrire & de mettre à jour vos tests. Ceci vous permettra de “tout casser” sereinement !

Je ne vais pas m’attarder sur la mise en place des tests unitaires, mais je vais vous présenter un exemple adapté aux changements.

Si vous utilisez rspec, l’exemple ci-dessous pourra vous servir de template afin de tester de vos Objets 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

Voici quelques liens qui pourront vous êtes utile pour aller plus loin :

Conclusion

Après ce travail, vous devriez avoir séparé la logique métier des actions des controllers dans des objets spécifique. Ceux-ci, ont leurs dépendances bien identifiées, elles vont pouvoir être testés correctement.

Vous pouvez désormais utiliser ces objets n’importe où dans votre projet, ce qui évitera de dupliquer le code entre vos deux versions d’API.

Vous avez donc dès à présent une bonne valeur ajoutée en termes de clarté et de structuration !

Tags

  • graphql
  • rest
  • ruby
  • api

Cet article à été posté le