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.
- EventFlow treats cancellation as a workflow, so one user action can safely trigger promotion, notification, and audit side effects without falling apart.
- The transactional outbox is the system’s spine because it keeps database state changes and external effects from drifting out of sync.
- JWT revocation via auth_version gives the project a practical middle path between fully stateless tokens and server-side sessions.
- The codebase is organized like a system with boundaries, and that structure is what makes the reliability features testable instead of accidental.
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.
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
}
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.
| Concern | Typical event app | EventFlow |
|---|---|---|
| Registration flow | Insert row and hope | Transaction plus outbox plus domain orchestration |
| Notification delivery | Send inline during request | Write event, publish later through worker |
| JWT revocation | Wait for expiry | Increment auth_version and invalidate immediately |
| Duplicate actions | Handled in app code only | Backstopped by database constraints |
| Waitlist behavior | Manual or ad hoc | Promote the next user as part of cancellation |
| Operational posture | Works when everything is calm | Keeps 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/
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
| Dimension | Typical CRUD event app | EventFlow |
|---|---|---|
| Registration handling | Single write path | Durable workflow with side effects |
| Notification reliability | Best-effort inline call | Transactional outbox plus worker |
| Auth posture | Token expires eventually | Versioned revocation with auth_version |
| State safety | Mostly application logic | Application logic plus database constraints |
| Waitlist | Manual admin action | Automated promotion step |
| Architecture | Handlers and models | Domain 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.