Goでデザインパターン (Proxyパターン)

Goデザインパターン

Proxyパターンとは

構造に関するデザインパターンの一つ。あるオブジェクトの代理・代用を提供し、元オブジェクトへのアクセス制御等の別処理を行う。Proxyが担う役割毎にいくつかの種類に分類される。

Proxyの一例

Virtual Proxy (仮想プロキシ)

Serviceオブジェクトの遅延初期化(lazy initialization)を行う。クライアントからの要求に対して、基本的にProxyで対処し、必要となった時点でServiceオブジェクトの生成・初期化を行う。
Serviceオブジェクトの生成にコストがかかる場合に利用される。

Remote Proxy (遠隔プロキシ)

ネットワークを介してリモートサービスのメソッド呼び出しを行う。JavaのRMI (Remote Mtthod Invocation: 遠隔メソッド呼び出し)がこれに相当する。

Access Proxy (アクセスプロキシ)

Serviceオブジェクトの利用に対して、特定のクライアントからのみ利用できるように制限を加える。

Logging Proxy (ロギングプロキシ)

Serviceオブジェクトへ送られるリクエストのログを記録する。

Cache Proxy (キャッシュプロキシ)

Serviceオブジェクトへのリクエスト結果を保持し、必要ならばこのリクエストを返す。

長所

  • クライアントに影響を与えることなく、サービスオブジェクトの制御を行うことができる。
  • サービスオブジェクトが準備中等でもプロキシ側で対応できる。
  • サービスやクライアントに変更することなく、新規プロキシを追加できる。(開放閉鎖の原則)
  • 外部ライブラリ等に対しても、新規にロギングやアクセス制御を行える。

短所

  • コードが複雑になる。

利用場面

  • サービスオブジェクトの生成にコストがかかり、実際に利用するまで初期化を遅らせたい場合(仮想プロキシ)
  • 特定のクライアントのみがサービスオブジェクトを利用できるようにしたい場合(アクセスプロキシ)
  • サービスオブジェクトへのリクエストログを残したい場合(ロギングプロキシ)
  • サービスへのリクエスト結果が大きい場合(キャッシュプロキシ)

クラス図

Proxyパターン

Service Interface

ServiceとProxyがともに実装するべきインターフェースを宣言する。これを実装することでクライアントはProxyをServiceと同様の方法で利用することができる。

Service

外部作成のライブラリ等の既存システム。何らかのビジネスロジックを提供する。

Proxy

Service Interfaceを実装し、Serviceオブジェクトへの参照を保持する。Proxyはクライアントからの要求に対して何らかの処理(遅延初期化・ロギング・キャッシュ処理など)を行い、その後必要ならばServiceオブジェクトへ仕事を委譲する。
またServiceオブジェクトのライフサイクルを管理する役割を持つ。

実装例

Service Interface

package main

type Server interface {
	HandleRequest(url, method string, body map[string]string) (int, string)
}

Service

package main

import "fmt"

type App struct {}

func NewApp() *App {
	return &App{}
}

func (a *App) HandleRequest(url, method string, body map[string]string) (int, string) {
	fmt.Println("App received a request.")

	if url == "/users" && method == "GET" {
		return 200, "['tanaka', 'kato', 'yamamoto']"
	}

	return 404, "Not Found"
}

Proxy

package main

import (
	"fmt"
	"sync"
)

var mu sync.Mutex

type Proxy struct {
	app *App
	cache map[string](map[int]string) // ex: {url_method: {statusCode: response}}
}

func NewProxy() *Proxy {
	return &Proxy{
		cache: map[string](map[int]string){},
	}
}

func (p *Proxy) HandleRequest(url, method string, body map[string]string) (int, string) {
	// 認証 (Access Proxy: アクセスプロキシ)
	user := body["user"]
	password := body["password"]
	if ok := p.checkAuth(user, password); !ok {
		return 401, "Unauthorized"
	}

	// 遅延生成 (Virtual Proxy: 仮想プロキシ)
	p.createApp()

	// キャッシュ (Cache Proxy: キャッシュプロキシ)
	key := fmt.Sprintf("%s_%s", url, method)
	if res, ok := p.cache[key]; ok {
		for code, response := range res {
			fmt.Println("response from proxy cache.")
			return code, response
		}
	}

	statusCode, response := p.app.HandleRequest(url, method, body)
	p.cache[key] = map[int]string{statusCode: response}
	return statusCode, response
}

func (p *Proxy) checkAuth(user, password string) bool {
	if user == "test_user" && password == "password" {
		return true
	}
	return false
}

func (p *Proxy) createApp() {
	mu.Lock()
	if p.app == nil {
		fmt.Println("create App instance.")
		p.app = NewApp()
	}
	mu.Unlock()
}

動作確認

package main

import "fmt"

const (
	USER = "test_user"
	WRONG_USER = "wrong_user"
	PASSWORD = "password"
	GET = "GET"
	URL = "/users"
)

func main() {
	body := map[string]string{
		"password": PASSWORD,
	}

	proxy := NewProxy()

	// 不正ユーザー
	body["user"] = WRONG_USER
	code, res := proxy.HandleRequest(URL, GET, body)
	fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)

	// 正規ユーザー
	body["user"] = USER
	code, res = proxy.HandleRequest(URL, GET, body)
	fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)

	// キャッシュ
	code, res = proxy.HandleRequest(URL, GET, body)
	fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)

	// 不正URL
	code, res = proxy.HandleRequest("", GET, body)
	fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)
}
>> go run .
Status Code: 401, Response: Unauthorized

create App instance.
App received a request.
Status Code: 200, Response: ['tanaka', 'kato', 'yamamoto']

response from proxy cache.
Status Code: 200, Response: ['tanaka', 'kato', 'yamamoto']

App received a request.
Status Code: 404, Response: Not Found

コメント

タイトルとURLをコピーしました