Architektúra
A gpsystem nem egy monolit keretrendszer: 12 önálló Go modul, mindegyik külön go get-elhető és bármely Go programban használható, plusz egy vékony kit, ami ezeket összerakja egy futó service-szé. Minden modul önmagában is hasznos és önmagában is érthető; ez az oldal a térképet adja: mi hol van, mi mire épül, és milyen elv szerint áll össze az egész. A kit szerepe ebben az értelemben microservice chassis (Richardson): cross-cutting infrastruktúra (életciklus, HTTP-réteg, telemetria, hibamodell), amit library-ként importálsz. Nincs benne container és nincs facade, ami a részeket láthatatlanul összefűzné: ami összekapcsolódik, az a te main.go-dban, explicit kóddal kapcsolódik össze.
Modultérkép
Minden modul saját repó, saját go.mod, saját v0.1.0 tag: egyikhez sem kell a kit, és egyikhez sem kell a listán szereplő másik modul, kivéve ahol jelezve van.
| Modul | Import-út | Mit ad | Docs-szekció |
|---|---|---|---|
errs | github.com/gp-system/errs | stackelhető hibák géppel olvasható kóddal és kliensbiztos üzenettel; nulla függőség | Áttekintés |
envconf | github.com/gp-system/envconf | típusos konfiguráció env-változókból, .env-támogatással | Áttekintés |
httperr (+httperr/validate) | github.com/gp-system/httperr | RFC 9457 problem+json hibaválaszok, egyetlen mappinggel, plusz struct-tag-alapú kérésvalidáció | Áttekintés |
dbx (+dbx/pg, dbx/bunx, dbx/seed) | github.com/gp-system/dbx | az adatbázis-agnosztikus Config/Transactor szerződés, pgx- és bun-alapú implementáció, seedelés | Áttekintés |
paginate | github.com/gp-system/paginate | offset- és cursor-lapozás típusai és segédei | Áttekintés |
queue | github.com/gp-system/queue | asynq + Valkey: task-enqueue, opciók, event-envelope | Áttekintés |
events (+events/outbox, events/scheduler) | github.com/gp-system/events | eventek listener-fan-outtal; tranzakciós outbox; cron/intervallum ütemezés leader electionnel | Áttekintés |
auth (+auth/rbac, auth/policy) | github.com/gp-system/auth | JWT kibocsátás és middleware, szerep-/permission-ellenőrzés, kérésenkénti policy-k | Áttekintés |
mail (+mail/smtp, mail/mjml) | github.com/gp-system/mail | Mailer interfész, üzenet-builder, MJML template-renderelés | Áttekintés |
notify (+notify/broadcast, notify/database) | github.com/gp-system/notify | egy címzetthez rendelt, több-csatornás értesítések (adatbázis, e-mail, élő broadcast) | Áttekintés |
storage (+storage/memory, storage/local, storage/storagetest, storage/driver/s3) | github.com/gp-system/storage | object storage egy Driver/Disk/Manager modell mögött, memória-, helyi lemez- és S3-kompatibilis driverekkel | Áttekintés |
telemetry (+telemetry/logx, telemetry/otelx, telemetry/sentryx) | github.com/gp-system/telemetry | a homlokzat: telemetry.Setup egy hívásban bootolja a logolást, az OpenTelemetryt és a Sentryt | Áttekintés |
A storage/driver/s3 a lista egyetlen beágyazott Go modulja (behúzza az AWS SDK-t, és csak azoknak a projekteknek kell ezért fizetniük, amelyeknek tényleg kell S3); minden más fenti alcsomag a szülő modulján belül szállít.
Kit chassis
A github.com/gp-system/gpsystem maga egy vékony, négy csomagos chassis, plusz a scaffolding CLI (cmd/gpsystem, lásd a CLI-referenciát):
| Csomag | Import-út | Mit ad | Docs-szekció |
|---|---|---|---|
app | github.com/gp-system/gpsystem/app | a signal-vezérelt graceful-shutdown processz-életciklus, amin minden chassis ül | Alkalmazás-életciklus |
server | github.com/gp-system/gpsystem/server | a chi-alapú HTTP engine: server.Config, server.Run, RegisterFunc, StrictValidator | A server mag |
worker | github.com/gp-system/gpsystem/worker | a háttérfeldolgozó chassis: asynq szerver + outbox relay + ütemező egy binárisban | Worker |
realtime | github.com/gp-system/gpsystem/realtime | a WebSocket/SSE gateway, ami élőben pusholja az értesítéseket a kapcsolódó klienseknek, TopicAuth | Realtime |
A generátorok és a sablonok a kit internal/-jában élnek. A fogyasztó projektek csak a kimenetüket látják, plusz a TypeSpec→OpenAPI→szerverkód pipeline-t.
Rétegződés: minden önálló, a kit felül
A 12 modul és a kit szigorú rétegződést alkot, nem egy összegubancolódott hálót:
- Az
errsvan legalul, nulla 3rd-party függőséggel. A listán semmi nem függ a kittől, de több modul (httperr,dbx,auth,events,mail,notify) saját hibaértékeihez azerrs-re épít, ugyanúgy, ahogy bármelyik saját csomagod is tehetné. - Minden modul önállóan is működik, bármilyen Go programban, kit nélkül is: az
import "github.com/gp-system/dbx/pg"egy CLI-eszközben, egy Lambdában vagy egy simanet/httpservice-ben pontosan azt a modul-lábnyomot húzza be, semmit a másik 11-ből. - A kit ezekre épít fel. A
server, aworkerés arealtimeegy kiválasztott modul-részhalmazt drótoz be egy futtatható processzbe (config-kompozíció, életciklus, routing), és a CLI generálja a wiring-kódot, így ritkán kell kézzel megírnod. A kit használata kényelmi szempont, sosem kötelezettség: bármely modul-részhalmazt közvetlenül is átvehetsz a kit teljes kihagyásával, vagy indulhatsz a kittel, és ott lépsz le egy modul saját API-jára, ahol a generált wiring nem illik.
Ezért indulhat egyszerre a telepítési útmutató és a shop mintaalkalmazás is a go get github.com/gp-system/<modul>@latest-től: nincs privát lépés, nincs replace-direktíva, nincs szükség monorepo-checkoutra semmihez.
Egy service életciklusa
A server csomag server.Config-ja a HTTP-réteg beállításait hordozza (timeoutok, CORS, body-limit); maga a processz-életciklus az app csomagban él, és a server.Run az app.Run-ra delegál. A lépéssor minden gpsystem HTTP-processzben ugyanaz:
Telemetria bootstrap
A telemetry.Setup egy hívásban bootolja az observabilityt: először a Sentryt (telemetry/sentryx, ha van SENTRY_DSN), aztán az OTel SDK-t (telemetry/otelx: resource, OTLP exporterek a standard OTEL_* env-változók szerint, W3C propagáció), végül a default slog loggert (telemetry/logx: OTLP bridge + konzol handler + Sentry handler egy fanoutban). OTEL_DEV_MODE=true mellett exporterek nélkül, olvasható konzolloggal fut. Az egész lépés kihagyható a WithoutTelemetry() opcióval.
App / router felépítése
A server.Run a kit alapértelmezéseivel épül fel: a router maga írja az RFC 9457 Problemet panicre, 404-re és 405-re (a httperr-en keresztül); az elkapott panic errs hibává válik, amelynek stackje a panic helyére mutat; a kérésvalidáció a generált strict pipeline-ban fut (server.StrictValidator); majd az OTel HTTP middleware, kérésenkénti Sentry hub, CORS és body-limit, végül a WithMiddleware(...)-rel átadott extrák.
Route-regisztráció
A RegisterFunc callbacked mountolja a modulokat: chi.Router-t kapsz, és a natív chi API-val dolgozol: groupok, route-ok, middleware.
Listen + signal-kezelés
A listen goroutine-ban fut; a signal.NotifyContext figyeli a SIGINT/SIGTERM-et. A szülő context cancelje is shutdownt vált ki, ami tesztekben hasznos.
Graceful shutdown
Signalra, szigorú sorrendben:
- shutdown a
ShutdownTimeout-tal: nincs új kérés, az in-flight kérések lefutnak - a
WithClosercloserek LIFO sorrendben futnak (amit utoljára nyitottál, az záródik először) - a telemetria utolsóként flushol, így a closerek spanjei is exportálódnak
Második signal azonnal öli a processzt. Tiszta leállásnál a Run nil-t ad vissza.
A worker chassis (worker.Run) pontosan ugyanerre az app.Run-ra épül: HTTP helyett egy asynq szervert, az outbox relay-t és az ütemező futtatót állítja le ugyanebben a rendben. A részletes belső működést az Alkalmazás-életciklus oldal írja le.
server.New(cfg, opts...) futtatás nélkül adja vissza a konfigurált *chi.Mux-ot.Centralizál, nem absztrahál
A kit tervezési elve: a külső függőségekhez való hozzáférés egy-egy csomagban összpontosul, de a típusaik szabadon átfolynak a publikus API-n. Nincs "gpsystem router-interfész", nincs "gpsystem query-absztrakció". A szignatúrák nyíltan a 3rd-party típusokat hordozzák:
// server: a RegisterFunc chi.Router-t kap
func Run(ctx context.Context, cfg server.Config, register server.RegisterFunc, opts ...Option) error
// dbx/pg: nyers pgxpool.Pool-t és pgx típusokat ad
func MustNewPool(ctx context.Context, cfg dbx.Config) *pgxpool.Pool
func (d *DB) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
// dbx/bunx: *bun.DB-t ad ugyanarra a poolra
func Open(pool *pgxpool.Pool) *bun.DB
// worker: az escape hatch nyers asynq-ot ad
func WithMux(fn func(mux *asynq.ServeMux)) Option
A chi-, pgx-, bun- és asynq-dokumentáció, a Stack Overflow-válaszok és az upstream példakódok nálad is egy az egyben érvényesek. Nem egy kit-specifikus wrapper-API-t kell megtanulnod, hanem a Go-ökoszisztéma bevett library-jeit, egy helyen összecsiszolva. A generált projekt ebben az értelemben egy service template (Richardson): futtatható vázkód, amit megkapsz és onnantól a tiéd, nem egy futásidejű réteg, ami az app fölött él.
A három kimondott kivétel
Három helyen egy-egy modul mégis interfész mögé rejti a függőséget, mindháromszor konkrét okkal:
- A
storageDriver/Disk/Managermodellje elrejti a konkrét backendet: egy fájlfeltöltéshez ne kelljen az app-kódodnak az AWS SDK-t importálnia, és a backend konfigurációval cserélhető. Ez a modul mutatja meg legjobban, hogy az absztrakció megdolgozik a keretéért: nem elméleti varrat, valóban több valódi driver áll mögötte (memóriabeli unit tesztekhez, helyi lemez egy egy-node-os telepítéshez, S3-kompatibilis production-höz), mind ugyanazt aDriverszerződést teljesíti, és astorage/storagetestugyanazt a konformancia-suite-ot futtatja le mindegyiken. - A
telemetry/sentryxbecsomagolja a sentry-go-t: a Sentry env-ből kapcsolható (SENTRY_DSNüresen = teljesen kikapcsolva, minden hook no-op), és atelemetryhomlokzaton kívül senki nem importálja: aki csak atelemetry/otelx-et használja (pl. egy CLI vagy migrációs tool), annak a binárisába a sentry-go be sem linkelődik. - A
mailMailerinterfésze mögött él a go-mail és az mjml-go, így tesztben memória-mailerre cserélhető.
Cserélhető és elhagyható részek
A centralizálás másik oldala, hogy a részek nem tapadnak össze:
- A
dbx/dbx/pga kit nélkül is használható: egy CLI-eszköz, egy Lambda vagy acmd/migratebináris ugyanazzal a poollal és transactorral dolgozik, HTTP-réteg ésserver-import nélkül. - A Sentry teljesen elhagyható: üres
SENTRY_DSNmellett atelemetry/sentryxintegráció halott kód. - Az OTel dev módban exporterek nélkül fut (
OTEL_DEV_MODE=true), vagy teljesen kihagyható aWithoutTelemetry()-vel. - Az üzleti kód független a transporttól: a handler, a service és a repository kód generált interfészekre és
dbx/auth/eventstípusokra épül, sosem közvetlenül achi-re; csak az entrypoint és aregister.goér hozzá a HTTP-réteghez. - A DB-implementáció importtal dől el: a repository
dbx/pg-t (SQL) vagydbx/bunx-ot (query builder) importál, adbx.Transactorinterfész és a contextben utazó tranzakció közös.
Hol a határ
A kit nem vezet be néhány dolgot, ami átbillentené framework felé (Fowler: Inversion of Control): nincs Module-interfész, amit egy bootstrap felfedez és sorba rendez; nincs kit-tulajdonú lifecycle-hook a business-kódban; nincs futásidejű DI-konténer, ami feloldja a függőségi gráfot helyetted. Ha ezek közül bármelyik megjelenne, a becsületes elnevezés innentől "framework" lenne, nem "toolkit".
Közös kód: egy döntési létra
Minden projektben előbb-utóbb szükség lesz olyan kódra, amit több surface vagy több modul is használ. Hogy hova kerüljön, attól függ, mennyire széles körben osztott. Ez egy döntési létra, nem egyetlen szabály:
Surface-lokális
Egy surface használja → marad a surface service csomagjában. Ne emeld ki csak azért, mert újrahasznosíthatónak tűnik. Akkor emeld ki, amikor tényleg megjelenik a második hívó.
Modul-core
Egy modul több surface-e használja az üzleti szabályt vagy entitást → a modul generált core/ csomagjába kerül. Ez a megosztott szabályok alapértelmezett, generált helye: minden modul az első surface-ével együtt kapja, a surface-service-ek ide delegálnak. A perzisztencia párja a modul-szintű repository/: modulonként egyetlen perzisztencia-réteg, minden surface számára közösen.
Modul-szint
Nem üzleti szabály, hanem a modul által termelt tartalom, amit a jobs/listeners egységek vagy több surface használ → egy névvel ellátott sibling csomag az internal/modules/<modul>/ alatt, pl. internal/modules/contact/mail/. Soha ne shared/ legyen a neve: nevezd el aszerint, amit nyújt, ugyanaz a szabály, ami bármely Go csomagra vonatkozik (lásd Design patternek).
App-szint
Két vagy több modul használja, vagy maga a cmd/* wiring → internal/platform/<név>/. Ez az egyetlen app-szintű közös hely, amivel egy generált projekt rendelkezik; nincs pkg/ és nincs internal/pkg/ (lásd Projektstruktúra). Az internal/platform tartalmazhat: env-config kompozíciót, infrastruktúra-adaptereket és gyárfüggvényeket, framework-glue-t, kis, technikai jellegű, keresztmetsző segédeszközöket (kérés-nyelv, kérés-ID). NEM tartalmazhat: üzleti entitásokat, üzleti szabályokat, vagy modul-specifikus sablonokat és payloadokat; ez a tartalom a modul szintjein marad, lejjebb a létrán. Mint minden más szinten, itt is érvényes az elnevezési szabály: util, common, helpers, shared tilos.
Kit-szint
Projekt-agnosztikus, és tényleg kell egy második projektnek → promóció egy önálló modulba (vagy, ha chassis-specifikus wiringről van szó, a gpsystem kitbe).
A teljes létra egy kidolgozott példán: a mail/mail/smtp modulok a transzport (modul-szintű abban az értelemben, hogy minden projektnek kell egy SMTP-kliens, de önálló: nulla projekt-specifikus tudást hordoznak). Egy modul címzettlistája és e-mail sablonjai üzleti tartalom (modul-szint a projektben: internal/modules/contact/mail/). Az összerakott MAIL_* config az internal/platform/config-ban él. Lásd a Mail oldal teljes példáját.
Merre tovább
- A shop mintaalkalmazás megmutatja, hogyan áll össze mindebből egy projekt: belépési pontokkal, workerrel, eventekkel.
- Az Alkalmazás-életciklus az
app.Runbelsejét nyitja fel. - A Hibamodell az
errs→httperrláncot követi végig. - A Codegen pipeline a TypeSpec-től a generált szerverkódig visz.
- A Design patternek a fenti modulokban visszatérő tervezési mintákat gyűjti egy katalógusba, kódpéldával és külső forrással.
- A Külső függőségek minden mögöttes library-t felsorol verzióval és hivatalos dokumentációval.