Storage
The persistence layer -- in-memory, SQLite, and PostgreSQL store implementations.
Ctrl Plane separates business logic from persistence through store interfaces. Each subsystem defines its own store interface, and the aggregate store.Store composes them all into a single type.
Aggregate store
The store.Store interface is the composition of every subsystem's store:
type Store interface {
instance.Store
deploy.Store
health.Store
telemetry.Store
network.Store
secrets.Store
admin.Store
Migrate(ctx context.Context) error
Ping(ctx context.Context) error
Close() error
}Pass a Store implementation to app.WithStore() when constructing Ctrl Plane.
Implementations
In-memory
The store/memory package provides a store backed by Go maps and sync.RWMutex. It requires no external dependencies and is ideal for unit tests and local development.
import "github.com/xraph/ctrlplane/store/memory"
s := memory.New()Data is lost when the process exits.
SQLite
The store/sqlite package stores data in a local SQLite file. Good for standalone deployments and development environments where you want persistence without running a database server.
import "github.com/xraph/ctrlplane/store/sqlite"
s, err := sqlite.New(sqlite.Config{
Path: "/var/data/ctrlplane.db",
})PostgreSQL
The store/postgres package is the production-grade backend. It supports all features including concurrent access, proper transaction isolation, and efficient pagination.
import "github.com/xraph/ctrlplane/store/postgres"
s, err := postgres.New(postgres.Config{
URL: "postgres://user:pass@localhost:5432/ctrlplane?sslmode=disable",
})Subsystem store interfaces
Each subsystem defines the store methods it needs. Here's the instance store as an example:
// instance/store.go
type Store interface {
Insert(ctx context.Context, instance *Instance) error
GetByID(ctx context.Context, tenantID string, id id.ID) (*Instance, error)
GetBySlug(ctx context.Context, tenantID string, slug string) (*Instance, error)
List(ctx context.Context, tenantID string, opts ListOptions) (*ListResult, error)
Update(ctx context.Context, instance *Instance) error
Delete(ctx context.Context, tenantID string, id id.ID) error
CountByTenant(ctx context.Context, tenantID string) (int, error)
}Every query method requires a tenantID parameter. There are no methods that operate across tenants.
Migrations
Call store.Migrate(ctx) to create or update the database schema:
if err := s.Migrate(ctx); err != nil {
log.Fatalf("migration failed: %v", err)
}The PostgreSQL and SQLite implementations handle schema creation automatically. The in-memory store's Migrate is a no-op.
Choosing a store
| Store | Use case | Persistence | Concurrency |
|---|---|---|---|
| Memory | Unit tests, quick prototyping | None (in-process) | Single process |
| SQLite | Standalone deployments, development | File-based | Single process |
| PostgreSQL | Production multi-instance deployments | Full ACID | Multi-process |