Design patterns in Go: Bridge

01 Jan 2021

Have you ever had the situation when you need to use different database drivers for your repository. Well, in order to avoid using separate repositories for different database drivers, I would suggest to use Bridge design pattern to make things more elegant.


Let’s say that you have started to create an application, and you designed it to use MySQL database. It’s actually a good choice to start with, essentially it is always good to start with a relational database. But you came across an issue: your start receiving errors alerts in the logs like database connection pool exceeded. The problem is that a MySQL database is not as easy to scale horizontally as a NoSQL database, like MongoDB.

So you decide to migrate to a MongoDB database. You already prepared the infrastracture, but the problem now is that you have to migrate all data, but still keep it consistent, at least before the moment when you will prepare a migration script for that.

In order to make transition easier, you need to be able to connect with both databases. Hence, you will need to use a bridge, to migrate all the data.

Let’s take a look on how an implementation would look like.


First of all, we will need an interface with common driver operation (CRUD). For this current implementation example we will use on Create and Read actions, for the sake of simplicity:

type Database interface {
	// For now only two methods will be defined, just illustrate the idea
	Create(input map[string]interface{}, result interface{}) error
	Find(id string, result interface{}) error

Let’s see how the MySQL driver will look like, in the context of this new interface:

type MySqlConnection struct {
	url string

func NewMySqlConnection(url string) *MySqlConnection {
	return &MySqlConnection{url}

type MysqlDatabase struct {
	conn *MySqlConnection

func NewMysqlDatabase(conn *MySqlConnection) *MysqlDatabase {
	return &MysqlDatabase{conn}

func (ms MysqlDatabase) Create(input map[string]interface{}, result interface{}) error {
	fmt.Printf("Creating %s using MySQL\n", reflect.TypeOf(result))
	return nil

func (ms MysqlDatabase) Find(id string, result interface{}) error {
	fmt.Printf("Fetching %s, with id %s, using MySQL\n", reflect.TypeOf(result), id)
	return nil

I have also added, some rudimentary MySQL connection object, that should be injected into driver, but is can have a different structure for a production system.

Now let’s add a repository and model struct, in order to operate somehow; let’s say it will be User model:

type User struct {
	FirstName string
	LastName  string
	Email     string

type UserRepository struct {
	db Database

func NewUserRepository(db Database) *UserRepository {
	return &UserRepository{db}

func (repo UserRepository) Create(user User) error {
	data := map[string]interface{}{
		"first_name": user.FirstName,
		"last_name":  user.LastName,
		"email":      user.Email,

	err := repo.db.Create(data, &user)

	return err

func (repo UserRepository) Find(id string) (User, error) {
	var result User
	var err error

	err = repo.db.Find(id, &result)

	return result, err

As we can see here, it does a preprocessing for User data that will be used for drivers in order to create new records. And also the result will be written to the reference of the User that is passed to drivers.

In order to add the MongoDB driver, we will simply implement the Database interface for MondoDB driver:

type MongodbConnection struct {
	url string

func NewMongodbConnection(url string) *MongodbConnection {
	return &MongodbConnection{url}

type MongoDatabase struct {
	conn *MongodbConnection

func NewMongoDatabase(conn *MongodbConnection) *MongoDatabase {
	return &MongoDatabase{}

func (md MongoDatabase) Create(input map[string]interface{}, result interface{}) error {
	fmt.Printf("Creating %s using MongoDB\n", reflect.TypeOf(result))
	return nil

func (md MongoDatabase) Find(id string, result interface{}) error {
	fmt.Printf("Fetching %s, with id %s, using MongoDB\n", reflect.TypeOf(result), id)
	return nil

And now let’s test it:

	var db Database

  if os.Getenv("DB_DRIVER") == "MY_SQL" {
		db = NewMysqlDatabase(NewMySqlConnection(os.Getenv("DB_URL")))
  } else if os.Getenv("DB_DRIVER") == "MONGO_DB" {
    db = NewMongoDatabase(NewMongodbConnection(os.Getenv("DB_URL")))
  } else {
    panic("Unknown DB driver had been given")

	repo := NewUserRepository(db)

		FirstName: "John",
		LastName:  "Smith",
		Email:     "",


which, if the DB_DRIVER will have MONGO_DB value, it will produce following:

Creating *main.User using MongoDB
Fetching *main.User, with id 1, using MongoDB


The bridge as we saw, separates the implementation from the interface, which can be used in the client object, only by injecting with specific implementation. This could be very handy, in other scenarios, when you need to have several implementations of the same abstraction, and have a client that will just consume the injected object only by using it’s abstraction, which as a result might help when implementing an Abstract Factory