Laravelből érkezőknek
Ha Laravelen dolgoztál, a gpsystem legtöbb építőköve ismerős lesz: több csomag kimondottan a Laravel-megfelelője mintájára készült. Ez az oldal megmutatja, mi minek felel meg, és őszintén azt is, mi nem transzferálódik.
Ami átjön: a gondolkodás az eventekről, listenerekről, policy-król, ütemezésről és a queue-król egy az egyben. Ami nem: nincs ORM-mágia (nincs Eloquent, a repository-kban SQL-t vagy bun query buildert írsz), nincs container-autowiring (a függőségek egy explicit Dependencies structban utaznak, amit a main.go-ban te töltesz ki), és nincs globális facade sem: minden függés látható és követhető.
Megfeleltetési tábla
| Laravel | gpsystem | Hol |
|---|---|---|
Task Scheduling ($schedule->daily()) | scheduler.Schedule fluent API (sched.DailyAt(...), sched.Job(...)) | Ütemezés |
Events & Listeners, ShouldQueue | events.Event + events.Listen (minden listener önálló, újrapróbált task) | Események |
Queues, queue:work, Horizon | queue (asynq + Valkey) + cmd/worker bináris | Queue, Worker |
Policies ($this->authorize()) | policy.Registry + generált enforcer middleware, fail-closed | Policy-k |
| Gates, szerepek (spatie/permission) | rbac.Identity, RequireRole / RequirePermission | RBAC |
| Auth guardok, Sanctum tokenek | auth (JWT kibocsátás és middleware) | Autentikáció |
| Monolog (dev channel) | logx/monolog (Monolog-formátumú konzol-handler) | Logolás |
php artisan migrate | go run ./cmd/migrate (beágyazott, számozott SQL) | Migrációk |
DB::transaction(fn) | Transactor.WithinTransaction(ctx, fn) (a tranzakció a contextben utazik) | Tranzakciók |
| FormRequest szabályok | validator struct-tagek + generált request-típusok | Validáció |
Exception render() / report() | errs kódok + public üzenet → RFC 9457 problem+json / Sentry | Hibamodell |
config() + .env | envconf.MustLoad[T] (típusos struct env-ből, .env-támogatással) | Konfiguráció |
| Storage facade, flysystem diskek | a storage modul Driver/Disk/Manager modellje + S3-driver | Tárolás |
| Mailables, markdown mail | mail.Message builder + MJML template-ek; Mail::fake() ↔ memória-mailer | |
php artisan make:* | go tool gpsystem new/add ... | CLI |
paginate() / cursorPaginate() | paginate.Page[T] / paginate.CursorPage[T] | Lapozás |
| Eloquent ORM | pgx (SQL) vagy bun (query builder), nincs ActiveRecord-szimuláció | Adatbázis |
Route regisztráció (routes/api.php, auto-discovery) | Register<Surface>(router, deps) (explicit hívás a main.go-ban) | Architektúra |
| Route-csoport / külön controller-készlet közönségenként (publikus API vs admin) | surface (felület-köteg): surfaces/<surface>/, saját kontrakttal és middleware-rel | add surface |
| Model-metódusok + közös service-logika | a modul core/ csomagja (egyszer leírt szabályok, minden surface hívja) | Projektstruktúra |
| Query-réteg (Eloquent scope-ok, query builder hívások) | a modul-szintű repository/: modulonként egy, minden surface közösen használja | Projektstruktúra |
Két példa egymás mellett
Ütemezett feladat: a ->onOneServer() viselkedése itt alapértelmezés (Valkey-lease leader election):
Schedule::command('report:nightly-sales')
->dailyAt('03:00')
->onOneServer();
func Register(sched *scheduler.Schedule, deps shop.Dependencies) {
sched.Job("shop.nightlySalesReport", "0 3 * * *",
nightlySalesReport(deps))
}
Policy (kérésenkénti tulajdonos-ellenőrzés, amikor a szerep önmagában kevés):
class OrderPolicy
{
public function view(User $user, Order $order): bool
{
return $user->id === $order->user_id
|| $user->hasRole('admin');
}
}
func New() *kitpolicy.Registry {
reg := kitpolicy.NewRegistry()
reg.Register("orders.view", func(ctx context.Context, id *rbac.Identity, req any) error {
if id.HasRole("admin") {
return nil
}
r := req.(gen.GetOrderRequest)
if !ownsOrder(ctx, id, r.OrderId) {
return kitpolicy.Deny("You can only view your own orders.")
}
return nil
})
return reg
}
Egy lényegi különbség: Laravelben a policy-t a controllerből hívod ($this->authorize()), és el is felejthető. Itt a policy-t a TypeSpec-kontraktus @policy dekorátora rendeli a művelethez, az enforcer middleware pedig a generált táblából kényszeríti ki. A megjelölt, de nem regisztrált policy nem átenged, hanem hibázik (fail-closed).
Viselkedésbeli különbségek
A mapping-tábla azt mutatja, mi minek felel meg. Ez a szakasz azt mutatja meg, hol nem azonos a viselkedés, ha a felszíni hasonlóság mögé nézel.
- Tranzakciók. Laravelben a tranzakciót egy globális connection-menedzser tartja számon: bármely modell-hívás automatikusan benne fut, a
DB::transactionpedig deadlock esetén újrapróbálkozik (attemptsparaméter), a rollbackot kivétel váltja ki. gpsystemben a tranzakciót a context hordozza (Transactor.WithinTransaction): csak az a hívás vesz részt benne, amelyik a closurectx-ét kapja; nincs automatikus deadlock-retry (a döntés a tiéd); a rollbackot simaerrorvisszaadása váltja ki, nem kivétel. Részletek: Tranzakciók. - Migrációk. A
goose_db_versiontábla a Laravelmigrationstáblájának felel meg, de nincs schema builder: sima SQL fut. Ez tudatos trade-off: a builder adatbázisok közti hordozhatóságot ad, amire itt nincs szükség (a kit PostgreSQL-re épül), cserébe elfedi a tényleges DDL-t. Sima SQL-lel elérhető PostgreSQL teljes felülete (partial indexek,GENERATEDoszlopok, CTE-s backfillek), amit egy schema builder csakDB::statement-tel tudna. Részletek: Migrációk. - Események és a fan-out. A gpsystem minden listenert önálló, saját retry-kerettel futó asynq taskként kezel: egy event N listener-taskra terül szét, egy hibázó listener újrapróbálkozása sosem futtatja újra a többit. Ez a lényegi eltérés a Laravel szinkron event-loopjától (ahol egy listener kivétele megállíthatja a többit is), és az ára az idempotencia-követelmény minden listeneren. Részletek: Események.
- Tranzakciós outbox. Laravelben nincs beépített megoldás a dual-write problémára: az
afterCommita rollback-esetet kezeli, de a commit-utáni crash-ablakot (a folyamat a DB-commit és az event-enqueue közt hal le) nem zárja be; aki garanciát akar, ott is egy külön outbox-csomagot húz be. gpsystemben ez gyárilag jön: az outbox az eventet ugyanabba az adatbázisba, ugyanabban a tranzakcióban írja, mint az üzleti változást. Részletek: Outbox. - OpenTelemetry. Ennek nincs igazi PHP-s megfelelője: a Laravel-ökoszisztémában a Telescope dev-only eszköz, a production APM pedig fizetős szolgáltatások (Blackfire, New Relic) területe. A Go-ökoszisztémában az elosztott trace-elés standard, gyártófüggetlen infrastruktúra, amit a kit egy hívással bekapcsol. Részletek: OpenTelemetry.
Mi nincs, és miért
- Nincs Eloquent. A repository-réteg pgx-szel (SQL) vagy bunnal (query builder) dolgozik, a Go-ökoszisztémában az explicit lekérdezés a bevett út, és a kit nem próbál ActiveRecordot szimulálni. A bun opcionális adapter, ha a query builder kényelmét szeretnéd.
- Nincs service container. A
Dependenciesstruct kézzel épül amain.go-ban. Cserébe a "honnan jön ez a függőség?" kérdésre mindig egycmd/<modul>/main.go-beli sor a válasz. - Nincs Blade. A kit API-backendekre fókuszál; a szerveroldali renderelés nem része.
Háttér
A kit szerzőjének motivációja pont az volt, hogy a Go-ökoszisztémában is legyen egy, a Laravelhez hasonlóan könnyen használható rendszer: ahol a projekt első napján üzleti logikát írsz, nem infrastruktúrát válogatsz. A Go-világ ereje a kis, egymástól független, jól tesztelt library-kben van. A gpsystem nem lecserélni akarja ezeket, hanem egyetlen, összecsiszolt és karbantartott kompozíciót adni belőlük, a Laravelből ismerős fogalmi kerettel. Ezért látod mindenhol a párhuzamokat, és ezért nem rejti el a kit az alatta lévő library-ket: a library-fél egy microservice chassis, a generált projekt egy service template (Richardson), nem egy futásidejű réteg, ami az app fölött él. A cél a kényelmes indulás, nem egy új, zárt keretrendszer.