3rd Party Integration using Rails Engines
What are Rails Engines ?
Engines can be considered miniature applications that provide functionality to their host applications
Popular Rails Engines
- Devise , an engine that provides authentication for its parent applications, or
- Forem , an engine that provides forum functionality.
- Spree which provides an e-commerce platform, and
- RefineryCMS, a CMS engine.
What is 3rd Party ?
Anything which we don’t own and are using is 3rd Party
Typical Examples are Payment Gateways, Mailing / Sms services in e-commerce app.
Some classify as any service as 3rd Party
Now familar with some of the typical examples of 3rd Party and Rails Engines,
- lets dive deep into the co-relation between 3rd Party Integration & Rails Engines and
- how can we leverage Rails Engines to fully isolate 3rd Party Integration from Core of App
- Why it could be a bad idea to directly jump to Rails Engines whenever 3rd Party Integrations come along
We will be starting with the incremental process of extraction
- Starting with 3rd Party Integration code in our Rails App
- Extracting the stable parts not interacting with request / response cycle into a Ruby Gem Wiki
- Extracting the parts interacting with request / response cycle into a Rails Engine
Incremental Process of Extraction is an important bit as systems running and used daily can never be changed in a day and requires extra effort to understand the feedback / connection with other parts in system.
Extract a part and hook it back with defined interface, let it stabilize and then start with next bit.
From the looks of it, you must have guessed it by now that the described activity is a REFACTORING EXERCISE.
Brief Introduction about the extraction we would do
There is a payment gateway by the name of Gtpay in Nigeria.
For better or worse, there is no Ready-made Ruby Gem available for its integration like Stripe / Paypal
We started with integrating it in our app and now when its running smoothly, we decided to decouple it from our app.
Couple of Payment Integrations are already integrated and thrown away.
In an attempt of not loosing what we created and also, keeping it very easy to include / exclude when needed, we started the journey from RAILS APP to RUBY GEM to RAILS ENGINE
Process Walkthrough of what we would extract step - by - step
- A link is needed to process the payments through Gtpay
- A spinner should come up to indicate connection attempt with Gtpay
- Behind the scenes (spinner), form need to be submitted to redirect to Gtpay with information like transaction amount, currency, merchant details etc
- When on Payment Page i.e. Gtpay
- there should be an end-point acting as callback to handle the success / failure responses from Gtpay
Code Structure
Here is the code on github to follow along :)
Monlith Rails App
app/controllers/
├── application_controller.rb
├── gtpay
│ └── transactions_controller.rb
└── home_controller.rb
app/gtpay/
├── gtpay
│ ├── request.rb
│ ├── response.rb
│ └── transaction.rb
└── gtpay.rb
app/models/
├── gtpay
│ └── gt_pay_transaction.rb
├── order.rb
└── user.rb
app/views/
├── gtpay
│ └── transactions
│ ├── auto_submit_gtpay_forms.html.erb
│ └── create.html.erb
├── home
│ ├── index.html.erb
│ └── new.html.erb
└── layouts
└── application.html.erb
Everything scoped under gtpay is interesting and is a contendor for extraction
Monlith Rails App Reduced and Supported by Gem
Gtpay GEM - Business Logic Extracted
gtpay
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── gtpay.gemspec
├── lib
│ ├── generators
│ │ ├── gtpay
│ │ │ ├── controllers_generator.rb
│ │ │ ├── install_generator.rb
│ │ │ └── model_generator.rb
│ │ └── templates
│ │ ├── controllers
│ │ │ └── transactions_controller.rb
│ │ ├── initializers
│ │ │ └── gtpay.rb
│ │ ├── migrations
│ │ │ └── 001_create_gtpay_transaction.rb
│ │ ├── models
│ │ │ └── gt_pay_transaction.rb
│ │ ├── spec
│ │ │ └── gtpay_transactions_controller_spec.rb
│ │ └── views
│ │ └── transactions
│ │ ├── auto_submit_gtpay_forms.html.erb
│ │ └── create.html.erb
│ ├── gtpay
│ │ ├── request.rb
│ │ ├── response.rb
│ │ └── transaction.rb
│ └── gtpay.rb
└── spec
├── active_record
│ ├── setup_ar.rb
│ └── test.db
├── gtpay
│ ├── gtpay_spec.rb
│ ├── request_spec.rb
│ ├── response_spec.rb
│ └── transaction_spec.rb
└── spec_helper.rb
Rails App - Gem got included back in Gemfile
# Gemfile
gem 'gtpay', '0.0.1'
# gtpay_engine.gemspec
s.add_dependency "gtpay", "~> 0"
app/controllers/
├── application_controller.rb
├── gtpay
│ └── transactions_controller.rb
└── home_controller.rb
app/gtpay/ [error opening dir]
app/models/
├── gtpay
│ └── gt_pay_transaction.rb
├── order.rb
└── user.rb
app/views/
├── gtpay
│ └── transactions
│ ├── auto_submit_gtpay_forms.html.erb
│ └── create.html.erb
├── home
│ ├── index.html.erb
│ └── new.html.erb
└── layouts
└── application.html.erb
Rails Engine
# Gemfile
gem 'gtpay', '0.0.1'
gtpay_engine_gem/
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.rdoc
├── Rakefile
├── app
│ ├── assets
│ │ ├── images
│ │ │ └── gtpay_engine
│ │ ├── javascripts
│ │ │ └── gtpay_engine
│ │ │ └── application.js
│ │ └── stylesheets
│ │ └── gtpay_engine
│ │ └── application.css
│ ├── controllers
│ │ └── gtpay_engine
│ │ ├── application_controller.rb
│ │ └── transactions_controller.rb
│ ├── helpers
│ │ └── gtpay_engine
│ │ └── application_helper.rb
│ ├── mailers
│ ├── models
│ │ └── gtpay_engine
│ │ └── gt_pay_transaction.rb
│ └── views
│ ├── gtpay_engine
│ │ ├── application
│ │ │ └── home.html.erb
│ │ └── transactions
│ │ ├── auto_submit_gtpay_forms.html.erb
│ │ └── create.html.erb
│ └── layouts
│ └── gtpay_engine
│ └── application.html.erb
├── config
│ ├── initializers
│ │ └── gtpay.rb
│ └── routes.rb
├── db
│ └── migrate
│ └── 20141114193621_create_gtpay_transaction.rb
├── gtpay_engine-0.0.1.gem
├── gtpay_engine.gemspec
├── lib
│ ├── gtpay_engine
│ │ ├── engine.rb
│ │ └── version.rb
│ ├── gtpay_engine.rb
│ └── tasks
│ └── gtpay_engine_tasks.rake
├── script
│ └── rails
├── spec
│ └── controllers
│ └── gtpay_transactions_controller_spec.rb
└── test
├── dummy <Rails App to test Engine>
Rails App - Engine got included back in Gemfile
# Gemfile
gem 'gtpay_engine', '0.0.1'
# config/routes.rb
mount GtpayEngine::Engine => "/gtpay_engine"
app/controllers/
└── application_controller.rb
app/gtpay/ [error opening dir] <No gtpay folder at all>
app/models/
app/views/
└── layouts
└── application.html.erb
Incremental directory structures are self-explanatory of the movement and separation / reduction in the host Rails App.
Running / Using the 3 implementations are exactly same as no external behavior changed but just code is moved around for better separation and maintenance, easy to plug in and out of Rails App.
Benefits
The process of including / excluding a feature as important as integrating a payment gateway has reduced from many lines of code to just 2 lines inclusion and removal
# Gemfile
gem 'gtpay_engine', '0.0.1'
# config/routes.rb
mount GtpayEngine::Engine => "/gtpay_engine"