A robust Ticketing System implementation showcasing Clean Architecture, Domain-Driven Design (DDD), and Test-Driven Development (TDD) in Go.
This project implements a high-concurrency ticket purchasing system where data consistency is paramount. It serves as a reference for handling complex business rules (e.g., race conditions, atomic transactions) within a maintainable, modular codebase.
Key Concepts:
- Clean Architecture: Decoupled layers (Domain, UseCase, Infrastructure, Presentation) ensuring testability.
- DDD (Domain-Driven Design): Rich domain models encapsulate business logic (
Order,Ticket). - TDD (Test-Driven Development): Comprehensive test coverage for all layers.
- RBAC (Role-Based Access Control): Secure access tailored for
UserandAdminroles.
- Language: Go 1.25+
- Framework: Gin (HTTP Web Framework)
- Database: PostgreSQL (Driver:
pgx, Helper:sqlx) - Authentication: JWT (
golang-jwt/jwt/v5) - Migrations: Goose
- Testing: Testify, SQLMock
- Utilities: Google UUID, Bcrypt
.
├── cmd/api # Application entrypoint
├── internal
│ ├── domain # Enterprise Business Rules (Entities)
│ ├── usecase # Application Business Rules (Interactors)
│ └── infra # Frameworks & Drivers
│ ├── database # DB Connection & Transactions
│ ├── http # Handlers, Middleware, Routes
│ └── repository # Data Access Implementation
├── sql/migrations # Database schema migrations
└── Makefile # Automation commands
- Go 1.25+
- Docker & Docker Compose
- Make
-
Install Dependencies:
make install
-
Start Services:
make run # OR docker-compose up -d -
Run Migrations:
make migration-up
-
Run Tests:
make test-unit # Run unit tests only make test-e2e # Run end-to-end tests (requires test database) make test-all # Run all tests
classDiagram
class User {
+UUID ID
+String Name
+String Email
+String Password
+String Role
}
class Event {
+UUID ID
+UUID UserID
+String Name
+String Location
+String Organization
+String Rating
+DateTime Date
+String ImageURL
+Int Capacity
+Decimal Price
+String Description
}
class Order {
+UUID ID
+UUID EventID
+UUID UserID
+Decimal TotalAmount
+Int Quantity
+String Status
+DateTime CreatedAt
}
class Ticket {
+UUID ID
+UUID EventID
+UUID OrderID
+Decimal Price
+String Status
}
User "1" -- "N" Order : places
Event "1" -- "N" Ticket : defines
Order "1" -- "N" Ticket : contains
Ticket "1" -- "1" Event : belongs_to
This diagram illustrates the critical section handling for ticket purchasing to prevent race conditions.
sequenceDiagram
participant U as User
participant API as Ticket API
participant DB as Postgres
Note over U, API: User selects 2 tickets for Event X
U->>API: POST /orders {eventId, qty: 2}
rect rgb(240, 240, 240)
Note right of API: Transaction Start (Critical Section)
API->>DB: BEGIN TRANSACTION
API->>DB: SELECT capacity ... FOR UPDATE
DB-->>API: Row Lock (Event Info)
API->>API: Calculate Available Capacity
alt Sufficient Stock
API->>DB: INSERT INTO orders (PENDING)
API->>DB: INSERT INTO tickets (x2)
API->>DB: COMMIT
API-->>U: 201 Created
else Insufficient Stock
API->>DB: ROLLBACK
API-->>U: 409 Conflict (Sold Out)
end
end
Swagger Documentation: http://localhost:8080/api/v1/swagger/index.html
POST /api/v1/signup: Register a new user.- Body:
{"name": "...", "email": "...", "password": "..."} - Default Role:
user - Response:
201 Createdwith user details
- Body:
POST /api/v1/login: Authenticate and receive JWT token.- Body:
{"email": "...", "password": "..."} - Response:
200 OKwith{"token": "..."}
- Body:
GET /api/v1/events: List all available events.- Response:
200 OKwith array of events
- Response:
POST /api/v1/orders/:id/status: Update order status (Webhook).- Headers:
X-Webhook-Secret: <secret>(if WEBHOOK_SECRET env var is set) - Body:
{"status": "PAID|REJECTED", "reason": "optional"} - Response:
204 No Content
- Headers:
All protected routes require a valid JWT token in the Authorization header:
Authorization: Bearer <your-jwt-token>
GET /api/v1/me: Get current user information.- Response:
200 OKwith user details (id, name, email, role)
- Response:
GET /api/v1/me/events: List events created by the authenticated user.- Response:
200 OKwith array of user's events
- Response:
POST /api/v1/orders: Purchase tickets for an event.- Body:
{"event_id": "uuid", "quantity": 2} - Response:
201 Createdwith order details - Note: Handles race conditions with database-level locking
- Body:
GET /api/v1/orders: List all orders for the authenticated user.- Response:
200 OKwith array of orders
- Response:
These routes require authentication AND the admin role:
POST /api/v1/events: Create a new event.- Body:
{ "name": "Rock in Rio", "location": "Rio de Janeiro", "organization": "Live Nation", "rating": "Livre", "date": "2025-10-10T00:00:00Z", "capacity": 100000, "price": 100.0, "image_url": "http://example.com/image.jpg", "description": "The biggest music festival in Latin America" } - Response:
201 Createdwith event details
- Body:
- Add edit user endpoint
- Add edit event endpoint
- Add event description field
- Add soft delete event endpoint
- Add event image upload endpoint // using cloud storage
- Add event check-in endpoint // using ticket id