GNdb follows Clean Architecture principles with clear separation between business logic, use cases, and implementation details.
- Dependency Rule: Dependencies point inward - outer layers depend on inner layers, never the reverse
- Interface Segregation: Small, focused interfaces for each business operation
- Separation of Concerns: Business logic is independent of frameworks, databases, and I/O
cmd/ Frameworks & Drivers (CLI, entry point)
↓ depends on
internal/ Interface Adapters (implementations)
├── iopopulate/ - Populator implementation
├── ioschema/ - SchemaManager implementation
├── iooptimize/ - Optimizer implementation
├── iodb/ - Database adapter (pgx)
└── iofs/ - File system adapter
↓ depends on
pkg/gndb/ Use Cases (core operations)
├── SchemaManager - Database schema operations
├── Populator - SFGA data import
└── Optimizer - Performance optimizations
↓ depends on
pkg/ Entities (domain models)
├── schema/ - Database entities (DataSource, NameString, etc.)
├── sources/ - Source configuration entities
├── config/ - Application configuration
└── db/ - Database gateway interface
config/- Application configuration with Option patterngndb/- Core use case interfaces and version infoschema/- Database models (GORM entities for scientific names)sources/- SFGA source configuration and validationdb/- Database operator interface (connection, basic operations)errcode/- Error codes for programmatic handling
iopopulate/- Implements Populator (reads SFGA, bulk inserts)ioschema/- Implements SchemaManager (wraps GORM AutoMigrate)iooptimize/- Implements Optimizer (creates indexes, views)iodb/- Implements database Operator (pgx connection pooling)iofs/- File system operations (config directories)iologger/- Logging setup
- Cobra/Viper framework integration
- Command definitions (schema, populate, optimize)
- Flag parsing and configuration initialization
All core operations defined as interfaces in pkg/gndb/:
type SchemaManager interface {
Create(ctx, cfg) error
Migrate(ctx, cfg) error
}
type Populator interface {
Populate(ctx) error
}
type Optimizer interface {
Optimize(ctx, cfg) error
}Implementations live in internal/ with private structs, public constructors:
// internal/ioschema/manager.go
type manager struct { /* private */ }
func NewManager(op db.Operator) gndb.SchemaManager {
return &manager{operator: op}
}Config is immutable after creation - all mutations via Option functions:
cfg := config.New() // Always valid
cfg.Update(config.OptDatabaseHost("localhost")) // Safe mutationpkg/sources/- Pure validation (data structure checks)internal/iopopulate/sources.go- I/O validation (file existence)
- Testability - Business logic tested without I/O or frameworks
- Maintainability - Clear boundaries, one-way dependencies
- Flexibility - Swap implementations (GORM to sqlc, pgx to database/sql)
- Portability - Business logic independent of CLI, can add HTTP API
- Screaming Architecture - Package names reveal domain, not technical patterns
CLI Command (cmd/populate.go)
↓
Creates Populator (internal/iopopulate/populator.go)
↓
Calls Populate() via gndb.Populator interface
↓
Uses entities (pkg/sources/, pkg/schema/)
↓
Writes to database via db.Operator interface
↓
pgx implementation (internal/iodb/pgx_operator.go)
Dependencies always point inward: cmd → internal → pkg/gndb → entities.
gndb/
├── cmd/ # CLI commands (Cobra)
├── pkg/ # Public API (importable)
│ ├── gndb/ # Core interfaces + version
│ ├── schema/ # Database models
│ ├── sources/ # Source configuration
│ ├── config/ # Configuration
│ ├── db/ # Database interface
│ └── errcode/ # Error codes
├── internal/ # Private implementation
│ ├── iopopulate/ # Population implementation
│ ├── ioschema/ # Schema implementation
│ ├── iooptimize/ # Optimization implementation
│ ├── iodb/ # Database implementation
│ ├── iofs/ # File system operations
│ └── iologger/ # Logging setup
└── main.go # Entry point
- Clean Architecture by Robert C. Martin
- Project layout standards