EventFlow: The Event Platform That Treats Registration Like a Distributed System

A Go and PostgreSQL codebase that turns registration, waitlists, auth revocation, and audit logging into one disciplined flow.

8 min read • View on GitHub • More from AkshatShrivastava0104

A registration desk reimagined as a mechanical control room, where one signup splits into a notification queue, a waitlist ladder, and an audit log binder. The image explains that a single user action in EventFlow triggers durable side effects instead of a simple database insert.
Registration is the visible action. The real work is the chain of durable consequences behind it.
Key Takeaways

The smallest action in EventFlow is not small at all. A registration click can update capacity, move a person off the waitlist, emit a notification, and leave an audit trail behind. That is the difference between a demo and software that expects things to go wrong.

The smallest action in EventFlow is not small at all

EventFlow is a multi-tenant event management platform for admins and attendees, but the product story is not really about events. It is about the hard edges around events: oversubscription, cancellations, stale tokens, duplicate writes, and the need to explain what happened later.

Most event apps stop at CRUD. EventFlow keeps going, because the real product work begins when the first attendee cancels and the system has to decide who gets promoted, what gets notified, and how to keep the record straight.

The outbox pattern is the real spine

The most important design choice in the repo is the transactional outbox. Primary state changes and outbox rows land in the same database transaction, which means the system can say, with confidence, that a successful write will eventually produce its side effects.

That matters because the usual failure mode is split-brain behavior. The database says the registration succeeded, but the notification never fires. EventFlow avoids that gap by making the outbox table part of the source of truth, then letting a worker publish the deferred work later.

A cancellation does not end the story. It starts a coordinated sequence that preserves consistency across services and background work.

Waitlist promotion turns cancellation into a workflow

`CancelRegistration` is a good example of the repo’s bias toward orchestration. It does not just remove a row and stop. It calls `waitlistService.PromoteNextUser`, which turns a cancellation into a handoff between services.

That choice keeps the logic decoupled. The registration domain knows that the next person should move up, but it does not need to know the mechanics of ordering, notification timing, or queue processing. Those details stay where they belong.

// Conceptual flow inside the registration service
func (s *Service) CancelRegistration(ctx context.Context, registrationID string) error {
    // 1. Update registration state in the transaction
    // 2. Ask waitlist service to promote the next attendee
    // 3. Write outbox event for deferred notifications
    // 4. Commit once all state transitions are consistent
    return nil
}
A close-up of a JWT stamp being overprinted with a new auth_version seal, while an underlying user record increments a version counter in a ledger. The image explains how EventFlow can revoke old tokens immediately without waiting for expiry.
A version counter in the database gives JWTs a revocation switch.

JWTs get a revocation switch

The auth story is more interesting than ordinary JWT middleware. EventFlow uses `auth_version` as a database-backed revocation check, which means a password change or security event can invalidate outstanding tokens immediately.

That is a smart compromise. Fully stateless tokens are easy to issue but hard to retract. Fully server-side sessions are easy to revoke but heavier to manage. EventFlow picks a middle path that keeps JWT convenience while restoring control when it matters.

Why the database is doing so much of the heavy lifting

The migrations suggest a project that expects concurrency problems rather than hoping they never happen. Unique constraints and idempotency rules help stop duplicate registrations, duplicate check-ins, and other forms of accidental double entry before they become user-visible bugs.

ConcernTypical event appEventFlow
Registration flowInsert row and hopeTransaction plus outbox plus domain orchestration
Notification deliverySend inline during requestWrite event, publish later through worker
JWT revocationWait for expiryIncrement auth_version and invalidate immediately
Duplicate actionsHandled in app code onlyBackstopped by database constraints
Waitlist behaviorManual or ad hocPromote the next user as part of cancellation
Operational postureWorks when everything is calmKeeps behaving when things fail

This is what production-minded design looks like in practice. The database is not just storage. It is the place where the system defends itself against duplicate intent, race conditions, and partial failure.

The codebase is organized like a system, not a pile of handlers

The repository layout matters because it matches the shape of the problem. `cmd/api` starts the app, `internal/` holds the business logic, repositories own persistence, handlers stay thin, and migrations define the database contract. That is how a Go service stays navigable after the first feature rush.

The payoff is testability. When boundaries are explicit, you can reason about registration, waitlisting, auth, and audit logging as separate units that still cooperate under load.

EventFlow versus a typical event app

DimensionTypical CRUD event appEventFlow
Registration handlingSingle write pathDurable workflow with side effects
Notification reliabilityBest-effort inline callTransactional outbox plus worker
Auth postureToken expires eventuallyVersioned revocation with auth_version
State safetyMostly application logicApplication logic plus database constraints
WaitlistManual admin actionAutomated promotion step
ArchitectureHandlers and modelsDomain split with repositories, services, handlers, and workers

EventFlow is not impressive because it has many features. It is impressive because those features are wired together like they were meant to survive real use. The repo encodes habits that many teams only discover after incidents.

What this repo teaches

The main lesson is simple. Reliability is not a layer you add at the end. It is the shape of the code, the database, and the boundaries between them. EventFlow looks like an event app, but it behaves like a system that expects to be stressed.