DEV Community

Cover image for Build flexible Rails application w/ pattern Bridge (Scale your Rails App #1)
Pimp My Ruby
Pimp My Ruby

Posted on

Build flexible Rails application w/ pattern Bridge (Scale your Rails App #1)

Problem Statement

You're developing your Ruby on Rails app, and you're using SMS Factor as your SMS sending platform. The code is running, and the SMS sending feature is operational. You're calling your SMS sending class throughout your code. Suddenly, a new opportunity arises. You need to integrate a new SMS provider, Twilio. But how can you add this functionality without having to modify your existing code calls?

This is a perfect use case to explore together the Bridge pattern. In this first edition of Scale your Rails App, we will explore how to use the Bridge pattern to overcome this challenge!

What is the Bridge?

The Bridge pattern is a structural design pattern that allows you to separate an abstraction from its implementation so that both can evolve independently. In other words, it allows you to define an interface, called the abstraction, which acts as a bridge between your code and specific implementations of external services.

Practical Case, Switching from SMSFactor to Twilio:

Let's imagine that you have a class called SmsSender that you're using throughout your code to send SMS messages.

# app/lib/sms_sender
class SmsSender
  def send(phone_number:, body:)
    # logic to send SMS with SMS Factor
  end
end
Enter fullscreen mode Exit fullscreen mode

You're calling SmsSender all over your application like this:

SmsSender.new.send(phone_number: "0612345678", body: "Hello World!")
Enter fullscreen mode Exit fullscreen mode

And you don't want that to change!

This is where the Bridge pattern becomes interesting. You can separate the SMS sending logic from SmsSender to make it Provider Agnostic.

So, you'll create an Adapter for each provider:

# app/lib/sms_provider/sms_factor_adapter.rb
class Sms::Provider::SmsFactorAdapter
  # This logic was in SmsSender previously
  def send(phone_number, body)
    # logic to send SMS with SMS Factor
  end
end

# app/lib/sms_provider/twilio_adapter.rb
class Sms::Provider::TwilioAdapter
  def send(phone_number, body)
    # logic to send SMS with Twilio
  end
end
Enter fullscreen mode Exit fullscreen mode

And now, all you have to do is change your initial SmsSender class:

# app/lib/sms_sender.rb
class SmsSender
  def initialize(provider: SmsProvider::TwilioAdapter.new)
    @provider = provider
  end

  def send(phone_number, message)
    @provider.send(phone_number, message)
  end
end
Enter fullscreen mode Exit fullscreen mode

And voila! The magic happens. All the places where you call SmsSender are unaware of which provider you're using to send your SMS. You can still specify which Adapter you want to use by providing it as an argument during the initialization of SmsSender:

SmsSender.new(provider: SmsProvider::SmsFactorAdapter.new)
                 .send(phone_number: "0612345678", body: "Hello World!")
Enter fullscreen mode Exit fullscreen mode

What's Next?

The Bridge pattern is highly valuable because it significantly enhances the extensibility of your application.

→ If you want to add a new provider:

You only need to add a new Adapter. You can set it as the default adapter in the SmsSender class.

→ If you want to remove a provider:

You can simply delete the Adapter, without even touching SmsSender.

And all of this without modifying your calls to SmsSender in the code!

TL;DR

Using the Bridge pattern in this situation offers several advantages:

  • Flexibility: You can easily add new providers without modifying calls in your business logic.
  • Separation of Concerns: The abstraction SmsSender clearly separates the SMS sending logic from the specific provider's implementation.
  • Maintainability: Changes in one service provider do not affect the code of other providers or calls to SmsSender.

Conclusion

The Bridge pattern is a powerful tool for managing external service providers in your Ruby on Rails application. It allows you to maintain a clean and organized codebase while facilitating the integration of new services.

I hope this article has inspired you, and you'll apply it in your Ruby on Rails projects for even more efficient and flexible results! 🚀

Learn More

Top comments (2)

Collapse
 
brdnicolas profile image
Nicolas B.

Is it the most common use case ? Do you have another exemples ?

Great article :)

Collapse
 
pimp_my_ruby profile image
Pimp My Ruby

Hi Nicolas, thanks :)

In the use case I described, I used the Bridge pattern to split the logic between all the SMS provider efficiently.

In other projects, I barely do the same thing. The idea is to split a god class into several small classes that are revelant for a variation of the behavior of the given god class.