Go CDK ist eine Abkürzung für The Go Cloud Development Kit, ein Projekt zur Verwaltung von Diensten mit fast denselben Funktionen, die von großen Cloud-Anbietern mit einer einheitlichen API (früher bekannt als Go Cloud) bereitgestellt werden.
Beispielsweise kann der Prozess zum Speichern / Abrufen eines Objekts in einem Cloud-Speicherdienst mithilfe von Go CDK [^ 1] wie folgt geschrieben werden.
[^ 1]: Die im Beispielcode in diesem Artikel verwendete gocloud.dev
ist v0.20.0.
package main
import (
"context"
"fmt"
"log"
"gocloud.dev/blob"
_ "gocloud.dev/blob/s3blob"
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "s3://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
//sparen
if err := bucket.WriteAll(ctx, "sample.txt", []byte("Hello, world!"), nil); err != nil {
log.Fatal(err)
}
//Erhalten
data, err := bucket.ReadAll(ctx, "sample.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
package main
import (
"context"
"fmt"
"log"
"gocloud.dev/blob"
_ "gocloud.dev/blob/gcsblob"
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "gs://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
//sparen
if err := bucket.WriteAll(ctx, "sample.txt", []byte("Hello, world!"), nil); err != nil {
log.Fatal(err)
}
//Erhalten
data, err := bucket.ReadAll(ctx, "sample.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
package main
import (
"context"
"fmt"
"log"
"gocloud.dev/blob"
_ "gocloud.dev/blob/azureblob"
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "azblob://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
//sparen
if err := bucket.WriteAll(ctx, "sample.txt", []byte("Hello, world!"), nil); err != nil {
log.Fatal(err)
}
//Erhalten
data, err := bucket.ReadAll(ctx, "sample.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
Die Codeunterschiede bei Verwendung verschiedener Cloud-Anbieter befinden sich im Importteil des Treibers und in blob.OpenBucket ()
. Nur das Schema der angegebenen URL. Es ist wunderbar!
Auf diese Weise erleichtert Go CDK die Implementierung von Multi-Cloud-Anwendungen und hoch Cloud-portablen Anwendungen.
Wenn Sie mehr über Go CDK erfahren möchten, lesen Sie bitte die offiziellen Informationen.
Es ist ein sehr praktisches Go-CDK, aber ab Oktober 2020 scheint der Projektstatus "API ist Alpha, aber produktionsbereit" zu sein [^ 2]. Bitte gehen Sie bei der Einführung auf eigenes Risiko.
Dies ist das Hauptthema.
Ich bin sicher, es gibt Leute, die denken: "Ich benutze nur AWS! Bender Lock-In ist gut!" In diesem Artikel möchte ich die Vorteile der Verwendung von Go CDK auch für solche Personen am Beispiel von "S3-Objektoperationen (Speichern / Abrufen)" vorstellen.
Die Go CDK-API ist intuitiv, leicht verständlich und einfach zu handhaben.
Das Lesen und Schreiben von Objekten in den Cloud-Speicher mit Go CDK ist unter blob.Bucket
verfügbar.
NewReader ()
und NewWriter ()
Blob.Reader
([[email protected]/blob#Bucket.NewWriter) erhalten von /[email protected]/blob#Bet) Implementiert io.Reader
) und blob.Writer
Verwenden Sie / blob # Writer) (Implementierung von io.Writer
).
Es ist sehr intuitiv, dass Sie ein Objekt mit blob.Reader
( io.Reader
) abrufen (lesen) und mit io.Writer
(io.Writer
) speichern (schreiben) können. Auf diese Weise können Sie mit Objekten in der Cloud arbeiten, als würden Sie mit einer lokalen Datei arbeiten.
Lassen Sie uns anhand konkreter Beispiele einen Blick darauf werfen, wie es einfacher zu verstehen ist als die Verwendung des AWS SDK.
Für das AWS SDK [Upload ()
in s3manager.Uploader
](Https://pkg.go.dev/github.com/aws/aws-sdk-go/service/s3/s3manager#Uploader.Upload) wird verwendet.
Übergeben Sie den Inhalt des hochzuladenden Objekts an die Methode als "io.Reader". Beim Hochladen einer lokalen Datei ist es praktisch, os.File
so wie es ist zu übergeben, aber das Problem ist, dass die Daten im Speicher in irgendeiner Form vorliegen. Wenn Sie mit codieren und speichern möchten, wie es ist.
Zum Beispiel ist der Prozess von JSON-codiert und S3 wie er ist im AWS SDK wie folgt.
package main
import (
"encoding/json"
"io"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
func main() {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("ap-northeast-1"),
})
if err != nil {
log.Fatal(err)
}
uploader := s3manager.NewUploader(sess)
data := struct {
Key1 string
Key2 string
}{
Key1: "value1",
Key2: "value2",
}
pr, pw := io.Pipe()
go func() {
err := json.NewEncoder(pw).Encode(data)
pw.CloseWithError(err)
}()
in := &s3manager.UploadInput{
Bucket: aws.String("bucket"),
Key: aws.String("sample.json"),
Body: pr,
}
if _, err := uploader.Upload(in); err != nil {
log.Fatal(err)
}
}
Io.Pipe ()
zum Verbinden von io.Writer
zum Codieren von JSON und io.Reader
zum Übergeben an s3manager.UploadInput
#Pipe) muss verwendet werden.
Bei Go CDK erfolgt das Schreiben mit blob.Writer
( io.Writer
), also json.NewEncoder ()
Gib es einfach an.
package main
import (
"context"
"encoding/json"
"log"
"gocloud.dev/blob"
_ "gocloud.dev/blob/s3blob"
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "s3://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
data := struct {
Key1 string
Key2 string
}{
Key1: "value1",
Key2: "value2",
}
w, err := bucket.NewWriter(ctx, "sample.json", nil)
if err != nil {
log.Fatal(err)
}
defer w.Close()
if err := json.NewEncoder(w).Encode(data); err != nil {
log.Fatal(err)
}
}
Natürlich können Sie beim Hochladen einer lokalen Datei einfach schreiben.
Verwenden Sie einfach io.Copy
, als würden Sie von Datei zu Datei kopieren.
package main
import (
"context"
"io"
"log"
"os"
"gocloud.dev/blob"
_ "gocloud.dev/blob/s3blob"
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "s3://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
file, err := os.Open("sample.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
w, err := bucket.NewWriter(ctx, "sample.txt", nil)
if err != nil {
log.Fatal(err)
}
defer w.Close()
if _, err := io.Copy(w, file); err != nil {
log.Fatal(err)
}
}
Übrigens wird der Writer von "s3blob" durch Umschließen von "s3manager.Uploader" implementiert, sodass Sie von der parallelen Upload-Funktion von "s3manager.Uploader" profitieren können.
Betrachten Sie den Fall, JSON von S3 abzurufen und zu dekodieren.
Verwenden Sie für das AWS SDK s3.GetObject ()
.
S3manager.Downloader
, das mit s3manager.Uploader
gekoppelt ist, ist das Ausgabeziel. Beachten Sie, dass es in diesem Fall nicht verwendet werden kann, da es io.WriterAt
implementieren muss.
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
func main() {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("ap-northeast-1"),
})
if err != nil {
log.Fatal(err)
}
svc := s3.New(sess)
data := struct {
Key1 string
Key2 string
}{}
in := &s3.GetObjectInput{
Bucket: aws.String("bucket"),
Key: aws.String("sample.json"),
}
out, err := svc.GetObject(in)
if err != nil {
log.Fatal(err)
}
defer out.Body.Close()
if err := json.NewDecoder(out.Body).Decode(&data); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", data)
}
Für Go CDK schreiben Sie es einfach in die entgegengesetzte Richtung des Hochladens.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"gocloud.dev/blob"
_ "gocloud.dev/blob/s3blob"
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "s3://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
r, err := bucket.NewReader(ctx, "sample.json", nil)
if err != nil {
log.Fatal(err)
}
defer r.Close()
data := struct {
Key1 string
Key2 string
}{}
if err := json.NewDecoder(r).Decode(&data); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", data)
}
Verwenden Sie s3manager.Downloader
, um das abgerufene Objekt in eine lokale Datei zu schreiben. tun können.
package main
import (
"log"
"os"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
func main() {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("ap-northeast-1"),
})
if err != nil {
log.Fatal(err)
}
downloader := s3manager.NewDownloader(sess)
file, err := os.Create("sample.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
in := &s3.GetObjectInput{
Bucket: aws.String("bucket"),
Key: aws.String("sample.txt"),
}
if _, err := downloader.Download(file, in); err != nil {
log.Fatal(err)
}
}
In Go CDK ist es in Ordnung, wenn Sie es entgegen der Zeit zum Hochladen schreiben.
package main
import (
"context"
"io"
"log"
"os"
"gocloud.dev/blob"
_ "gocloud.dev/blob/s3blob"
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "s3://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
file, err := os.Create("sample.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
r, err := bucket.NewReader(ctx, "sample.txt", nil)
if err != nil {
log.Fatal(err)
}
defer r.Close()
if _, err := io.Copy(file, r); err != nil {
log.Fatal(err)
}
}
Obwohl diese Methode einfach ist, weist sie einige Nachteile auf.
Im Fall von "s3manager.Downloder" verfügt es nicht über "io.WriterAt" für das Ausgabeziel, sondern über eine parallele Downloadfunktion und eine hervorragende Leistung. Im Fall von Go CDK kann der parallele Download jedoch nicht so ausgeführt werden, wie er ist.
Wenn Sie parallel zu Go CDK herunterladen möchten, müssen Sie es selbst mit NewRangeReader ()
implementieren. es gibt.
package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"sync"
"gocloud.dev/blob"
_ "gocloud.dev/blob/s3blob"
)
const (
downloadPartSize = 1024 * 1024 * 5
downloadConcurrency = 5
)
func main() {
ctx := context.Background()
bucket, err := blob.OpenBucket(ctx, "s3://bucket")
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
file, err := os.Create("sample.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
d := &downloader{
ctx: ctx,
bucket: bucket,
key: "sample.txt",
partSize: downloadPartSize,
concurrency: downloadConcurrency,
w: file,
}
if err := d.download(); err != nil {
log.Fatal(err)
}
}
type downloader struct {
ctx context.Context
bucket *blob.Bucket
key string
opts *blob.ReaderOptions
partSize int64
concurrency int
w io.WriterAt
wg sync.WaitGroup
sizeMu sync.RWMutex
errMu sync.RWMutex
pos int64
totalBytes int64
err error
partBodyMaxRetries int
}
func (d *downloader) download() error {
d.getChunk()
if err := d.getErr(); err != nil {
return err
}
total := d.getTotalBytes()
ch := make(chan chunk, d.concurrency)
for i := 0; i < d.concurrency; i++ {
d.wg.Add(1)
go d.downloadPart(ch)
}
for d.getErr() == nil {
if d.pos >= total {
break
}
ch <- chunk{w: d.w, start: d.pos, size: d.partSize}
d.pos += d.partSize
}
close(ch)
d.wg.Wait()
return d.getErr()
}
func (d *downloader) downloadPart(ch chan chunk) {
defer d.wg.Done()
for {
c, ok := <-ch
if !ok {
break
}
if d.getErr() != nil {
continue
}
if err := d.downloadChunk(c); err != nil {
d.setErr(err)
}
}
}
func (d *downloader) getChunk() {
if d.getErr() != nil {
return
}
c := chunk{w: d.w, start: d.pos, size: d.partSize}
d.pos += d.partSize
if err := d.downloadChunk(c); err != nil {
d.setErr(err)
}
}
func (d *downloader) downloadChunk(c chunk) error {
var err error
for retry := 0; retry <= d.partBodyMaxRetries; retry++ {
err := d.tryDownloadChunk(c)
if err == nil {
break
}
bodyErr := &errReadingBody{}
if !errors.As(err, &bodyErr) {
return err
}
c.cur = 0
}
return err
}
func (d *downloader) tryDownloadChunk(c chunk) error {
r, err := d.bucket.NewRangeReader(d.ctx, d.key, c.start, c.size, d.opts)
if err != nil {
return err
}
defer r.Close()
if _, err := io.Copy(&c, r); err != nil {
return err
}
d.setTotalBytes(r.Size())
return nil
}
func (d *downloader) getErr() error {
d.errMu.RLock()
defer d.errMu.RUnlock()
return d.err
}
func (d *downloader) setErr(err error) {
d.errMu.Lock()
defer d.errMu.Unlock()
d.err = err
}
func (d *downloader) getTotalBytes() int64 {
d.sizeMu.RLock()
defer d.sizeMu.RUnlock()
return d.totalBytes
}
func (d *downloader) setTotalBytes(size int64) {
d.sizeMu.Lock()
defer d.sizeMu.Unlock()
d.totalBytes = size
}
type chunk struct {
w io.WriterAt
start int64
size int64
cur int64
}
func (c *chunk) Write(p []byte) (int, error) {
if c.cur >= c.size {
return 0, io.EOF
}
n, err := c.w.WriteAt(p, c.start+c.cur)
c.cur += int64(n)
return n, err
}
type errReadingBody struct {
err error
}
func (e *errReadingBody) Error() string {
return fmt.Sprintf("failed to read part body: %v", e.err)
}
func (e *errReadingBody) Unwrap() error {
return e.err
}
s3manager.Downloader
Go CDK wird entwickelt, um eine lokale Implementierung für alle Dienste bereitzustellen. Daher können Sie den Betrieb des Cloud-Dienstes problemlos durch die lokale Implementierung ersetzen. Auf einem lokalen Server für die Entwicklung ist es beispielsweise praktisch, alle Dienste durch lokale Implementierungen zu ersetzen, da sie ohne Zugriff auf AWS oder GCP betrieben werden können.
Für das Paket gocloud.dev / blob
, das den Cloud-Speicher verwaltet, [ fileblob
](https: // pkg. Es wird eine Implementierung bereitgestellt, die lokale Dateien liest und schreibt (go.dev/[email protected]/blob/fileblob).
Das Folgende ist ein Beispiel für das Umschalten des Ausgabeziels von codiertem JSON auf S3 und lokal, abhängig von der Option.
package main
import (
"context"
"encoding/json"
"flag"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"gocloud.dev/blob"
"gocloud.dev/blob/fileblob"
"gocloud.dev/blob/s3blob"
)
func main() {
var local bool
flag.BoolVar(&local, "local", false, "output to a local file")
flag.Parse()
ctx := context.Background()
bucket, err := openBucket(ctx, local)
if err != nil {
log.Fatal(err)
}
defer bucket.Close()
data := struct {
Key1 string
Key2 string
}{
Key1: "value1",
Key2: "value2",
}
w, err := bucket.NewWriter(ctx, "sample.json", nil)
if err != nil {
log.Fatal(err)
}
defer w.Close()
if err := json.NewEncoder(w).Encode(data); err != nil {
log.Fatal(err)
}
}
func openBucket(ctx context.Context, local bool) (*blob.Bucket, error) {
if local {
return openLocalBucket(ctx)
}
return openS3Bucket(ctx)
}
func openLocalBucket(ctx context.Context) (*blob.Bucket, error) {
return fileblob.OpenBucket("output", nil)
}
func openS3Bucket(ctx context.Context) (*blob.Bucket, error) {
sess, err := session.NewSession(&aws.Config{
Region: aws.String("ap-northeast-1"),
})
if err != nil {
return nil, err
}
return s3blob.OpenBucket(ctx, sess, "bucket", nil)
}
Wenn Sie es so ausführen, wie es ist, wird sample.json
in S3 gespeichert. Wenn Sie es jedoch mit der Option -local
ausführen, wird es in der lokalen output / sample.json
gespeichert.
Zu diesem Zeitpunkt wird die Eigenschaft des Objekts als "output / sample.json.attrs" gespeichert. Dadurch können die Eigenschaften des gespeicherten Objekts problemlos abgerufen werden.
Code, der APIs von externen Diensten wie AWS aufruft, muss sich immer darum kümmern, wie eine testbare Implementierung erstellt werden kann, aber mit Go CDK müssen Sie sich darüber keine Gedanken machen. Normalerweise abstrahieren Sie einen externen Dienst als Schnittstelle zum Implementieren von Mock und ersetzen ihn beim Testen durch Mock. Go CDK hat jedoch jeden Dienst bereits ordnungsgemäß abstrahiert und seine lokale Implementierung bereitgestellt. Da es fertig ist, können Sie es einfach so verwenden, wie es ist.
Testen Sie beispielsweise eine Struktur, die eine Schnittstelle zum Hochladen von codiertem JSON in den Cloud-Speicher implementiert, z.
type JSONUploader interface {
func Upload(ctx context.Context, key string, v interface{}) error
Im Fall des AWS SDK werden Schnittstellen für verschiedene Service-Clients bereitgestellt, sodass die Testbarkeit durch deren Verwendung gewährleistet ist.
Für s3manager
wird die Schnittstelle in einem Paket namens [ s3manageriface
] bereitgestellt (https://pkg.go.dev/github.com/aws/aws-sdk-go/service/s3/s3manager/s3manageriface).
type jsonUploader struct {
bucketName string
uploader s3manageriface.UploaderAPI
}
func (u *jsonUploader) Upload(ctx context.Context, key string, v interface{}) error {
pr, pw := io.Pipe()
go func() {
err := json.NewEncoder(pw).Encode(v)
pw.CloseWithError(err)
}()
in := &s3manager.UploadInput{
Bucket: aws.String(u.bucketName),
Key: aws.String(key),
Body: pr,
}
if _, err := u.uploader.UploadWithContext(ctx, in); err != nil {
return err
}
return nil
}
Mit einer solchen Implementierung können Sie testen, ohne tatsächlich auf S3 zuzugreifen, indem Sie ein geeignetes Modell in "jsonUploader.uploader" einfügen. Diese Scheinimplementierung wird jedoch nicht offiziell bereitgestellt, sodass Sie sie selbst implementieren oder ein geeignetes externes Paket finden müssen.
Im Fall von Go CDK wird es zu einer Struktur mit hoher Testbarkeit, indem es einfach so implementiert wird, wie es ist.
type jsonUploader struct {
bucket *blob.Bucket
}
func (u *jsonUploader) Upload(ctx context.Context, key string, v interface{}) error {
w, err := u.bucket.NewWriter(ctx, key, nil)
if err != nil {
return err
}
defer w.Close()
if err := json.NewEncoder(w).Encode(v); err != nil {
return err
}
return nil
}
Zum Testen ist es zweckmäßig, die speicherinterne "blob" -Implementierung mit dem Namen memblob
zu verwenden.
func TestUpload(t *testing.T) {
bucket := memblob.OpenBucket(nil)
uploader := &jsonUploader{bucket: bucket}
ctx := context.Background()
key := "test.json"
type data struct {
Key1 string
Key2 string
}
in := &data{
Key1: "value1",
Key2: "value2",
}
if err := uploader.Upload(ctx, key, in); err != nil {
t.Fatal(err)
}
r, err := bucket.NewReader(ctx, key, nil)
if err != nil {
t.Fatal(err)
}
out := &data{}
if err := json.NewDecoder(r).Decode(out); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(in, out) {
t.Error("unmatch")
}
}
Mit der Einführung von Go CDK haben wir andere Vorteile als Multi-Cloud-Unterstützung und Cloud-Portabilität eingeführt. Aufgrund der Art und Weise, wie mehrere Cloud-Anbieter einheitlich behandelt werden, gibt es natürlich Schwachstellen, z. B. die Unfähigkeit, Funktionen zu verwenden, die für einen bestimmten Cloud-Anbieter spezifisch sind. Daher denke ich, dass das SDK jedes Cloud-Anbieters entsprechend den Anforderungen ordnungsgemäß verwendet wird.
Go CDK selbst entwickelt sich noch, daher hoffe ich, dass es in Zukunft mehr Funktionen haben wird.