As a Software Engineer with a background in traditional OOP languages and Clean Architecture principles, I created JonoMot to bridge the gap between SOLID principles and Go's idiomatic approach to software design.
After reading "100 Go Mistakes and How to Avoid Them," I recognized the need for a reference implementation that applies these lessons adhereing SOLID principal to create maintainable, testable, and scalable Go applications. JonoMot serves as this blueprint, demonstrating how to properly structure Go APIs while following both SOLID principles and Go-specific best practices.
JonoMot adheres to SOLID principles while respecting Golang's philosophy:
-
Single Responsibility Principle: Each package has a clear, focused responsibility:
internal/user
: Manages user-related functionalityinternal/poll
: Handles poll functionalitypkg/logger
: Centralized loggingpkg/response
: Standardized API responses
-
Open/Closed Principle: The codebase is designed for extension without modification:
- Service interfaces like
UserService
andPollService
can have multiple implementations - New features can be added via the tool in
cmd/tools/feature
- Service interfaces like
-
Liskov Substitution Principle: Interfaces are properly designed for substitution:
- Repository interfaces (
user.Repository
,poll.Repository
) can be swapped with test mocks database.Service
abstracts database operations
- Repository interfaces (
-
Interface Segregation Principle: Interfaces are client-specific and focused:
PollService
defines only methods required for poll operationsUserService
defines only methods needed for user functionality
-
Dependency Inversion Principle: Dependencies flow toward abstractions:
- Service constructors like
NewService
accept Repository interfaces RegisterRoutes
accepts service interfaces
- Service constructors like
JonoMot implements numerous best practices:
-
Code Organization:
- Proper package structure separating
cmd
,internal
, andpkg
directories - Clean separation between application layers (model, repository, service, routes)
- Proper package structure separating
-
Error Handling:
- Custom error handling with
pkg/error
- Centralized error responses via
response.ErrorBuilder
- Custom error handling with
-
Interfaces:
- Interfaces declared by consumers, not implementers
- Focused interfaces with minimal method sets
-
Database Handling:
- Connection pooling with configured MaxOpenConns/MaxIdleConns
- Proper context usage in database operations
- Parameterized SQL queries to prevent SQL injection
-
Testing:
- Comprehensive unit tests with mocks
- SQL mocking using sqlmock
- Integration tests for database operations
-
Concurrency:
- Graceful shutdown implementation
- Proper context usage for cancellation
-
Configuration:
- Environment-based configuration in
config
- Sensible defaults with overrides
- Environment-based configuration in
-
API Design:
- RESTful API with consistent response formats
- Structured logging via
pkg/logger
- Swagger documentation
- Go 1.19+
- PostgreSQL or Docker
- Make
- Clone the repository
- Copy
.env.example
to.env
and adjust values - Run database:
make docker-run
- Apply migrations:
make migrate-up
- Build and run:
make run
# Database Management
make migration create_poll # Create a new migration for "poll" feature
make migrate-up # Apply all pending migrations
make migrate-down # Rollback last migration
make docker-run # Start PostgreSQL container
make docker-down # Stop PostgreSQL container
# Development
make build # Build the application
make run # Run the application
make watch # Run with live reload
# Testing
make test # Run unit tests
make itest # Run integration tests
make all # Build and test
# Utilities
make clean # Clean build artifacts
Swagger documentation is available at docs
when the server is running.