Proxy Design Pattern in Go

ansu jain
3 min readFeb 20, 2023

--

Before I start Proxy Design pattern, let me explain more about Manager design pattern.Manager is common software design pattern to create a separate “manager” object that coordinates and manages other objects within a system. The responsibilities of a manager class can vary depending on the context, but generally, it is responsible for the high-level coordination of objects and the delegation of responsibilities.

A manager class can be implemented in Go by creating a struct and adding methods to it that coordinate the behaviour of other objects.

There are several design pattern similar to the Manager pattern. For examples

  1. Facade Pattern — The Facade pattern is used to provide a simplified interface to a complex system, making it easier to use and understand. Like the manager pattern, the Facade provides a single entry point to a set of functionality, but its focus is on simplifying the interface rather than managing the underlying objects.
  2. Proxy Pattern — The Proxy pattern provides a substitute or placeholder for another object, allowing the proxy to control access to the original object. This can be useful for adding extra functionality or security checks around an object, without modifying the object itself.
  3. Mediator Pattern — The Mediator pattern is used to reduce dependencies between objects by having them communicate through a central mediator object. The mediator object coordinates the interactions between the other objects, and can help to simplify the design of complex systems.

Proxy Design Pattern

This can be useful for adding extra functionality, like logging, caching, or security checks, without modifying the original object. In Go, the Proxy pattern can be implemented using interfaces and struct types.

Here’s an example of the Proxy pattern in Go:

package main

import (
"database/sql"
"errors"
"fmt"
"time"

_ "github.com/mattn/go-sqlite3"
)

// Database defines the interface that the real database and proxy will implement.
type Database interface {
Connect() (*sql.DB, error)
}

// RealDatabase implements the Database interface and represents the actual database resource.
type RealDatabase struct{}

func (r *RealDatabase) Connect() (*sql.DB, error) {
db, err := sql.Open("sqlite3", "test.db")
if err != nil {
return nil, err
}
// Simulate a slow connection by waiting for a few seconds.
time.Sleep(2 * time.Second)
return db, nil
}

// ProxyDatabase implements the Database interface and provides a surrogate or placeholder for the real database connection.
type ProxyDatabase struct {
realDatabase *RealDatabase
}

func (p *ProxyDatabase) Connect() (*sql.DB, error) {
if p.realDatabase == nil {
p.realDatabase = &RealDatabase{}
}
// Perform some security checks before allowing access to the real database.
// For example, check if the user is authorized to access the database.
if !isUserAuthorized() {
return nil, errors.New("user is not authorized to access the database")
}
// Log the access to the database.
fmt.Println("User accessed the database at", time.Now().Format(time.RFC3339))
return p.realDatabase.Connect()
}

func isUserAuthorized() bool {
// Perform some logic to determine if the user is authorized to access the database.
return true
}

func main() {
var db Database = &ProxyDatabase{}
connection, err := db.Connect()
if err != nil {
fmt.Println("Failed to connect to the database:", err)
return
}
defer connection.Close()
// Use the database connection.
fmt.Println("Successfully connected to the database.")
}

In this example, we define the Database interface, which both the RealDatabase and ProxyDatabase types will implement. The RealDatabase struct implements the Connect method to establish a connection to the actual database, while the ProxyDatabase struct provides a Connect method that performs some security checks and logs the access to the database before calling the Connect method of the real database.

In the Connect method of the ProxyDatabase struct, we check if the realDatabase field has been initialized, and if not, we create a new RealDatabase object. We then perform some security checks to determine if the user is authorized to access the database, and log the access to the database. If everything checks out, we call the Connect method of the realDatabase object.

Finally, in the main function, we create a variable of the Database interface type, and assign it to a new instance of the ProxyDatabase struct. We then call the Connect method on the db variable, which will call the Connect method of the ProxyDatabase object, which in turn will call the Connect method of the RealDatabase object.

This example shows how the Proxy pattern can be used to control access to a sensitive resource like a database, by performing security checks and logging access before allowing access to the real resource.

--

--