Kezdő lépések

Symfonyból érkezőknek

Fogalomról fogalomra megfeleltetés a Symfony és a gpsystem között.

Ha Symfonyval dolgoztál, a gpsystem gondolkodásmódja ismerős lesz: a Messenger, az EventDispatcher és a Voterek mögötti fogalmak egy az egyben átjönnek. Ez az oldal megmutatja, mi minek felel meg, és őszintén azt is, mi nem transzferálódik.

Ami átjön: az üzenet-alapú aszinkron feldolgozás, az esemény/listener-gondolkodás és a kérésenkénti jogosultság-ellenőrzés fogalmi kerete. Ami nem: nincs service container és autowiring (a függőségek egy explicit Dependencies structban utaznak, amit a main.go-ban te töltesz ki), nincs bundle-rendszer, és nincs Doctrine ORM: a repository-kban SQL-t vagy bun query buildert írsz.

Megfeleltetési tábla

SymfonygpsystemHol
Scheduler komponens (#[AsPeriodicTask])scheduler.Schedule fluent API (sched.DailyAt(...), sched.Job(...))Ütemezés
Messenger (#[AsMessageHandler])events.Event + events.Listen (minden listener önálló, újrapróbált task)Események
Messenger transport, worker (messenger:consume)queue (asynq + Valkey) + cmd/worker binárisQueue, Worker
Security Voterek (voteOnAttribute)policy.Registry + generált enforcer middleware, fail-closedPolicy-k
Role hierarchy, ROLE_*rbac.Identity, RequireRole / RequirePermissionRBAC
Security firewall, authenticatorokauth (JWT kibocsátás és middleware)Autentikáció
MonologBundle (dev channel)logx/monolog (Monolog-formátumú konzol-handler)Logolás
Doctrine Migrations (doctrine:migrations:migrate)go run ./cmd/migrate (beágyazott, számozott SQL)Migrációk
EntityManager::wrapInTransaction()Transactor.WithinTransaction(ctx, fn) (a tranzakció a contextben utazik)Tranzakciók
Validator attribútumok (#[Assert\NotBlank])validator struct-tagek + generált request-típusokValidáció
Exception listenerek, ExceptionEventerrs kódok + public üzenet → RFC 9457 problem+json / SentryHibamodell
config/packages/*.yaml + .envenvconf.MustLoad[T] (típusos struct env-ből, .env-támogatással)Konfiguráció
Flysystem bundle (oneup)a storage modul Driver/Disk/Manager modellje + S3-driverTárolás
Mailer komponensmail.Message builder + MJML template-ek; a memória-mailer a teszt-duplaE-mail
Notifier komponensnotify (multi-csatornás értesítések)Értesítések
Mercurerealtime (first-party publish/subscribe)Realtime
MakerBundle (make:*)go tool gpsystem new/add ...CLI
Pagerfanta / KnpPaginatorBundlepaginate.Page[T] / paginate.CursorPage[T]Lapozás
Doctrine ORMpgx (SQL) vagy bun (query builder), nincs ActiveRecord/Doctrine-szimulációAdatbázis
Route-annotációk (#[Route]), auto-discoveryRegister<Surface>(router, deps) (explicit hívás a main.go-ban)Architektúra
Külön controller-készlet közönségenként (publikus API vs admin firewall mögött)surface (felület-köteg): surfaces/<surface>/, saját kontrakttal és middleware-reladd surface
Entity-metódusok + közös service-logikaa modul core/ csomagja (egyszer leírt szabályok, minden surface hívja)Projektstruktúra
Doctrine repository-ka modul-szintű repository/: modulonként egy, minden surface közösen használjaProjektstruktúra

Két példa egymás mellett

Messenger handler: egy asztali üzenet feldolgozása:

#[AsMessageHandler]
final class OrderPlacedHandler
{
    public function __invoke(OrderPlaced $message): void
    {
        $this->mailer->send(
            (new TemplatedEmail())
                ->to($message->email)
                ->htmlTemplate('emails/order_confirmation.html.twig')
                ->context(['orderId' => $message->orderId]),
        );
    }
}

Jogosultság-ellenőrzés: kérésenkénti tulajdonos-ellenőrzés, amikor a szerep önmagában kevés:

class OrderVoter extends Voter
{
    protected function voteOnAttribute(string $attribute, mixed $order, TokenInterface $token): bool
    {
        $user = $token->getUser();
        return $order->getUserId() === $user->getId()
            || in_array('ROLE_ADMIN', $user->getRoles(), true);
    }
}

Egy lényegi különbség: egy Symfony Voter abstain-nel is válaszolhat (nem dönt, adja tovább a láncnak), és a denyAccessUnlessGranted() hívás a controllerben elfelejthető. Itt a policy a TypeSpec-kontraktus @policy dekorátorával kötődik 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); nincs "abstain" állapot.

Viselkedésbeli különbségek

  • Események és a fan-out. A Symfony Messenger egy üzenethez egy handlert rendel (több handler regisztrálható, de tipikusan egy retry-konfiguráció vonatkozik az üzenetre). 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. Részletek: Események.
  • Tranzakciós outbox. A Doctrine EntityManager::wrapInTransaction() a commit-előtti rollbacket kezeli, de a commit-utáni crash-ablakot (a folyamat a DB-commit és az üzenet-dispatch közt hal le) nem zárja be. A gpsystem outboxa gyárilag ugyanabba az adatbázisba, ugyanabban a tranzakcióban írja az eventet, mint az üzleti változást. Részletek: Outbox.
  • Migrációk. A Doctrine Migrations-nek van séma-diff generátora (doctrine:migrations:diff); a gpsystemben nincs, csak sima, kézzel írt SQL, tudatos trade-off a diff-mágia elfedő hatása ellen. Részletek: Migrációk.

Mi nincs, és miért

  • Nincs service container és autowiring. A Dependencies struct kézzel épül a main.go-ban. Cserébe a "honnan jön ez a függőség?" kérdésre mindig egy cmd/<modul>/main.go-beli sor a válasz.
  • Nincs Doctrine ORM. 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. A bun opcionális adapter, ha a query builder kényelmét szeretnéd.
  • Nincs Twig. 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 batteries-included PHP frameworkökhöz 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 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.

Copyright © 2026