17/05/2020 - GO
İki harici sosyal medya kanalımız var ve API'larını kullanmak istiyoruz. Uygulamamızı aşağıda listelenen iki gerçek senaryoyu karşılayacak şekilde tasarlamalıyız.
Tahmin edebileceğiniz gibi, ilk senaryoya giren harici bir API ile uğraşıyorsanız, büyük olasılıkla kodunuzda veya yapınızda bazı kopyaların yanı sıra, bazı ekstra mantıksal işlemleriniz olacaktır. Bununla birlikte, ikinci senaryoya giren harici bir API ile uğraşıyorsanız, işiniz çok kolay olacaktır. Tek yapmanız gereken bir yapılandırma parametresidir.
Bu örnekte her iki senaryo için de örnekler göreceğiz ama daha bitmedi. Her iki senaryo da aşağıda listelenen iki alt senaryo ile birlikte gelir.
İlk senaryoda, istemci name
ve env
sorgu parametrelerini gönderir. İkinci senaryoda, istemci name
sorgusu parametresi gönderir. Yukarıdaki senaryolara dayanarak, uygulamamız doğru "kanalı" ve "ortamı" alacak ve harici API'yı buna göre seçecek. Bu bir tür strateji tasarım modelidir.
Sonunç olarak bunlar bizim örneklerimiz. API sözleşmeleri olarak token oluşturacağız, tokeni yenileyeceğiz, mesaj göndereceğiz ve resim yükleyeceğiz.
Aşağıdaki uygulamalarda SocialMedia
genel amaçlı arayüzdür. Bir kanalın bir yönteme ihtiyacı olsun ya da olmasın, en azından boş bir gövdeyle kullanılmalıdır. Ayrıca yöntemlerin aynı imzalara sahip olması gerekecektir. Bunun için de argümanları standartlaştırmanız gerekecek. Belki genel amaçlı bir yapılandırma yapısı ile. Tasarım açısından bakıldığında, 2.2
en iyi ve 1.1
en kötü senaryolardır. Bu örnekleri size bir fikir vermek için buradalar yani ihtiyaçlarınıza göre ayarlayabilirsiniz.
Harici API'ler dışarıdan tüketilirken farklı uygulamalar gerektiriyor. Tüketicinin hangi ortamı kullandığı önemli değil. İstemci, kanalı ve ortamı istek içinde belirler.
# Image upload
- GET /image/upload?name={facebook|twitter}&env={production|sandbox}
# Message send
- GET /message/send?name={facebook|twitter}&env={production|sandbox}
# Token create
- GET /token/create?name={facebook|twitter}&env={production|sandbox}
# Token refresh
- GET /token/refresh?name={facebook|twitter}&env={production|sandbox}
├── cmd
│ └── socialise
│ └── main.go
└── internal
├── image
│ └── upload.go
├── message
│ └── send.go
├── pkg
│ └── socialmedia
│ ├── channel.go
│ │ ├── production
│ │ │ ├── facebook.go
│ │ │ ├── image.go
│ │ │ ├── message.go
│ │ │ └── token.go
│ │ └── sandbox
│ │ ├── facebook.go
│ │ ├── image.go
│ │ ├── message.go
│ │ └── token.go
│ ├── production
│ │ ├── image.go
│ │ ├── message.go
│ │ ├── token.go
│ │ └── twitter.go
│ └── sandbox
│ ├── image.go
│ ├── message.go
│ ├── token.go
│ └── twitter.go
└── token
├── create.go
└── refresh.go
package main
import (
"log"
"net/http"
"socialise/internal/image"
"socialise/internal/message"
"socialise/internal/pkg/socialmedia"
"socialise/internal/token"
facebookproduction "socialise/internal/pkg/socialmedia/facebook/production"
facebooksandbox "socialise/internal/pkg/socialmedia/facebook/sandbox"
twitterproduction "socialise/internal/pkg/socialmedia/twitter/production"
twittersandbox "socialise/internal/pkg/socialmedia/twitter/sandbox"
)
func main() {
// APPLICATION BOOTSTRAP
// ---------------------
// Register all social media channels with environments
sm := socialmedia.New()
sm.Add(facebookproduction.Name, socialmedia.Production, facebookproduction.New(facebookproduction.Config{
BaseURI: "https://api.facebook.com",
Username: "usr_1",
Password: "psw_1",
}))
sm.Add(facebooksandbox.Name, socialmedia.Sandbox, facebooksandbox.New(facebooksandbox.Config{
BaseURI: "https://api-sandbox.facebook.com",
Username: "usr_2",
Password: "psw_2",
}))
sm.Add(twitterproduction.Name, socialmedia.Production, twitterproduction.New(twitterproduction.Config{
BaseURI: "https://api.twitter.com",
ClientID: "cid_1",
ClientSecret: "csr_1",
}))
sm.Add(twittersandbox.Name, socialmedia.Sandbox, twittersandbox.New(twittersandbox.Config{
BaseURI: "https://api-sandbox.twitter.com",
ClientID: "cid_2",
ClientSecret: "csr_2",
}))
//
// Setup all routes
imgUpld := image.NewUpload(*sm)
msgSend := message.NewSend(*sm)
tokCret := token.NewCreate(*sm)
tokRefr := token.NewRefresh(*sm)
//
// Register HTTP routers
router := http.NewServeMux()
router.HandleFunc("/image/upload", imgUpld.Handle)
router.HandleFunc("/message/send", msgSend.Handle)
router.HandleFunc("/token/create", tokCret.Handle)
router.HandleFunc("/token/refresh", tokRefr.Handle)
//
// Start HTTP server
log.Println("http://0.0.0.0:8910")
server := &http.Server{Addr: ":8910", Handler: router}
log.Fatalln(server.ListenAndServe())
//
}
package image
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Upload struct {
channel socialmedia.Channel
}
// GET /image/upload?name={facebook|twitter}&env={production|sandbox}
func NewUpload(channel socialmedia.Channel) Upload {
return Upload{
channel: channel,
}
}
func (i Upload) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := i.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.UploadImage()))
}
package message
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Send struct {
channel socialmedia.Channel
}
// GET /message/send?name={facebook|twitter}&env={production|sandbox}
func NewSend(channel socialmedia.Channel) Send {
return Send{
channel: channel,
}
}
func (m Send) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := m.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.SendMessage()))
}
package socialmedia
import (
"fmt"
)
type (
Name string
Env string
)
const (
Production Env = "production"
Sandbox = "sandbox"
)
type SocialMedia interface {
CreateToken() string
RefreshToken() string
UploadImage() string
SendMessage() string
}
type Channel struct {
channels map[Name]map[Env]SocialMedia
}
func New() *Channel {
return &Channel{
channels: make(map[Name]map[Env]SocialMedia),
}
}
func (m *Channel) Add(name Name, env Env, channel SocialMedia) {
val, ok := m.channels[name]
if !ok {
val = make(map[Env]SocialMedia)
m.channels[name] = val
}
m.channels[name][env] = channel
}
func (m *Channel) Get(name Name, env Env) (SocialMedia, error) {
val, ok := m.channels[name][env]
if !ok {
return nil, fmt.Errorf("invalid channel environment: %s %s", name, env)
}
return val, nil
}
package production
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "facebook"
type Config struct {
BaseURI string
Username string
Password string
}
type Facebook struct {
Config
}
func New(config Config) Facebook {
return Facebook{config}
}
package production
func (f Facebook) UploadImage() string {
return "upload image: " + f.BaseURI
}
package production
func (f Facebook) SendMessage() string {
return "send message: " + f.BaseURI
}
package production
func (f Facebook) CreateToken() string {
return "create token: " + f.BaseURI
}
func (f Facebook) RefreshToken() string {
return "refresh token: " + f.BaseURI
}
package sandbox
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "facebook"
type Config struct {
BaseURI string
Username string
Password string
}
type Facebook struct {
Config
}
func New(config Config) Facebook {
return Facebook{config}
}
package sandbox
func (f Facebook) UploadImage() string {
return "upload image: " + f.BaseURI
}
package sandbox
func (f Facebook) SendMessage() string {
return "send message: " + f.BaseURI
}
package sandbox
func (f Facebook) CreateToken() string {
return "create token: " + f.BaseURI
}
func (f Facebook) RefreshToken() string {
return "refresh token: " + f.BaseURI
}
package production
func (t Twitter) UploadImage() string {
return "upload image: " + t.BaseURI
}
package production
func (t Twitter) SendMessage() string {
return "send message: " + t.BaseURI
}
package production
func (t Twitter) CreateToken() string {
return "create token: " + t.BaseURI
}
func (t Twitter) RefreshToken() string {
return "refresh token: " + t.BaseURI
}
package production
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "twitter"
type Config struct {
BaseURI string
ClientID string
ClientSecret string
}
type Twitter struct {
Config
}
func New(config Config) Twitter {
return Twitter{config}
}
package sandbox
func (t Twitter) UploadImage() string {
return "upload image: " + t.BaseURI
}
package sandbox
func (t Twitter) SendMessage() string {
return "send message: " + t.BaseURI
}
package sandbox
func (t Twitter) CreateToken() string {
return "create token: " + t.BaseURI
}
func (t Twitter) RefreshToken() string {
return "refresh token: " + t.BaseURI
}
package sandbox
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "twitter"
type Config struct {
BaseURI string
ClientID string
ClientSecret string
}
type Twitter struct {
Config
}
func New(config Config) Twitter {
return Twitter{config}
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Create struct {
channel socialmedia.Channel
}
// GET /token/create?name={facebook|twitter}&env={production|sandbox}
func NewCreate(channel socialmedia.Channel) Create {
return Create{
channel: channel,
}
}
func (t Create) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := t.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.CreateToken()))
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Refresh struct {
channel socialmedia.Channel
}
// GET /token/refresh?name={facebook|twitter}&env={production|sandbox}
func NewRefresh(channel socialmedia.Channel) Refresh {
return Refresh{
channel: channel,
}
}
func (t Refresh) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := t.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.RefreshToken()))
}
Harici API'ler dışarıdan tüketilirken farklı uygulamalar gerektiriyor. Tüketicinin hangi ortamı kullandığı önemli değil. İstemci kanalı istek içinde belirler, ancak ortamı uygulama belirler.
# Image upload
- GET /image/upload?name={facebook|twitter}
# Message send
- GET /message/send?name={facebook|twitter}
# Token create
- GET /token/create?name={facebook|twitter}
# Token refresh
- GET /token/refresh?name={facebook|twitter}
├── cmd
│ └── socialise
│ └── main.go
└── internal
├── image
│ └── upload.go
├── message
│ └── send.go
├── pkg
│ └── socialmedia
│ ├── channel.go
│ │ ├── production
│ │ │ ├── facebook.go
│ │ │ ├── image.go
│ │ │ ├── message.go
│ │ │ └── token.go
│ │ └── sandbox
│ │ ├── facebook.go
│ │ ├── image.go
│ │ ├── message.go
│ │ └── token.go
│ ├── production
│ │ ├── image.go
│ │ ├── message.go
│ │ ├── token.go
│ │ └── twitter.go
│ └── sandbox
│ ├── image.go
│ ├── message.go
│ ├── token.go
│ └── twitter.go
└── token
├── create.go
└── refresh.go
package main
import (
"log"
"net/http"
"socialise/internal/image"
"socialise/internal/message"
"socialise/internal/pkg/socialmedia"
"socialise/internal/token"
facebookproduction "socialise/internal/pkg/socialmedia/facebook/production"
facebooksandbox "socialise/internal/pkg/socialmedia/facebook/sandbox"
twitterproduction "socialise/internal/pkg/socialmedia/twitter/production"
twittersandbox "socialise/internal/pkg/socialmedia/twitter/sandbox"
)
func main() {
// APPLICATION BOOTSTRAP
// ---------------------
// Define application environment
env := "sandbox"
// Register all social media channels with environments
sm := socialmedia.New(socialmedia.Env(env))
switch socialmedia.Env(env) {
case socialmedia.Production:
sm.Add(facebookproduction.Name, socialmedia.Production, facebookproduction.New(facebookproduction.Config{
BaseURI: "https://api.facebook.com",
Username: "usr_1",
Password: "psw_1",
}))
sm.Add(twitterproduction.Name, socialmedia.Production, twitterproduction.New(twitterproduction.Config{
BaseURI: "https://api.twitter.com",
ClientID: "cid_1",
ClientSecret: "csr_1",
}))
case socialmedia.Sandbox:
sm.Add(facebooksandbox.Name, socialmedia.Sandbox, facebooksandbox.New(facebooksandbox.Config{
BaseURI: "https://api-sandbox.facebook.com",
Username: "usr_2",
Password: "psw_2",
}))
sm.Add(twittersandbox.Name, socialmedia.Sandbox, twittersandbox.New(twittersandbox.Config{
BaseURI: "https://api-sandbox.twitter.com",
ClientID: "cid_2",
ClientSecret: "csr_2",
}))
default:
log.Fatalln("invalid environment:", env)
}
//
// Setup all routes
imgUpld := image.NewUpload(*sm)
msgSend := message.NewSend(*sm)
tokCret := token.NewCreate(*sm)
tokRefr := token.NewRefresh(*sm)
//
// Register HTTP routers
router := http.NewServeMux()
router.HandleFunc("/image/upload", imgUpld.Handle)
router.HandleFunc("/message/send", msgSend.Handle)
router.HandleFunc("/token/create", tokCret.Handle)
router.HandleFunc("/token/refresh", tokRefr.Handle)
//
// Start HTTP server
log.Println("http://0.0.0.0:8910")
server := &http.Server{Addr: ":8910", Handler: router}
log.Fatalln(server.ListenAndServe())
//
}
package image
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Upload struct {
channel socialmedia.Channel
}
// GET /image/upload?name={facebook|twitter}
func NewUpload(channel socialmedia.Channel) Upload {
return Upload{
channel: channel,
}
}
func (i Upload) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := i.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.UploadImage()))
}
package message
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Send struct {
channel socialmedia.Channel
}
// GET /message/send?name={facebook|twitter}
func NewSend(channel socialmedia.Channel) Send {
return Send{
channel: channel,
}
}
func (m Send) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := m.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.SendMessage()))
}
package socialmedia
import (
"fmt"
)
type (
Name string
Env string
)
const (
Production Env = "production"
Sandbox = "sandbox"
)
type SocialMedia interface {
CreateToken() string
RefreshToken() string
UploadImage() string
SendMessage() string
}
type Channel struct {
env Env
channels map[Name]map[Env]SocialMedia
}
func New(env Env) *Channel {
return &Channel{
env: env,
channels: make(map[Name]map[Env]SocialMedia),
}
}
func (m *Channel) Add(name Name, env Env, channel SocialMedia) {
val, ok := m.channels[name]
if !ok {
val = make(map[Env]SocialMedia)
m.channels[name] = val
}
m.channels[name][env] = channel
}
func (m *Channel) Get(name Name) (SocialMedia, error) {
val, ok := m.channels[name][m.env]
if !ok {
return nil, fmt.Errorf("invalid channel environment: %s %s", name, m.env)
}
return val, nil
}
package production
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "facebook"
type Config struct {
BaseURI string
Username string
Password string
}
type Facebook struct {
Config
}
func New(config Config) Facebook {
return Facebook{config}
}
package production
func (f Facebook) UploadImage() string {
return "upload image: " + f.BaseURI
}
package production
func (f Facebook) SendMessage() string {
return "send message: " + f.BaseURI
}
package production
func (f Facebook) CreateToken() string {
return "create token: " + f.BaseURI
}
func (f Facebook) RefreshToken() string {
return "refresh token: " + f.BaseURI
}
package sandbox
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "facebook"
type Config struct {
BaseURI string
Username string
Password string
}
type Facebook struct {
Config
}
func New(config Config) Facebook {
return Facebook{config}
}
package sandbox
func (f Facebook) UploadImage() string {
return "upload image: " + f.BaseURI
}
package sandbox
func (f Facebook) SendMessage() string {
return "send message: " + f.BaseURI
}
package sandbox
func (f Facebook) CreateToken() string {
return "create token: " + f.BaseURI
}
func (f Facebook) RefreshToken() string {
return "refresh token: " + f.BaseURI
}
package production
func (t Twitter) UploadImage() string {
return "upload image: " + t.BaseURI
}
package production
func (t Twitter) SendMessage() string {
return "send message: " + t.BaseURI
}
package production
func (t Twitter) CreateToken() string {
return "create token: " + t.BaseURI
}
func (t Twitter) RefreshToken() string {
return "refresh token: " + t.BaseURI
}
package production
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "twitter"
type Config struct {
BaseURI string
ClientID string
ClientSecret string
}
type Twitter struct {
Config
}
func New(config Config) Twitter {
return Twitter{config}
}
package sandbox
func (t Twitter) UploadImage() string {
return "upload image: " + t.BaseURI
}
package sandbox
func (t Twitter) SendMessage() string {
return "send message: " + t.BaseURI
}
package sandbox
func (t Twitter) CreateToken() string {
return "create token: " + t.BaseURI
}
func (t Twitter) RefreshToken() string {
return "refresh token: " + t.BaseURI
}
package sandbox
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "twitter"
type Config struct {
BaseURI string
ClientID string
ClientSecret string
}
type Twitter struct {
Config
}
func New(config Config) Twitter {
return Twitter{config}
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Create struct {
channel socialmedia.Channel
}
// GET /token/create?name={facebook|twitter}
func NewCreate(channel socialmedia.Channel) Create {
return Create{
channel: channel,
}
}
func (t Create) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := t.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.CreateToken()))
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Refresh struct {
channel socialmedia.Channel
}
// GET /token/refresh?name={facebook|twitter}
func NewRefresh(channel socialmedia.Channel) Refresh {
return Refresh{
channel: channel,
}
}
func (t Refresh) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := t.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.RefreshToken()))
}
Harici API'ler dışarıdan tüketilirken sabit uygulamalar gerektiriyor. Tüketicinin hangi ortamı kullandığı önemli değil. İstemci, kanalı ve ortamı istek içinde belirler.
# Image upload
- GET /image/upload?name={facebook|twitter}&env={production|sandbox}
# Message send
- GET /message/send?name={facebook|twitter}&env={production|sandbox}
# Token create
- GET /token/create?name={facebook|twitter}&env={production|sandbox}
# Token refresh
- GET /token/refresh?name={facebook|twitter}&env={production|sandbox}
├── cmd
│ └── socialise
│ └── main.go
├── go.mod
└── internal
├── image
│ └── upload.go
├── message
│ └── send.go
├── pkg
│ └── socialmedia
│ ├── channel.go
│ │ ├── facebook.go
│ │ ├── image.go
│ │ ├── message.go
│ │ └── token.go
│ ├── image.go
│ ├── message.go
│ ├── token.go
│ └── twitter.go
└── token
├── create.go
└── refresh.go
package main
import (
"log"
"net/http"
"socialise/internal/image"
"socialise/internal/message"
"socialise/internal/pkg/socialmedia"
"socialise/internal/pkg/socialmedia/facebook"
"socialise/internal/pkg/socialmedia/twitter"
"socialise/internal/token"
)
func main() {
// APPLICATION BOOTSTRAP
// ---------------------
// Register all social media channels
sm := socialmedia.New()
sm.Add(facebook.Name, facebook.Production, facebook.New(facebook.Config{
BaseURI: "https://api.facebook.com",
Username: "usr",
Password: "psw",
}))
sm.Add(facebook.Name, facebook.Sandbox, facebook.New(facebook.Config{
BaseURI: "https://api-sandbox.facebook.com",
Username: "usr",
Password: "psw",
}))
sm.Add(twitter.Name, twitter.Production, twitter.New(twitter.Config{
BaseURI: "https://api.twitter.com",
ClientID: "cid",
ClientSecret: "csr",
}))
sm.Add(twitter.Name, twitter.Staging, twitter.New(twitter.Config{
BaseURI: "https://api-staging.twitter.com",
ClientID: "cid",
ClientSecret: "csr",
}))
// Setup all routes
imgUpld := image.NewUpload(*sm)
msgSend := message.NewSend(*sm)
tokCret := token.NewCreate(*sm)
tokRefr := token.NewRefresh(*sm)
//
// Register HTTP routers
router := http.NewServeMux()
router.HandleFunc("/image/upload", imgUpld.Handle)
router.HandleFunc("/message/send", msgSend.Handle)
router.HandleFunc("/token/create", tokCret.Handle)
router.HandleFunc("/token/refresh", tokRefr.Handle)
//
// Start HTTP server
log.Println("http://0.0.0.0:8910")
server := &http.Server{Addr: ":8910", Handler: router}
log.Fatalln(server.ListenAndServe())
//
}
package image
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Upload struct {
channel socialmedia.Channel
}
// GET /image/upload?name={facebook|twitter}&env={production|sandbox|staging}
func NewUpload(channel socialmedia.Channel) Upload {
return Upload{
channel: channel,
}
}
func (i Upload) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := i.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.UploadImage()))
}
package message
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Send struct {
channel socialmedia.Channel
}
// GET /message/send?name={facebook|twitter}&env={production|sandbox|staging}
func NewSend(channel socialmedia.Channel) Send {
return Send{
channel: channel,
}
}
func (m Send) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := m.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.SendMessage()))
}
package socialmedia
import (
"fmt"
)
type (
Name string
Env string
)
type SocialMedia interface {
CreateToken() string
RefreshToken() string
UploadImage() string
SendMessage() string
}
type Channel struct {
channels map[Name]map[Env]SocialMedia
}
func New() *Channel {
return &Channel{
channels: make(map[Name]map[Env]SocialMedia),
}
}
func (m *Channel) Add(name Name, env Env, channel SocialMedia) {
val, ok := m.channels[name]
if !ok {
val = make(map[Env]SocialMedia)
m.channels[name] = val
}
m.channels[name][env] = channel
}
func (m *Channel) Get(name Name, env Env) (SocialMedia, error) {
val, ok := m.channels[name][env]
if !ok {
return nil, fmt.Errorf("invalid channel environment: %s %s", name, env)
}
return val, nil
}
package facebook
import (
"socialise/internal/pkg/socialmedia"
)
const (
Name socialmedia.Name = "facebook"
Production socialmedia.Env = "production"
Sandbox = "sandbox"
)
type Config struct {
BaseURI string
Username string
Password string
}
type Facebook struct {
Config
}
func New(config Config) Facebook {
return Facebook{config}
}
package facebook
func (f Facebook) UploadImage() string {
return "upload image: " + f.BaseURI
}
package facebook
func (f Facebook) SendMessage() string {
return "send message: " + f.BaseURI
}
package facebook
func (f Facebook) CreateToken() string {
return "create token: " + f.BaseURI
}
func (f Facebook) RefreshToken() string {
return "refresh token: " + f.BaseURI
}
package twitter
func (t Twitter) UploadImage() string {
return "upload image: " + t.BaseURI
}
package twitter
func (t Twitter) SendMessage() string {
return "send message: " + t.BaseURI
}
package twitter
func (t Twitter) CreateToken() string {
return "create token: " + t.BaseURI
}
func (t Twitter) RefreshToken() string {
return "refresh token: " + t.BaseURI
}
package twitter
import (
"socialise/internal/pkg/socialmedia"
)
const (
Name socialmedia.Name = "twitter"
Production socialmedia.Env = "production"
Staging = "staging"
)
type Config struct {
BaseURI string
ClientID string
ClientSecret string
}
type Twitter struct {
Config
}
func New(config Config) Twitter {
return Twitter{config}
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Create struct {
channel socialmedia.Channel
}
// GET /token/create?name={facebook|twitter}&env={production|sandbox|staging}
func NewCreate(channel socialmedia.Channel) Create {
return Create{
channel: channel,
}
}
func (t Create) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := t.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.CreateToken()))
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Refresh struct {
channel socialmedia.Channel
}
// GET /token/refresh?name={facebook|twitter}&env={production|sandbox|staging}
func NewRefresh(channel socialmedia.Channel) Refresh {
return Refresh{
channel: channel,
}
}
func (t Refresh) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
env := r.URL.Query()["env"][0]
media, err := t.channel.Get(socialmedia.Name(name), socialmedia.Env(env))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.RefreshToken()))
}
Harici API'ler dışarıdan tüketilirken sabit uygulamalar gerektiriyor. Tüketicinin hangi ortamı kullandığı önemli değil. İstemci kanalı istek içinde belirler, ancak ortamı uygulama belirler.
# Image upload
- GET /image/upload?name={facebook|twitter}
# Message send
- GET /message/send?name={facebook|twitter}
# Token create
- GET /token/create?name={facebook|twitter}
# Token refresh
- GET /token/refresh?name={facebook|twitter}
├── cmd
│ └── socialise
│ └── main.go
├── go.mod
└── internal
├── image
│ └── upload.go
├── message
│ └── send.go
├── pkg
│ └── socialmedia
│ ├── channel.go
│ │ ├── facebook.go
│ │ ├── image.go
│ │ ├── message.go
│ │ └── token.go
│ ├── image.go
│ ├── message.go
│ ├── token.go
│ └── twitter.go
└── token
├── create.go
└── refresh.go
package main
import (
"log"
"net/http"
"socialise/internal/image"
"socialise/internal/message"
"socialise/internal/pkg/socialmedia"
"socialise/internal/pkg/socialmedia/facebook"
"socialise/internal/pkg/socialmedia/twitter"
"socialise/internal/token"
)
func main() {
// APPLICATION BOOTSTRAP
// ---------------------
// Register all social media channels
sm := socialmedia.New()
sm.Add(facebook.Name, facebook.New(facebook.Config{
BaseURI: "https://api.facebook.com",
Username: "usr",
Password: "psw",
}))
sm.Add(twitter.Name, twitter.New(twitter.Config{
BaseURI: "https://api.twitter.com",
ClientID: "cid",
ClientSecret: "csr",
}))
// Setup all routes
imgUpld := image.NewUpload(*sm)
msgSend := message.NewSend(*sm)
tokCret := token.NewCreate(*sm)
tokRefr := token.NewRefresh(*sm)
//
// Register HTTP routers
router := http.NewServeMux()
router.HandleFunc("/image/upload", imgUpld.Handle)
router.HandleFunc("/message/send", msgSend.Handle)
router.HandleFunc("/token/create", tokCret.Handle)
router.HandleFunc("/token/refresh", tokRefr.Handle)
//
// Start HTTP server
log.Println("http://0.0.0.0:8910")
server := &http.Server{Addr: ":8910", Handler: router}
log.Fatalln(server.ListenAndServe())
//
}
package image
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Upload struct {
channel socialmedia.Channel
}
// GET /image/upload?name={facebook|twitter}
func NewUpload(channel socialmedia.Channel) Upload {
return Upload{
channel: channel,
}
}
func (i Upload) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := i.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.UploadImage()))
}
package message
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Send struct {
channel socialmedia.Channel
}
// GET /message/send?name={facebook|twitter}
func NewSend(channel socialmedia.Channel) Send {
return Send{
channel: channel,
}
}
func (m Send) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := m.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.SendMessage()))
}
package socialmedia
import (
"fmt"
)
type Name string
type SocialMedia interface {
CreateToken() string
RefreshToken() string
UploadImage() string
SendMessage() string
}
type Channel struct {
channels map[Name]SocialMedia
}
func New() *Channel {
return &Channel{
channels: make(map[Name]SocialMedia),
}
}
func (m *Channel) Add(name Name, channel SocialMedia) {
m.channels[name] = channel
}
func (m *Channel) Get(name Name) (SocialMedia, error) {
val, ok := m.channels[name]
if !ok {
return nil, fmt.Errorf("invalid channel: %s", name)
}
return val, nil
}
package facebook
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "facebook"
type Config struct {
BaseURI string
Username string
Password string
}
type Facebook struct {
Config
}
func New(config Config) Facebook {
return Facebook{config}
}
package facebook
func (f Facebook) UploadImage() string {
return "upload image: " + f.BaseURI
}
package facebook
func (f Facebook) SendMessage() string {
return "send message: " + f.BaseURI
}
package facebook
func (f Facebook) CreateToken() string {
return "create token: " + f.BaseURI
}
func (f Facebook) RefreshToken() string {
return "refresh token: " + f.BaseURI
}
package twitter
func (t Twitter) UploadImage() string {
return "upload image: " + t.BaseURI
}
package twitter
func (t Twitter) SendMessage() string {
return "send message: " + t.BaseURI
}
package twitter
func (t Twitter) CreateToken() string {
return "create token: " + t.BaseURI
}
func (t Twitter) RefreshToken() string {
return "refresh token: " + t.BaseURI
}
package twitter
import (
"socialise/internal/pkg/socialmedia"
)
const Name socialmedia.Name = "twitter"
type Config struct {
BaseURI string
ClientID string
ClientSecret string
}
type Twitter struct {
Config
}
func New(config Config) Twitter {
return Twitter{config}
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Create struct {
channel socialmedia.Channel
}
// GET /token/create?name={facebook|twitter}
func NewCreate(channel socialmedia.Channel) Create {
return Create{
channel: channel,
}
}
func (t Create) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := t.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.CreateToken()))
}
package token
import (
"net/http"
"socialise/internal/pkg/socialmedia"
)
type Refresh struct {
channel socialmedia.Channel
}
// GET /token/refresh?name={facebook|twitter}
func NewRefresh(channel socialmedia.Channel) Refresh {
return Refresh{
channel: channel,
}
}
func (t Refresh) Handle(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query()["name"][0]
media, err := t.channel.Get(socialmedia.Name(name))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
_, _ = w.Write([]byte(media.RefreshToken()))
}