1. The problem

The fact is, the strategy pattern is one of the easiest patterns in the behavioral pattern group, but it always shows how practical it is since there are unaccountable projects that implement it.

I’ve already had one post about this pattern and how to implement it in PHP. Today I’m gonna take another example, which is the real issue that I faced while building a small project in Golang.

Ok, imagine your project has to support multi databases driver? What if it needs to be able to read/write data on two or more separate database systems? Today we got 2 types of DB, tomorrow is 3, we can’t know. So let create a strategy implementation that determines which database system will be used in the flow.

2. The solution

Firstly, Take a look at how we gonna organize the flow

Draw.io

There are 3 Golang structs for 3 types of database systems, those structs gonna follow the IDbFactory.

Then, very simple, we just have to create an external function for the driver package, which return IDbFactory{} type

3. The rules

The rule of the strategy pattern is quite simple, in the case above, you have to make sure that

  • Every databaseDriver has to follow the same interface and have “make sense” methods. In this example, they all have ConnectDatabase() method
  • The return type of those method have to follow the inteface, in this example, we have no return type.

4. The Implementation of strategy pattern

Ok, so let build the one that controls the flow, as we discussed, it will have a method that returns the strategy object.

// driverFactory.go
package driver

import "links-crawler/config"

type IDbFactory interface {
	ConnectDatabase()
}

func GetDbDriverFactory(dbType string) IDbFactory {

	switch dbType {
	case "mongo":
		return &MongoDB{}
	case "mysql":
		return &Mysql{}
	case "postgres":
		return &Postgres{}
	}

	return nil
}

Next, please be aware that those structs like MongoDb{}, Mysql{} and Postgres{} must have ConnectDatabase() method
Let see file mysqlDriver.go and file mongoDriver.go

// mysqlDriver.go
package driver

import (
	"database/sql"
	"os"

	_ "github.com/go-sql-driver/mysql"
)

type Mysql struct {
	Client sql.DB
}

var MysqlDB = &Mysql{}

func (mysql *Mysql) ConnectDatabase() {
	db, dbErr := sql.Open("mysql", "Mysql_connection_string") // get the connection string in.env file
	if dbErr != nil {
		panic(dbErr.Error())
	}
	MysqlDB.Client = *db
	// defer the close till after the main function has finished
	// executing
	defer db.Close()
}
// mongoDriver.go

package driver

import (
	"context"
	"fmt"
	"os"
	"time"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)

type MongoDB struct {
	Client *mongo.Client
}

var Mongo = &MongoDB{}

func (mongodb *MongoDB) ConnectDatabase() {

	// connStr := getConnectionString(user, password)
	client, err := mongo.NewClient("Mongo_connection_string")

	if err != nil {
		panic(err)
	}

	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	err = client.Connect(ctx)
	if err != nil {
		panic(err)
	}

	err = client.Ping(ctx, readpref.Primary())
	if err != nil {
		panic(err)
	}

	fmt.Println("connection ok")
	Mongo.Client = client
}

All of the preparations are done, let wrap them all

> driver
	> driverFactory.go
	> mongoDriver.go
	> mysqlDriver.go
main.go

Just load the database at the start of the program:

// main.go

package main

import (
	"sample-module/driver"
)

func main() {

	loadDatabase()

	// do some stuffs
}
func loadDatabase() {
	driver.GetDbDriverFactory("mongo").ConnectDatabase()
}

5. The conclusion

I do believe that there are many more types of cases that can be handled by strategy patterns. Programming is very interesting, yeah, and the software communities are always great!


Leave a Reply

Your email address will not be published.