Bu örnekte, iki strateji modeli uygulamasını inceleyeceğiz, ancak ikisi de aynı sonucu biraz farklı bir şekilde elde ediyor. Farklı bankalar için basitçe token oluştururlar.


Her iki strateji modeli de gerçek dünya senaryolarında geçerlidir, bu nedenle "yinelenen çözüm" mutlaka kötü bir tasarım olduğu anlamına gelmez. Burada, bu tür bir iş durumunu nasıl halledebileceğimizi göreceğiz. Ayrıca her iki modelin de güçlü ve zayıf yönleri vardır.



Her iki strateji modeli de istekteki ProviderName parametresini kullanırlar. Verinin içeriğine göre:



Tüm stratejiler aynı işi farklı bir şekilde ele alır


Yapı


├── main.go
└── obie
├── access_token.go
├── barclays
│   ├── config.go
│   └── provider.go
├── natwest
│   ├── config.go
│   └── provider.go
└── provider.go

Dosyalar


main.go

package main

import (
"context"
"net/http"

"github.com/you/obie"
"github.com/you/obie/barclays"
"github.com/you/obie/natwest"
)

func main() {
barcPrv := barclays.NewProvider(barclays.Config{
TokenEndpoint: "https://barclays/token/endpoint",
ClientID: "barclays-client",
ClientSecret: "barclays-secret",
}, http.DefaultTransport)

natwPrv := natwest.NewProvider(natwest.Config{
TokenEndpoint: "https://natwest/token/endpoint",
ClientID: "natwest-client",
ClientSecret: "natwest-secret",
Scope: "natwest scope",
}, http.DefaultTransport)

provider := obie.NewProviderStrategy()
provider.Add(barclays.Name, barcPrv)
provider.Add(natwest.Name, natwPrv)

ctx := context.Background()

_, _ = accessToken(ctx, provider, obie.AccessTokenRequest{ProviderName: barclays.Name})
_, _ = accessToken(ctx, provider, obie.AccessTokenRequest{ProviderName: natwest.Name})
}

func accessToken(ctx context.Context, prv obie.Provider, req obie.AccessTokenRequest) (obie.AccessToken, error) {
return prv.AccessToken(ctx, req)
}

obie/access_token.go

package obie

type AccessTokenRequest struct {
ProviderName Name
}

type AccessToken struct {
AccessToken string
RefreshToken string
TokenType string
Scope string
ExpiresIn int
}

obie/provider.go

package obie

import (
"context"
"fmt"
)

type Name string

type Provider interface {
AccessToken(context.Context, AccessTokenRequest) (AccessToken, error)
}

type ProviderStrategy struct {
providers map[Name]Provider
}

func NewProviderStrategy() *ProviderStrategy {
return &ProviderStrategy{
providers: make(map[Name]Provider),
}
}

func (p *ProviderStrategy) Add(name Name, prv Provider) {
p.providers[name] = prv
}

func (p *ProviderStrategy) AccessToken(ctx context.Context, req AccessTokenRequest) (AccessToken, error) {
if _, ok := p.providers[req.ProviderName]; !ok {
return AccessToken{}, fmt.Errorf("access token: unknown provider: %s", req.ProviderName)
}

return p.providers[req.ProviderName].AccessToken(ctx, req)
}

obie/barclays/config.go

package barclays

type Config struct{
TokenEndpoint string
ClientID string
ClientSecret string
}

obie/barclays/provider.go

package barclays

import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"

"github.com/you/obie"
)

var Name obie.Name = "Barclays"

type Provider struct {
config Config
transport http.RoundTripper
}

func NewProvider(conf Config, trans http.RoundTripper) Provider {
return Provider{
config: conf,
transport: trans,
}
}

func (p Provider) AccessToken(ctx context.Context, req obie.AccessTokenRequest) (obie.AccessToken, error) {
data := url.Values{}
data.Set("grant_type", "client_credentials")
data.Set("client_id", p.config.ClientID)
data.Set("client_secret", p.config.ClientSecret)

httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
p.config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)

body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))

// Send the request and handle the response
// b.transport.RoundTrip(httpReq)

return obie.AccessToken{}, nil
}

obie/natwest/config.go

package natwest

type Config struct{
TokenEndpoint string
ClientID string
ClientSecret string
Scope string
}

obie/natwest/config.go

package natwest

import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"

"github.com/you/obie"
)

var Name obie.Name = "Natwest"

type Provider struct {
config Config
transport http.RoundTripper
}

func NewProvider(conf Config, trans http.RoundTripper) Provider {
return Provider{
config: conf,
transport: trans,
}
}

func (p Provider) AccessToken(ctx context.Context, req obie.AccessTokenRequest) (obie.AccessToken, error) {
data := url.Values{}
data.Set("grant_type", "client_credentials")
data.Set("client_id", p.config.ClientID)
data.Set("client_secret", p.config.ClientSecret)
data.Set("scope", p.config.Scope)

httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
p.config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)

body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))

// Send the request and handle the response
// b.transport.RoundTrip(httpReq)

return obie.AccessToken{}, nil
}

Tüm stratejiler aynı işi aynı şekilde ele alır


Yapı


├── main.go
└── obie
├── access_token.go
├── client.go
├── config.go
└── refresh_token.go

Dosyalar


main.go

package main

import (
"context"
"net/http"

"github.com/you/obie"
)

func main() {
barclays := obie.ProviderConfig{
TokenEndpoint: "https://barclays/token/endpoint",
ClientID: "barclays-client",
ClientSecret: "barclays-secret",
}
natwest := obie.ProviderConfig{
TokenEndpoint: "https://natwest/token/endpoint",
ClientID: "natwest-client",
ClientSecret: "natwest-secret",
}

config := &obie.Config{
Providers: map[obie.ProviderName]obie.ProviderConfig{
obie.ProviderName("barclays"): barclays,
obie.ProviderName("natwest"): natwest,
},
}

ctx := context.Background()

client := obie.NewClient(config, http.DefaultTransport)

_, _ = client.AccessToken(ctx, obie.AccessTokenRequest{ProviderName: "barclays"})
_, _ = client.RefreshToken(ctx, obie.RefreshTokenRequest{ProviderName: "natwest", RefreshToken: "ref-tok"})
}

obie/config.go

package obie

type ProviderName string

type Config struct {
Providers map[ProviderName]ProviderConfig
}

type ProviderConfig struct {
TokenEndpoint string
ClientID string
ClientSecret string
}

obie/client.go

package obie

import (
"context"
"net/http"
)

type Client interface {
AccessToken(context.Context, AccessTokenRequest) (AccessToken, error)
RefreshToken(context.Context, RefreshTokenRequest) (AccessToken, error)
}

type ClientStrategy struct {
config *Config
transport http.RoundTripper
}

func NewClient(config *Config, transport http.RoundTripper) ClientStrategy {
return ClientStrategy{
config: config,
transport: transport,
}
}

obie/access_token.go

package obie

import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
)

type AccessTokenRequest struct {
ProviderName ProviderName
}

type AccessToken struct {
AccessToken string
RefreshToken string
TokenType string
Scope string
ExpiresIn int
}

func (c ClientStrategy) AccessToken(ctx context.Context, req AccessTokenRequest) (AccessToken, error) {
config := c.config.Providers[req.ProviderName]

data := url.Values{}
data.Set("grant_type", "client_credentials")
data.Set("client_id", config.ClientID)
data.Set("client_secret", config.ClientSecret)

httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)

httpReq.Header.Set("Cache-Control", "no-store")
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
httpReq.Header.Set("Pragma", "no-cache")

body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))

// Send the request and handle the response
// c.transport.RoundTrip(httpReq)

return AccessToken{}, nil
}

obie/refresh_token.go

package obie

import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
)

type RefreshTokenRequest struct {
ProviderName ProviderName
RefreshToken string
}

func (c ClientStrategy) RefreshToken(ctx context.Context, req RefreshTokenRequest) (AccessToken, error) {
config := c.config.Providers[req.ProviderName]

data := url.Values{}
data.Set("grant_type", "refresh_token")
data.Set("client_id", config.ClientID)
data.Set("client_secret", config.ClientSecret)
data.Set("refresh_token", req.RefreshToken)

httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)

httpReq.Header.Set("Cache-Control", "no-store")
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
httpReq.Header.Set("Pragma", "no-cache")

body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))

// Send the request and handle the response
// c.transport.RoundTrip(httpReq)

return AccessToken{}, nil
}