storage

Áttekintés

Object storage egy Driver/Disk/Manager modell mögött, memória-, helyi lemez- és S3-kompatibilis driverekkel.

A storage egy önálló Go modul (github.com/gp-system/storage): object storage feltöltésekhez, avatarokhoz, generált riportokhoz, exportokhoz, mindenhez, ami nem egy adatbázis-sor. A shop mintaalkalmazásban az admin surface ezen keresztül tölt fel termékképeket. A gyökér modulnak nulla gp-system függősége van, és mindössze három 3rd-party függősége (go.opentelemetry.io/otel, .../metric, .../trace), így ha egy projekt egyébként nem is használja a kitet, ennek behúzása semmibe nem kerül az OTel saját API-csomagján túl. Bármilyen Go programban működik: CLI-eszközben, Lambdában, sima net/http service-ben, a gpsystem kittel vagy anélkül.

Telepítés

go get github.com/gp-system/storage@v0.1.0 # a kit is ezt a taget használja
go get github.com/gp-system/storage@latest

Go 1.23+ (a Disk Files iterátora range-over-func-ot használ). A beépített driverek külön importok: a storage/memory és a storage/local ugyanabban a modulban szállít, plusz függőségi költség nélkül. Az S3-kompatibilis driver más eset, lásd az S3 oldalt, hogy miért saját modul.

A modell: Driver, Disk, Manager

A csomag szándékosan három rétegre bomlik, mindegyiknek egy feladata van:

RétegMi ezMire használod
Drivera bővíthető backend-szerződésegyszer implementálod backendenként; a legtöbb kód sosem hívja közvetlenül
Diskegy Driver köré épített instrumentált, ergonomikus wrappera service-rétegedben nap mint nap hívott API
ManagerDisk-ek névvel ellátott gyűjteményeakkor kell, ha egy projektben egynél több disk van

Driver: a backend-szerződés

type Driver interface {
    Name() string
    Put(ctx context.Context, path string, r io.Reader, opts PutOptions) error
    Reader(ctx context.Context, path string) (io.ReadCloser, error)
    RangeReader(ctx context.Context, path string, offset, length int64) (io.ReadCloser, error)
    Stat(ctx context.Context, path string) (FileInfo, error)
    List(ctx context.Context, opts ListOptions) (ListPage, error)
    Copy(ctx context.Context, src, dst string) error
    Delete(ctx context.Context, path string) error
    URL(ctx context.Context, path string) (string, error)
    TemporaryURL(ctx context.Context, path string, opts TemporaryURLOptions) (string, error)
    TemporaryUploadURL(ctx context.Context, path string, opts TemporaryUploadURLOptions) (UploadURL, error)
}

Egy Driver-nek nem kell mindent támogatnia: egy driver, ami nem tud aláírt URL-t adni, ErrUnsupported-ot ad vissza a TemporaryURL/TemporaryUploadURL-ből ahelyett, hogy hamisítana egyet, és aki ezzel törődik, ellenőrizni tudja (lásd Disk.ProvidesTemporaryURLs). Egy driver opcionálisan implementálhatja a Mover interfészt is:

type Mover interface {
    Move(ctx context.Context, src, dst string) error
}

ha natívan tud objektumot mozgatni (átnevezés, nem bájtról bájtra másolás); a Disk.Move ezt használja, ha elérhető, egyébként Copy + Delete-re esik vissza.

Három beépített driver érkezik az ökoszisztémával: storage/memory (in-process, teszthez), storage/local (fájlrendszer, egy-node-os telepítéshez) és storage/driver/s3 (S3-kompatibilis, productionhöz). Mindhárom ugyanazt a Driver szerződést teljesíti, és a storage/storagetest ugyanazt a konformancia-suite-ot futtatja le mindháromra, plusz bármelyik saját írású driveredre.

ValidPath

func ValidPath(path string) bool

Egy kis guard, amit minden Driver-implementációtól elvárunk, hogy meghívjon, mielőtt hozzáér a backendhez: elutasítja az üres path-ot, az abszolút path-okat, a ./.. szegmenseket, és mindent, ami kiszökhetne a storage gyökeréből. Egy saját driver írásakor ezt kell meghívni a Put, Copy és társai elején, és ErrInvalidPath-t visszaadni, ha az eredmény false.

Disk: az API, amit ténylegesen hívsz

disk := storage.NewDisk(driver, storage.WithName("uploads"))

A NewDisk egy Driver-t csomagol be névvel és OTel-instrumentációval (span műveletenként, a disk nevével és a path-tal mint attribútummal), és ez az, amitől a service-kód nap mint nap függ; a teljes metódus-készletet lásd a Disk API oldalon. Opciók: WithName(string), WithTracerProvider, WithMeterProvider; ha az utóbbi kettőt nem állítod be, az instrumentáció no-op, mint mindenhol máshol a kitben.

Manager: több névvel ellátott disk

mgr := storage.New("public", map[string]*storage.Disk{
    "public":  storage.NewDisk(local.New(cfg), storage.WithName("public")),
    "backups": storage.NewDisk(s3.New(s3cfg), storage.WithName("backups")),
})

mgr.Default().Put(ctx, "avatars/42.png", r, storage.PutOptions{})
backups, _ := mgr.Disk("backups")

A legtöbb projektnek egyetlen Disk elég. A Manager arra az esetre való, amikor egy app tényleg többet is használ: publikus assetek helyi lemezen, éjszakai adatbázis-dumpok S3-on, mondjuk. A New(defaultDiskName, disks) építi fel; a Default() a névvel megadott alapértelmezettet adja vissza, a Disk(name) név szerint keres (egy map-lookuppal azonos alakú ok bool-lal), a Names() pedig felsorolja az összes regisztráltat.

Dekorátorok: viselkedés komponálása egy Driverre

Mindkettő bármilyen Driver-t becsomagol, és egy másik Driver-t ad vissza, így komponálhatók:

d := storage.ReadOnly(s3.New(cfg))                      // elutasítja az írásokat
scoped, err := storage.Scoped(s3.New(cfg), "tenant-42") // egy prefixre korlátoz
  • ReadOnly(Driver) Driver: minden Put, Delete és Copy hívás azonnal ErrReadOnly-t ad vissza, anélkül hogy elérné a backendet. Hasznos egy olyan diskhoz, ami archivált vagy külsőleg kezelt tartalmat szolgál ki, ahol egy írni próbáló bug hangosan és lokálisan hibázzon, ne csendben sikerüljön a production storage ellen.
  • Scoped(Driver, prefix string) (Driver, error): minden műveletet a prefix alatti path-okra korlátoz, transzparensen. Gyakori használat a tenant-izoláció egy megosztott bucketen: minden tenant kapjon egy Disk-et, ami Scoped(sharedDriver, "tenants/"+tenantID)-ból épül, és egy bug, ami rossz path-ot állít össze az egyik tenanthoz, nem érhet el egy másik tenant fájljait, mert a scoped driver eleve nem lát a prefixén kívüli path-okat.

Mindkettő szabadon komponálható egymással és a beépített driverekkel, mivel egy Driver-t becsomagoló Driver maga is csak egy Driver.

Kapcsolódó oldalak

Copyright © 2026