REST API to GraphQL migration strategy - preparatory work ( Ruby on Rails )

REST API to GraphQL migration strategy - preparatory work ( Ruby on Rails )
Summary
  1. Preparatory work
  2. List the controllers & actions that will be present on the GraphQL version of the API
  3. Extract controller actions and transform them into objects
  4. Update your test coverage
  5. Conclusion

This article follows the previous one, on the theme of migrating a REST API to GraphQL.

In the previous article, we reviewed the action plan put in place to carry out this migration.

In this post we will look at the preparation phase in more detail. First, a short reminder:

Preparatory work

  • List the controllers & actions that will be present on the GraphQL version of the API.
  • Extract controller actions and transform them into objects (design pattern command).
  • Optional (but highly recommended !) : update your test coverage.

List the controllers & actions that will be present on the GraphQL version of the API

For this phase, only you can list which actions are used or not by your API's consumers.

For the methodology, an exhaustive pass over all controllers is necessary.

You can also take the opportunity to take notes on refactor work to be planned, unit tests to write... This will allow you to start estimating more precisely the time required for future steps.

Extract controller actions and transform them into objects

I recommend first familiarizing yourself with the Command (or Service Object ) design pattern

The goal of this step is to extract each action from your controllers into an object. By proceeding in this way, several advantages emerge:

  • You will be able to identify the dependencies of your code ;
  • You will be able to test the code logic more easily, independently of the controller context ;
  • You will be able to reuse your code in multiple places ;

In the following example, we will extract the logic of the create action in the posts_controller.rb controller.

Before 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

After 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

Here are some links that may be useful to go further:

Update your test coverage

Before any modification or refactor, I recommend writing & updating your tests. This will allow you to "break everything" with peace of mind!

I will not dwell on setting up unit tests, but I will present an example adapted to the changes.

If you use rspec, the example below can serve as a template to test your Command objects.

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

Here are some links that may be useful to go further:

Conclusion

After this work, you should have separated business logic from controller actions into specific objects. These have their dependencies well identified and can be properly tested.

You can now use these objects anywhere in your project, which will avoid duplicating code between your two API versions.

You therefore now have a good added value in terms of clarity and structure!

Tags

  • graphql

  • rest

  • ruby

  • api

This article was posted on

Comments

Loading...

REST API to GraphQL migration strategy - preparatory work ( Ruby on Rails ) | DEMILY Clément