Koncepciók

Architektúra

A modultérkép, egy service életciklusa, és az elv, ami az egészet összetartja.

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.

ModulImport-útMit adDocs-szekció
errsgithub.com/gp-system/errsstackelhető hibák géppel olvasható kóddal és kliensbiztos üzenettel; nulla függőségÁttekintés
envconfgithub.com/gp-system/envconftípusos konfiguráció env-változókból, .env-támogatássalÁttekintés
httperr (+httperr/validate)github.com/gp-system/httperrRFC 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/dbxaz adatbázis-agnosztikus Config/Transactor szerződés, pgx- és bun-alapú implementáció, seedelésÁttekintés
paginategithub.com/gp-system/paginateoffset- és cursor-lapozás típusai és segédeiÁttekintés
queuegithub.com/gp-system/queueasynq + Valkey: task-enqueue, opciók, event-envelopeÁttekintés
events (+events/outbox, events/scheduler)github.com/gp-system/eventseventek listener-fan-outtal; tranzakciós outbox; cron/intervallum ütemezés leader electionnelÁttekintés
auth (+auth/rbac, auth/policy)github.com/gp-system/authJWT 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/mailMailer interfész, üzenet-builder, MJML template-renderelésÁttekintés
notify (+notify/broadcast, notify/database)github.com/gp-system/notifyegy 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/storageobject 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/telemetrya 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):

CsomagImport-útMit adDocs-szekció
appgithub.com/gp-system/gpsystem/appa signal-vezérelt graceful-shutdown processz-életciklus, amin minden chassis ülAlkalmazás-életciklus
servergithub.com/gp-system/gpsystem/servera chi-alapú HTTP engine: server.Config, server.Run, RegisterFunc, StrictValidatorA server mag
workergithub.com/gp-system/gpsystem/workera háttérfeldolgozó chassis: asynq szerver + outbox relay + ütemező egy binárisbanWorker
realtimegithub.com/gp-system/gpsystem/realtimea WebSocket/SSE gateway, ami élőben pusholja az értesítéseket a kapcsolódó klienseknek, TopicAuthRealtime

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 errs van 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 az errs-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 sima net/http service-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, a worker és a realtime egy 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:

  1. shutdown a ShutdownTimeout-tal: nincs új kérés, az in-flight kérések lefutnak
  2. a WithCloser closerek LIFO sorrendben futnak (amit utoljára nyitottál, az záródik először)
  3. 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.

Tesztekhez a 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 storage Driver/Disk/Manager modellje 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 a Driver szerződést teljesíti, és a storage/storagetest ugyanazt a konformancia-suite-ot futtatja le mindegyiken.
  • A telemetry/sentryx becsomagolja a sentry-go-t: a Sentry env-ből kapcsolható (SENTRY_DSN üresen = teljesen kikapcsolva, minden hook no-op), és a telemetry homlokzaton kívül senki nem importálja: aki csak a telemetry/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 mail Mailer interfé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/pg a kit nélkül is használható: egy CLI-eszköz, egy Lambda vagy a cmd/migrate bináris ugyanazzal a poollal és transactorral dolgozik, HTTP-réteg és server-import nélkül.
  • A Sentry teljesen elhagyható: üres SENTRY_DSN mellett a telemetry/sentryx integráció halott kód.
  • Az OTel dev módban exporterek nélkül fut (OTEL_DEV_MODE=true), vagy teljesen kihagyható a WithoutTelemetry()-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/events típusokra épül, sosem közvetlenül a chi-re; csak az entrypoint és a register.go ér hozzá a HTTP-réteghez.
  • A DB-implementáció importtal dől el: a repository dbx/pg-t (SQL) vagy dbx/bunx-ot (query builder) importál, a dbx.Transactor interfé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.Run belsejét nyitja fel.
  • A Hibamodell az errshttperr lá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.
Copyright © 2026