A Go library that enhances sql.DB for building SQL-backed CRUD microservices with the Microbus framework.
- Connection Pool Management - Prevents database exhaustion in multi-microservice solutions
- Schema Migration - Concurrency-safe, incremental database migrations
- Ephemeral Test Databases - Isolated databases per test with automatic cleanup
- Code Generation - Generate complete CRUD microservices from minimal configuration
- Multi-Tenant Architecture - Built-in tenant isolation via discriminator columns
- AI Agent Integration - Rules and skills for Claude Code and other coding agents
- Cross-Driver Support - MySQL, PostgreSQL, and SQL Server with unified API
import "github.com/microbus-io/sequel"
// Open a database connection
db, err := sequel.Open("", "root:root@tcp(127.0.0.1:3306)/mydb")
// Run migrations
err = db.Migrate("myservice@v1", migrationFilesFS)
// Use db.DB for standard sql.DB operations
rows, err := db.Query("SELECT * FROM users WHERE tenant_id=?", tenantID)When many microservices connect to the same database, connection exhaustion becomes a concern. Sequel limits the connection pool of a single executable based on client count using a sqrt-based formula:
maxIdle ≈ sqrt(N)where N is the number of clientsmaxOpen ≈ (sqrt(N) * 2) + 2
This prevents overwhelming the database while maintaining reasonable throughput.
Sequel performs incremental schema migration using numbered SQL files (1.sql, 2.sql, etc.). Migrations are:
- Concurrency-safe - Distributed locking ensures only one replica executes each migration
- Tracked - A
sequel_migrationstable records completed migrations - Driver-aware - Use
-- DRIVER: drivernamecomments for driver-specific SQL
// Embed migration files
//go:embed sql/*.sql
var migrationFS embed.FS
// Run migrations (safe to call from multiple replicas)
err := db.Migrate("unique-sequence-name", migrationFS)Example migration file with driver-specific syntax:
-- DRIVER: mysql
ALTER TABLE users MODIFY COLUMN email VARCHAR(384) NOT NULL;
-- DRIVER: pgx
ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(384);
-- DRIVER: mssql
ALTER TABLE users ALTER COLUMN email NVARCHAR(384) NOT NULL;OpenTesting creates unique databases per test, providing isolation from other tests:
func TestUserService(t *testing.T) {
// Creates database: testing_{hour}_mydb_{testID}
db, err := sequel.OpenTesting("", "root:root@tcp(127.0.0.1:3306)/mydb", t.Name())
// Database is deleted when closed
db.Close()
}Configure database credentials in config.local.yaml at the project root:
all:
SQLDataSourceName: root:root@tcp(127.0.0.1:3306)/mydbSequel's code generator produces complete CRUD microservices from minimal configuration.
Add the generator directive to your project's doc.go:
//go:generate go run github.com/microbus-io/fabric/codegen
//go:generate go run github.com/microbus-io/sequel/codegenRun go generate at the root of the project.
- Create a microservice directory with
doc.go:
//go:generate go run github.com/microbus-io/fabric/codegen
//go:generate go run github.com/microbus-io/sequel/codegen- Run
go generate- createsservice.yamlwith SQL configuration:
sql:
table: book # snake_case database table
object: Book # PascalCase Go type- Run
go generateagain - generates complete implementation:bookapi/object.go- Domain object with validationbookapi/objectkey.go- Encrypted key handlingbookapi/query.go- Query filtering optionsresources/sql/1.sql- Initial schema migrationservice.go- CRUD endpoint implementationsservice_test.go- Integration tests
| Endpoint | Description |
|---|---|
Create / BulkCreate |
Insert new objects |
Store / BulkStore |
Update existing objects |
Revise / BulkRevise |
Update with optimistic locking |
Delete / BulkDelete |
Delete objects |
Load / BulkLoad |
Fetch by key |
List |
Query with filtering and pagination |
Lookup / MustLookup |
Single object queries |
Purge |
Batch delete by query |
Count |
Count matching objects |
After generation, customize your microservice:
- Add fields to
object.goand corresponding columns in a new migration script - Implement column mappings - Follow the
HINTcomments inservice.go:mapColumnsOnInsert()- Maps fields for INSERTmapColumnsOnUpdate()- Maps fields for UPDATEmapColumnsOnSelect()- Maps columns to object fields on SELECTprepareWhereClauses()- Builds WHERE clauses from Query
- Add query filters to
query.gofor custom search capabilities - Enhance tests in
service_test.go
Generated microservices include built-in multi-tenant isolation:
- Every table includes a
tenant_iddiscriminator column - All SQL statements (INSERT, UPDATE, DELETE, SELECT) include tenant filtering
- Composite primary keys start with
tenant_idfor data locality - All indexes are prefixed with
tenant_id
The tenant ID is extracted from the actor's JWT claims (tenant or tid). Solutions without multi-tenancy can ignore this; the tenant defaults to 0.
Sequel provides utilities for working with nullable columns:
// Writing: Convert zero values to NULL
columnMapping := map[string]any{
"nickname": sequel.Nullify(user.Nickname), // "" becomes NULL
}
// Reading: Convert NULL to zero values
columnMapping := map[string]any{
"nickname": sequel.Nullable(&user.Nickname), // NULL becomes ""
}
// Custom binding for complex transformations
columnMapping := map[string]any{
"tags": sequel.Bind(func(jsonStr string) error {
return json.Unmarshal([]byte(jsonStr), &obj.Tags)
}),
}Sequel includes rules and skills for AI coding agents like Claude Code.
| Skill | Description |
|---|---|
sequel/add-microservice |
Create a new CRUD microservice |
sequel/add-fields |
Add columns to object and schema |
sequel/chg-fields |
Modify existing field definitions |
sequel/rm-fields |
Remove fields from object |
sequel/rename-object |
Rename object and update references |
sequel/rename-table |
Rename database table |
Create a new microservice:
Create a new microservice to persist books in a SQL database
Add fields:
For the @book/ microservice, add the following fields: Title, Author, ISBN (unique)
Skip tests and documentation:
Create a new microservice to persist car in a SQL database. Be quick about it!
import "github.com/microbus-io/sequel"
type Service struct {
*intermediate.Intermediate
db *sequel.DB
}
func (svc *Service) OnStartup(ctx context.Context) (err error) {
if svc.Deployment() == connector.TESTING {
svc.db, err = sequel.OpenTesting("", svc.SQLDataSourceName(), svc.Plane())
} else {
svc.db, err = sequel.Open("", svc.SQLDataSourceName())
}
if err != nil {
return errors.Trace(err)
}
sqlFS, _ := fs.Sub(svc.ResFS(), "sql")
err = svc.db.Migrate("myservice@v1", sqlFS)
return errors.Trace(err)
}
func (svc *Service) OnShutdown(ctx context.Context) (err error) {
if svc.db != nil {
svc.db.Close()
}
return nil
}| Database | Driver | Data Source Name |
|---|---|---|
| MySQL | mysql |
root:root@tcp(127.0.0.1:3306)/db |
| PostgreSQL | pgx |
postgres://postgres:postgres@127.0.0.1:5432/db |
| SQL Server | mssql |
sqlserver://sa:sa@127.0.0.1:1433?database=db |
The driver is automatically inferred from the data source name format.
// Convert ? placeholders to $1, $2 for PostgreSQL
stmt := db.ConformArgPlaceholders("SELECT * FROM users WHERE id=? AND name=?")
// Get driver-specific current UTC time function
stmt := fmt.Sprintf("UPDATE users SET updated_at=%s WHERE id=?", db.NowUTC())
// Generate cross-driver REGEXP search
stmt := db.RegexpTextSearch("col1", "col2", "col3")Sequel is the copyrighted work of various contributors. It is licensed to you free of charge by Microbus LLC - a Delaware limited liability company formed to hold rights to the combined intellectual property of all contributors - under the Apache License 2.0.