A todo application built with hexagonal architecture, gRPC, and grpc-gateway.
- Preview
- Key Technologies
- System Architecture
- Hexagonal Architecture
- Quick Start
- Feature Addition Workflow
- Dev Commands
- Core: Go 1.22+, strict Hexagonal Architecture.
- Interfaces: gRPC & HTTP (via grpc-gateway), Buf schema definitions.
- Data & Caching: PostgreSQL (pgx), Memcached, Redis (go-redis).
- Auth & Security: PASETO v2, Google OAuth2 integration.
- Background Jobs: Asynchronous resilient queues via asynq.
- Infrastructure: Docker, Docker Compose, optimized multi-stage Distroless containers.
- Observability: Wide-event structured logging (slog).
The application is heavily decoupled into three highly-optimized discrete runtime binaries deployed independently via Docker Compose:
- server: The primary gRPC & HTTP REST boundary. Handles User flows, Auth, and immediate Todo mutations.
- scheduler: A lightweight SQL poller. Identifies upcoming deadlines (e.g., Todos due in <24 hours) and instantly routes queue payloads to Redis.
- worker: The Asynq driving adapter. A background processor handling heavy tasks safely (e.g., bouncing SMTP requests using Mailpit, sending HTML email with exponential backoff).
This project strictly follows the Hexagonal Architecture pattern to decouple core business logic from external concerns like databases, transport layers, and third-party services.
- Domain Layer (
internal/domain): The "heart" of the application. Contains pure business logic, entities (e.g.,Todo), and value objects. It has zero dependencies on any other layer or external framework. - Application Layer (
internal/application): Implements specific use cases (e.g.,CreateTodo,Login). It orchestrates domain objects and coordinates the flow of data. - Ports (
internal/port): Defines the boundaries via interfaces.- Input Ports: Interfaces that the application exposes to the outside world (e.g.,
AuthUseCase). - Output Ports: Interfaces that the application needs from the outside world (e.g.,
UserRepository,EmailSender).
- Input Ports: Interfaces that the application exposes to the outside world (e.g.,
- Adapters (
internal/adapter):- Driving Adapters (Input): Handlers that "drive" the application, such as gRPC/REST servers (
internal/adapter/driving/grpc) or background task processors. - Driven Adapters (Output): Implementations of the output ports that interact with external infrastructure like PostgreSQL, Redis, or SMTP.
- Driving Adapters (Input): Handlers that "drive" the application, such as gRPC/REST servers (
This decoupled structure ensures that core business rules remain testable, maintainable, and independent of infrastructure changes.
Everything operates smoothly inside Docker. No local dependencies required except Docker and make.
# 1. Clone the repository
git clone https://github.com/SemmiDev/go-todo-app.git
cd go-todo-app
# 2. Set up your environment variables
# Note: You MUST update .env with your own Google OAuth client credentials for login to work!
cp .env.example .env
# 3. Start the entire infrastructure (Postgres, Redis, Memcached, Mailpit, + 3 Binaries)
make docker-up
# 4. Access the different services
# Frontend Application: http://localhost:8080
# Backend API Gateway: http://localhost:8080/v1/...
# Mailpit (Email Logs): http://localhost:8025
# 5. View streaming logs
docker compose logs -fFollow this standard pipeline to introduce new domain features:
-
Database Migrations Create a new migration file to represent your domain schema. Create new queries within your data layer. Ensure
make migrate-upruns cleanly. -
Protobuf Definitions Define your services and messages in
api/proto/v1. Runmake buf-generateto predictably map your proto logic into Go stubs. -
Core Domain Layer Update Add logic strictly scoped to the pure
internal/application/bounds. Do not leak DB or HTTP frameworks here. -
Dependency Injection & Ports Plug the logic into your hexagonal HTTP or gRPC handlers within
internal/adapter/driving/grpc/. Add background payloads tointernal/adapter/driving/worker/handlers if processing occurs offline. -
Verification Deploy locally via
make run-serverormake docker-upto inspect.
The system leverages local sandboxing for development:
make db-up # Spin up only the DB infra
make migrate-up # Run database schemas
make run-server # Run the hot-code local API server
make run-worker # Run the hot-code local queue processor
make run-scheduler # Run the hot-code local polling cron

