data

package module
v0.2.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: Apache-2.0 Imports: 13 Imported by: 17

README

ncore/data

Production-grade data access layer for Go applications. Provides unified connection management, read/write splitting, transactions, health checking, and optional Space-level multi-tenant isolation for relational databases, caches, search engines, and message queues.

Installation

go get github.com/ncobase/ncore/data

Drivers are registered via blank import. Import only the backends your project uses:

import (
    _ "github.com/ncobase/ncore/data/postgres"     // PostgreSQL
    _ "github.com/ncobase/ncore/data/mysql"         // MySQL
    _ "github.com/ncobase/ncore/data/sqlite"        // SQLite
    _ "github.com/ncobase/ncore/data/redis"         // Redis
    _ "github.com/ncobase/ncore/data/mongodb"       // MongoDB
    _ "github.com/ncobase/ncore/data/elasticsearch" // Elasticsearch
    _ "github.com/ncobase/ncore/data/meilisearch"   // Meilisearch
    _ "github.com/ncobase/ncore/data/kafka"         // Kafka
    _ "github.com/ncobase/ncore/data/rabbitmq"      // RabbitMQ
)

Initialization

New returns a process-wide shared instance by default. Pass true to create an independent instance.

cfg := config.GetConfig(viper.GetViper())

d, cleanup, err := data.New(cfg)
if err != nil {
    log.Fatal(err)
}
defer cleanup()

Functional options are applied after construction:

data.WithMetricsCollector(myCollector)(d)
data.WithSpaceRouter(myRouter)(d)

For google/wire projects, use the provided provider set:

wire.Build(data.ProviderSet, ...)

Configuration

data:
  database:
    strategy: round_robin # round_robin | random | weight
    max_retry: 3
    master:
      driver: postgres
      source: "postgres://user:pass@host:5432/db?sslmode=disable"
      max_idle_conn: 5
      max_open_conn: 50
      max_life_time: 1h
      logging: false
    slaves:
      - driver: postgres
        source: "postgres://user:pass@replica:5432/db?sslmode=disable"
        weight: 2

  redis:
    addrs: ["localhost:6379"]
    password: ""
    db: 0

  mongodb:
    master:
      uri: "mongodb://localhost:27017/mydb"

  search:
    driver: meilisearch
    url: "http://localhost:7700"
    api_key: ""
    index_prefix: ""

  metrics:
    enabled: false
    batch_size: 100

  # Optional — omit to disable Space isolation.
  space:
    strategy: schema # schema (PostgreSQL) | database (MySQL)
    global_schema: public # default: "public"
    space_prefix: space_ # default: "space_"
    max_cached_pools: 100 # MySQL only, default: 100

Driver System

Drivers self-register in init() using a pattern identical to database/sql. The core data package contains no backend-specific code.

Available driver categories and their registration functions:

Category Registration function
Relational database RegisterDatabaseDriver(DatabaseDriver)
Cache RegisterCacheDriver(CacheDriver)
Search engine RegisterSearchDriver(SearchDriver)
Message queue RegisterMessageDriver(MessageDriver)
Object storage RegisterStorageDriver(StorageDriver)

Each driver implements the Name() string, Connect(ctx, cfg) (any, error), and Close(conn) error methods. DatabaseDriver and CacheDriver additionally implement Ping(ctx, conn) error.

To list all registered drivers at runtime:

drivers := data.ListRegisteredDrivers()
// map[string][]string{"database": ["postgres"], "cache": ["redis"], ...}

Database Access

master := d.GetMasterDB()          // *sql.DB — write node
slave, err := d.GetSlaveDB()       // *sql.DB — load-balanced read node
mgr := d.GetDBManager()            // *connection.DBManager — full manager

// Convenience aliases
master = d.DB()
slave, err = d.DBRead()

When no replicas are configured, GetSlaveDB falls back to the master.

Read/Write Splitting

DBManager routes writes to the master and reads to replicas using the strategy configured in data.database.strategy:

Strategy Behaviour
round_robin Cycles through replicas sequentially (default)
random Selects a replica uniformly at random
weight Distributes load proportionally by node weight
Other Backends
d.GetRedis()        // any — Redis client (cast to your client type)
d.GetMongoManager() // any — MongoDB manager
d.GetElasticsearch() // any
d.GetOpenSearch()    // any
d.GetMeilisearch()   // any

Transactions

All transaction methods store *sql.Tx in the context under ContextKeyTransaction. Retrieve it downstream with GetTx(ctx).

// Read-write transaction on master.
err := d.WithTx(ctx, func(ctx context.Context) error {
    tx, _ := data.GetTx(ctx) // *sql.Tx
    _, err := tx.ExecContext(ctx, "UPDATE ...")
    return err
})

// Read-only transaction on a replica.
err := d.WithTxRead(ctx, func(ctx context.Context) error {
    tx, _ := data.GetTx(ctx)
    // ...
    return nil
})

Space Isolation

Space isolation provides database-level separation for multi-tenant applications. Each Space operates in its own PostgreSQL schema or MySQL database, complementing application-layer permission checks.

The feature is opt-in. Projects that do not require multi-tenancy simply omit WithSpaceRouter; all Space-aware methods fall back to ordinary transactions automatically.

Architecture
HTTP middleware
  └── data.SetSpaceID(ctx, space.Slug)

Service / repository
  └── d.WithSpaceTx(ctx, fn)
        ├── [space ID present + router injected]
        │     └── SpaceRouter.WithSpace(ctx, spaceID, fn)
        │           └── ExecInTx: BEGIN → SET LOCAL … → fn → COMMIT
        └── [no space ID or no router]
              └── d.WithTx(ctx, fn)   // transparent fallback
Context helpers
// Middleware: attach the current space to the request context.
ctx = data.SetSpaceID(ctx, space.Slug)

// Repository: read the space ID (returns "", false when absent).
spaceID, ok := data.GetSpaceID(ctx)
Space-aware transactions
// Read-write transaction in the current space.
err := d.WithSpaceTx(ctx, func(ctx context.Context) error {
    tx, _ := data.GetTx(ctx) // *sql.Tx scoped to the space schema/database
    return repo.Create(ctx, entity)
})

// Read-only transaction in the current space.
err := d.WithSpaceTxRead(ctx, func(ctx context.Context) error {
    return repo.List(ctx, filters)
})
SpaceRouter interface

ncore provides no concrete implementation; each application supplies its own routing strategy:

type SpaceRouter interface {
    WithSpace(ctx context.Context, spaceID string, fn func(context.Context) error) error
    WithSpaceRead(ctx context.Context, spaceID string, fn func(context.Context) error) error
}

Inject the router after construction:

data.WithSpaceRouter(myRouter)(d)

Retrieve the injected router (e.g. for schema provisioning during Space creation):

router := d.GetSpaceRouter() // SpaceRouter or nil
ExecInTx

ExecInTx is the low-level helper for SpaceRouter implementors. It begins a transaction, executes an optional setup statement (e.g. SET LOCAL search_path), stores *sql.Tx in the context, runs the function, and commits or rolls back.

func ExecInTx(
    ctx      context.Context,
    db       *sql.DB,
    opts     *sql.TxOptions, // nil for read-write defaults
    setupSQL string,         // "" to skip; use SET LOCAL, never SET
    fn       func(context.Context) error,
) error
PostgreSQL — schema-per-space
func (r *PostgresSchemaRouter) WithSpace(ctx context.Context, spaceID string, fn func(context.Context) error) error {
    schema, err := data.BuildSchemaName(r.prefix, spaceID)
    if err != nil {
        return err
    }
    schemaIdent, err := data.QuotePostgresIdentifier(schema)
    if err != nil {
        return err
    }
    globalIdent, err := data.QuotePostgresIdentifier(r.globalSchema)
    if err != nil {
        return err
    }
    setup := fmt.Sprintf("SET LOCAL search_path TO %s, %s", schemaIdent, globalIdent)
    return data.ExecInTx(ctx, r.masterDB, nil, setup, fn)
}

func (r *PostgresSchemaRouter) WithSpaceRead(ctx context.Context, spaceID string, fn func(context.Context) error) error {
    schema, err := data.BuildSchemaName(r.prefix, spaceID)
    if err != nil {
        return err
    }
    schemaIdent, err := data.QuotePostgresIdentifier(schema)
    if err != nil {
        return err
    }
    globalIdent, err := data.QuotePostgresIdentifier(r.globalSchema)
    if err != nil {
        return err
    }
    db := r.slaveDB
    if db == nil {
        db = r.masterDB
    }
    setup := fmt.Sprintf("SET LOCAL search_path TO %s, %s", schemaIdent, globalIdent)
    return data.ExecInTx(ctx, db, &sql.TxOptions{ReadOnly: true}, setup, fn)
}

Space schema provisioning:

schemaIdent, err := data.QuotePostgresIdentifier(schema)
if err != nil {
    return err
}
_, err = masterDB.ExecContext(ctx, fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s", schemaIdent))

Critical: always use SET LOCAL (transaction-scoped), never SET (session-scoped). SET persists on the connection after the transaction ends and contaminates subsequent pool borrowers.

MySQL — database-per-space

Each Space uses its own *sql.DB connection pool. Maintain pools in a sync.Map or LRU map and evict stale entries to cap total pool count. No setup SQL is needed; the pool is already database-scoped.

func (r *MySQLDatabaseRouter) WithSpace(ctx context.Context, spaceID string, fn func(context.Context) error) error {
    db, err := r.getOrCreatePool(spaceID)
    if err != nil {
        return err
    }
    return data.ExecInTx(ctx, db, nil, "", fn)
}
Identifier security

Schema and database names cannot use SQL bind parameters. Always validate the space identifier before interpolating it into SQL:

// ValidateSpaceID enforces ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}$
if err := data.ValidateSpaceID(slug); err != nil {
    return err
}

// BuildSchemaName validates + normalises (lowercase, hyphens → underscores).
schema, err := data.BuildSchemaName("space_", slug)
// "Acme-Corp" → "space_acme_corp"

Never pass raw HTTP input as a space identifier. Resolve and validate it via an authoritative Space lookup that confirms the caller's membership.

Space isolation configuration

Helper methods on *config.Space:

cfg.Space.IsEnabled()              // true when Strategy != ""
cfg.Space.EffectiveGlobalSchema()  // "public" when unset
cfg.Space.EffectiveSpacePrefix()   // "space_" when unset
cfg.Space.EffectiveMaxCachedPools() // 100 when unset

Health Checks

Health probes every configured backend and returns a composite status map:

result := d.Health(ctx)
// map[string]any{
//   "status":    "healthy" | "degraded",
//   "timestamp": time.Time,
//   "services": map[string]any{
//     "database": {"healthy": true,  "response_ms": 2,    "error": ""},
//     "mongodb":  {"healthy": false, "response_ms": 0,    "error": "..."},
//     "rabbitmq": {"healthy": true,  "error": ""},
//     "kafka":    {"healthy": true,  "error": ""},
//     "search":   {"healthy": false, "error": "..."},
//   },
// }

Backends that are not configured are omitted from services and do not affect the overall status.

For lightweight connectivity checks use Ping:

err := d.Ping(ctx) // pings the relational database only

Metrics

Implement metrics.Collector and apply it as an option:

data.WithMetricsCollector(myCollector)(d)
// or, for the extension adapter:
data.WithExtensionCollector(myExtCollector)(d)

metrics.Collector interface:

type Collector interface {
    DBQuery(duration time.Duration, err error)
    DBTransaction(err error)
    DBConnections(count int)
    RedisCommand(command string, err error)
    RedisConnections(count int)
    MongoOperation(operation string, err error)
    SearchQuery(engine string, err error)
    SearchIndex(engine, operation string)
    MQPublish(system string, err error)
    MQConsume(system string, err error)
    HealthCheck(component string, healthy bool)
}

The built-in DataCollector accumulates counters in memory and supports pluggable Storage backends. Retrieve aggregated statistics:

stats := d.GetStats()
// map with sub-maps: "database", "redis", "mongodb", "search", "messaging", "health"

When metrics are not needed, the default NoOpCollector imposes zero overhead.

Ent ORM Mixins

The data/entgo/mixin sub-package provides reusable Ent schema mixins for projects that use Ent ORM.

Timestamp mixins:

import "github.com/ncobase/ncore/data/entgo/mixin"

func (MySchema) Mixin() []ent.Mixin {
    return []ent.Mixin{
        mixin.TimeAt{},    // created_at + updated_at (Unix milliseconds)
    }
}

Available timestamp fields: CreatedAt, UpdatedAt, DeletedAt, ExpiredAt, Expires, Released, StartedAt, CompletedAt.

Primary key mixins:

mixin.PrimaryKey                       // nanoid string primary key
mixin.PrimaryKeyAlias{...}            // named alias for a different storage key
mixin.CustomPrimaryKey{Length, DefaultFunc} // configurable key

Field mixins (via StringMixin, BoolMixin, IntMixin, etc.) provide typed, composable schema fields with configurable optionality, uniqueness, default values, and comments.

Lifecycle

// Close shuts down all connections and releases resources.
// Returns a slice of errors (one per failing backend).
errs := d.Close()

After Close, any method on the same *Data instance returns an error immediately. The cleanup function returned by New calls Close and is safe to call multiple times.

Documentation

Overview

Package data provides Space-level database isolation for multi-tenant applications.

Space Isolation Overview

Space isolation allows each logical space (tenant) to operate within its own database namespace, providing data separation at the database level rather than relying solely on application-layer checks.

The isolation is implemented through the SpaceRouter interface. ncore provides the plumbing and contract; the routing strategy is application-defined and injected via WithSpaceRouter.

Isolation Strategies

PostgreSQL – schema-per-space:

Each Space gets its own PostgreSQL schema. The search_path is set per-transaction
using SET LOCAL, which is safe with connection pools because the setting is
automatically reverted when the transaction ends.

  BEGIN;
  SET LOCAL search_path TO "space_acme", "public";
  -- all queries run in the space_acme schema
  COMMIT; -- connection returned to pool with original search_path restored

MySQL – database-per-space:

Each Space gets its own MySQL database. Application-managed per-space connection
pools are used, typically with LRU eviction to cap total pool count.

Security

Schema and database names are constructed from space identifiers. Because these identifiers cannot be parameterized via SQL placeholders, ValidateSpaceID enforces a strict allowlist regex to prevent injection attacks. Always call it before incorporating a space identifier into any SQL string.

Backward Compatibility

All existing APIs (Data.WithTx, Data.GetMasterDB, etc.) are unchanged. If no SpaceRouter is injected, Data.WithSpaceTx and Data.WithSpaceTxRead transparently fall back to the ordinary transaction methods.

Usage

// 1. Implement SpaceRouter in your application layer.
router := myapp.NewPostgresSchemaRouter(masterDB, slaveDB)

// 2. Initialize Data and inject the router.
d, cleanup, err := data.New(cfg)
data.WithSpaceRouter(router)(d)

// 3. Set space ID in HTTP middleware.
ctx = data.SetSpaceID(ctx, space.Slug)

// 4. Execute within the space namespace in service / repository.
err = d.WithSpaceTx(ctx, func(ctx context.Context) error {
    tx, _ := data.GetTx(ctx) // *sql.Tx scoped to the space's schema or database
    return repo.Create(ctx, entity)
})

Index

Constants

This section is empty.

Variables

View Source
var ProviderSet = wire.NewSet(ProvideData)

ProviderSet is the wire provider set for the data package. It provides *Data with a cleanup function that closes all connections.

Usage:

wire.Build(
    data.ProviderSet,
    // ... other providers
)

Functions

func BuildSchemaName added in v0.2.5

func BuildSchemaName(prefix, spaceID string) (string, error)

BuildSchemaName constructs a safe schema or database name by combining a validated prefix with a sanitized space identifier.

  • prefix must be a safe identifier component (e.g. "space_").
  • spaceID is validated via ValidateSpaceID, then lowercased, and hyphens are replaced with underscores to satisfy both PostgreSQL schema naming rules and MySQL database naming conventions.

Returns an error if prefix, spaceID, or the final name fails validation.

Example:

name, err := data.BuildSchemaName("space_", "Acme-Corp")
// name == "space_acme_corp"

func ExecInTx added in v0.2.5

func ExecInTx(ctx context.Context, db *sql.DB, opts *sql.TxOptions, setupSQL string, fn func(ctx context.Context) error) error

ExecInTx is a low-level helper intended for use inside SpaceRouter implementations. It:

  1. Begins a transaction on db using opts (pass nil for read-write defaults).
  2. If setupSQL is non-empty, executes it immediately after BEGIN. This is the hook for statements like: "SET LOCAL search_path TO space_acme, public"
  3. Stores the *sql.Tx in ctx under ContextKeyTransaction so that callers of fn can retrieve it with GetTx.
  4. Calls fn with the enriched context.
  5. Commits on success or rolls back on error.

The setupSQL string must NOT be constructed from untrusted user input. Always derive schema / database names through ValidateSpaceID first.

Example PostgreSQL schema router usage:

func (r *PostgresSchemaRouter) WithSpace(ctx context.Context, spaceID string, fn func(context.Context) error) error {
    schema, err := r.schemaName(spaceID) // internally calls ValidateSpaceID
    if err != nil {
        return err
    }
    schemaIdent, err := data.QuotePostgresIdentifier(schema)
    if err != nil {
        return err
    }
    globalIdent, err := data.QuotePostgresIdentifier(r.globalSchema)
    if err != nil {
        return err
    }
    setup := fmt.Sprintf("SET LOCAL search_path TO %s, %s", schemaIdent, globalIdent)
    return data.ExecInTx(ctx, r.masterDB, nil, setup, fn)
}

func GetSpaceID added in v0.2.5

func GetSpaceID(ctx context.Context) (string, bool)

GetSpaceID retrieves the Space identifier previously set by SetSpaceID. The boolean return value is false when no space ID is present or when it is the empty string.

func GetTx

func GetTx(ctx context.Context) (*sql.Tx, error)

GetTx retrieves transaction from context

func ListRegisteredDrivers added in v0.2.0

func ListRegisteredDrivers() map[string][]string

ListRegisteredDrivers returns a snapshot of all registered drivers. Useful for debugging and diagnostics.

func NewSearchClient added in v0.2.1

func NewSearchClient(d *Data, collector ...metrics.Collector) *search.Client

NewSearchClient creates a search client from ncore data layer. It automatically detects and creates adapters for available search engines.

Returns nil if no search engines are available. Applications should check if the returned client is nil to support optional search functionality.

func QuotePostgresIdentifier added in v0.2.5

func QuotePostgresIdentifier(identifier string) (string, error)

QuotePostgresIdentifier quotes a PostgreSQL identifier after validating it.

func RegisterCacheDriver added in v0.2.0

func RegisterCacheDriver(driver CacheDriver)

RegisterCacheDriver makes a cache driver available by the provided name. It follows the same pattern as RegisterDatabaseDriver.

func RegisterDatabaseDriver added in v0.2.0

func RegisterDatabaseDriver(driver DatabaseDriver)

RegisterDatabaseDriver makes a database driver available by the provided name. It is intended to be called from the init function in driver packages.

Example usage in a driver package:

func init() {
    data.RegisterDatabaseDriver(&postgresDriver{})
}

If RegisterDatabaseDriver is called twice with the same name or if driver is nil, it panics.

func RegisterMessageDriver added in v0.2.0

func RegisterMessageDriver(driver MessageDriver)

RegisterMessageDriver makes a message queue driver available by the provided name.

func RegisterSearchDriver added in v0.2.0

func RegisterSearchDriver(driver SearchDriver)

RegisterSearchDriver makes a search engine driver available by the provided name.

func RegisterStorageDriver added in v0.2.0

func RegisterStorageDriver(driver StorageDriver)

RegisterStorageDriver makes a storage driver available by the provided name.

func SetSpaceID added in v0.2.5

func SetSpaceID(ctx context.Context, id string) context.Context

SetSpaceID stores the Space identifier in ctx and returns the new context. It is typically called from an HTTP middleware after the space has been validated for the authenticated user.

ctx = data.SetSpaceID(r.Context(), space.Slug)

func ValidateIdentifier added in v0.2.5

func ValidateIdentifier(identifier string) error

ValidateIdentifier checks that an identifier component controlled by the application, such as a schema prefix or global schema name, is safe for SQL identifier use.

func ValidateSpaceID added in v0.2.5

func ValidateSpaceID(id string) error

ValidateSpaceID checks that id is safe for use as, or as a component of, a PostgreSQL schema name or MySQL database name.

Because schema / database names cannot be parameterized in SQL, they must be sanitized before string interpolation. ValidateSpaceID enforces the allowlist regex [a-zA-Z0-9][a-zA-Z0-9_-]{0,62}.

Callers should additionally convert the identifier to lowercase and replace hyphens with underscores before building the final schema or database name, since some databases treat these characters specially.

if err := data.ValidateSpaceID(slug); err != nil {
    return err
}
schema := "space_" + strings.ToLower(strings.ReplaceAll(slug, "-", "_"))

Types

type CacheDriver added in v0.2.0

type CacheDriver interface {
	// Name returns the driver identifier (e.g., "redis", "memcached")
	Name() string

	// Connect establishes a new cache connection.
	Connect(ctx context.Context, cfg any) (any, error)

	// Close terminates the cache connection.
	Close(conn any) error

	// Ping verifies the cache connection is alive.
	Ping(ctx context.Context, conn any) error
}

CacheDriver defines the interface for cache/key-value store drivers.

func GetCacheDriver added in v0.2.0

func GetCacheDriver(name string) (CacheDriver, error)

GetCacheDriver retrieves a registered cache driver by name.

type ContextKey

type ContextKey string
const ContextKeySpaceID ContextKey = "space_id"

ContextKeySpaceID is the context key used to store and retrieve the current Space identifier. Use SetSpaceID and GetSpaceID instead of reading this key directly.

const (
	ContextKeyTransaction ContextKey = "tx"
)

type Data

type Data struct {
	Conn *connection.Connections
	// contains filtered or unexported fields
}

func New

func New(cfg *config.Config, createNewInstance ...bool) (*Data, func(name ...string), error)

New creates new data layer

func ProvideData added in v0.2.0

func ProvideData(cfg *config.Config) (*Data, func(), error)

ProvideData initializes and returns the data layer with cleanup function. The cleanup function should be called when the application shuts down to properly close all database connections and release resources.

func (*Data) Close

func (d *Data) Close() []error

Close closes all data connections

func (*Data) ConsumeFromKafka

func (d *Data) ConsumeFromKafka(ctx context.Context, topic, groupID string, handler func([]byte) error) error

ConsumeFromKafka consumes messages from Kafka with metrics

func (*Data) ConsumeFromRabbitMQ

func (d *Data) ConsumeFromRabbitMQ(queue string, handler func([]byte) error) error

ConsumeFromRabbitMQ consumes messages from RabbitMQ with metrics

func (*Data) DB

func (d *Data) DB() *sql.DB

func (*Data) DBRead

func (d *Data) DBRead() (*sql.DB, error)

func (*Data) GetDBManager

func (d *Data) GetDBManager() *connection.DBManager

func (*Data) GetDatabaseNodes

func (d *Data) GetDatabaseNodes() (master *sql.DB, slaves []*sql.DB, err error)

GetDatabaseNodes returns information about all database nodes (master and slaves)

func (*Data) GetElasticsearch

func (d *Data) GetElasticsearch() any

func (*Data) GetMasterDB

func (d *Data) GetMasterDB() *sql.DB

func (*Data) GetMeilisearch

func (d *Data) GetMeilisearch() any

func (*Data) GetMetricsCollector

func (d *Data) GetMetricsCollector() metrics.Collector

GetMetricsCollector returns the metrics collector

func (*Data) GetMongoCollection

func (d *Data) GetMongoCollection(dbName, collName string, readOnly bool) (any, error)

func (*Data) GetMongoDatabase

func (d *Data) GetMongoDatabase(name string, readOnly bool) (any, error)

func (*Data) GetMongoManager

func (d *Data) GetMongoManager() any

func (*Data) GetOpenSearch

func (d *Data) GetOpenSearch() any

func (*Data) GetRedis

func (d *Data) GetRedis() any

func (*Data) GetSlaveDB

func (d *Data) GetSlaveDB() (*sql.DB, error)

func (*Data) GetSpaceRouter added in v0.2.5

func (d *Data) GetSpaceRouter() SpaceRouter

GetSpaceRouter returns the injected SpaceRouter, or nil if none was set. Useful for applications that need direct access to the router for lifecycle operations (e.g. schema provisioning).

func (*Data) GetStats

func (d *Data) GetStats() map[string]any

GetStats returns data layer statistics

func (*Data) Health

func (d *Data) Health(ctx context.Context) map[string]any

Health checks all components with comprehensive metrics collection

func (*Data) IsMessagingAvailable

func (d *Data) IsMessagingAvailable() bool

IsMessagingAvailable checks if any messaging (queue or memory) is available Deprecated: Use IsMessagingEnabled() and IsQueueAvailable() separately

func (*Data) IsMessagingEnabled

func (d *Data) IsMessagingEnabled() bool

IsMessagingEnabled checks if messaging services

func (*Data) IsQueueAvailable

func (d *Data) IsQueueAvailable() bool

IsQueueAvailable checks if external message queues are available

func (*Data) IsReadOnlyMode

func (d *Data) IsReadOnlyMode(ctx context.Context) bool

IsReadOnlyMode checks if the system is in read-only mode (only slaves available)

func (*Data) MongoHealthCheck

func (d *Data) MongoHealthCheck(ctx context.Context) error

func (*Data) Ping

func (d *Data) Ping(ctx context.Context) error

Ping checks all database connections

func (*Data) PublishToKafka

func (d *Data) PublishToKafka(ctx context.Context, topic string, key, value []byte) error

PublishToKafka publishes message to Kafka with metrics

func (*Data) PublishToRabbitMQ

func (d *Data) PublishToRabbitMQ(exchange, routingKey string, body []byte) error

PublishToRabbitMQ publishes message to RabbitMQ with metrics

func (*Data) ShouldUseMemoryFallback

func (d *Data) ShouldUseMemoryFallback() bool

ShouldUseMemoryFallback checks if should fallback to memory when queue unavailable

func (*Data) WithMongoTransaction

func (d *Data) WithMongoTransaction(ctx context.Context, fn func(any) error) error

func (*Data) WithSpaceTx added in v0.2.5

func (d *Data) WithSpaceTx(ctx context.Context, fn func(ctx context.Context) error) error

WithSpaceTx executes fn inside a read-write transaction scoped to the Space identified in ctx.

Routing behaviour:

  • ctx contains a Space ID AND a SpaceRouter has been injected → delegates to SpaceRouter.WithSpace; the transaction operates in the Space's dedicated schema or database.
  • Otherwise → falls back to Data.WithTx (identical to the pre-isolation behaviour, preserving backward compatibility).

The *sql.Tx is available inside fn via GetTx regardless of which path is taken.

func (*Data) WithSpaceTxRead added in v0.2.5

func (d *Data) WithSpaceTxRead(ctx context.Context, fn func(ctx context.Context) error) error

WithSpaceTxRead executes fn inside a read-only transaction scoped to the Space identified in ctx. It follows the same routing logic as [WithSpaceTx] but uses SpaceRouter.WithSpaceRead and falls back to Data.WithTxRead.

func (*Data) WithTx

func (d *Data) WithTx(ctx context.Context, fn func(ctx context.Context) error) error

WithTx wraps function within transaction

func (*Data) WithTxRead

func (d *Data) WithTxRead(ctx context.Context, fn func(ctx context.Context) error) error

WithTxRead wraps function within read-only transaction

type DatabaseDriver added in v0.2.0

type DatabaseDriver interface {
	// Name returns the driver identifier (e.g., "postgres", "mysql", "sqlite")
	Name() string

	// Connect establishes a new database connection using the provided configuration.
	// The returned connection should be ready for use or return an error.
	Connect(ctx context.Context, cfg any) (any, error)

	// Close terminates the database connection and releases resources.
	Close(conn any) error

	// Ping verifies the connection is alive and functional.
	Ping(ctx context.Context, conn any) error
}

DatabaseDriver defines the interface for relational database drivers. Implementations should handle connection lifecycle and health checks.

func GetDatabaseDriver added in v0.2.0

func GetDatabaseDriver(name string) (DatabaseDriver, error)

GetDatabaseDriver retrieves a registered database driver by name. It returns an error with helpful instructions if the driver is not found.

type MessageDriver added in v0.2.0

type MessageDriver interface {
	// Name returns the driver identifier (e.g., "kafka", "rabbitmq")
	Name() string

	// Connect establishes a new message broker connection.
	Connect(ctx context.Context, cfg any) (any, error)

	// Close terminates the message broker connection.
	Close(conn any) error
}

MessageDriver defines the interface for message queue/broker drivers.

func GetMessageDriver added in v0.2.0

func GetMessageDriver(name string) (MessageDriver, error)

GetMessageDriver retrieves a registered message queue driver by name.

type Option

type Option func(*Data)

func WithExtensionCollector

func WithExtensionCollector(collector metrics.ExtensionCollector) Option

func WithIndexPrefix

func WithIndexPrefix(prefix string) Option

func WithMetricsCollector

func WithMetricsCollector(collector metrics.Collector) Option

func WithSearchConfig

func WithSearchConfig(searchConfig *config.Search) Option

func WithSpaceRouter added in v0.2.5

func WithSpaceRouter(router SpaceRouter) Option

WithSpaceRouter returns an Option that injects a SpaceRouter into Data. Apply it after constructing Data for projects that require Space-level isolation:

d, cleanup, err := data.New(cfg)
data.WithSpaceRouter(myRouter)(d)

Projects that do not need multi-tenancy simply omit this option; all Space-aware methods fall back to ordinary transactions automatically.

type SearchCollectorAdapter added in v0.2.1

type SearchCollectorAdapter struct {
	// contains filtered or unexported fields
}

SearchCollectorAdapter adapts data/metrics.Collector to data/search.Collector

func (*SearchCollectorAdapter) SearchIndex added in v0.2.1

func (a *SearchCollectorAdapter) SearchIndex(engine, operation string)

SearchIndex records search index operation metrics

func (*SearchCollectorAdapter) SearchQuery added in v0.2.1

func (a *SearchCollectorAdapter) SearchQuery(engine string, err error)

SearchQuery records search query metrics

type SearchDriver added in v0.2.0

type SearchDriver interface {
	// Name returns the driver identifier (e.g., "elasticsearch", "meilisearch")
	Name() string

	// Connect establishes a new search engine connection.
	Connect(ctx context.Context, cfg any) (any, error)

	// Close terminates the search engine connection.
	Close(conn any) error
}

SearchDriver defines the interface for search engine drivers.

func GetSearchDriver added in v0.2.0

func GetSearchDriver(name string) (SearchDriver, error)

GetSearchDriver retrieves a registered search engine driver by name.

type SearchEngine added in v0.2.0

type SearchEngine interface {
	// Health checks if the search engine is available and responds
	Health(ctx context.Context) error

	// IndexDocument indexes a single document
	IndexDocument(ctx context.Context, index, docID string, document any) error

	// DeleteDocument deletes a document by ID
	DeleteDocument(ctx context.Context, index, docID string) error

	// IndexExists checks if an index exists
	IndexExists(ctx context.Context, index string) (bool, error)

	// CreateIndex creates a new index with optional settings
	CreateIndex(ctx context.Context, index, settings string) error
}

SearchEngine defines the interface that all search engine client implementations must satisfy. This allows the search.Client to work with any search backend through type assertions.

type SpaceRouter added in v0.2.5

type SpaceRouter interface {
	// WithSpace executes fn inside a read-write transaction scoped to the
	// namespace of the given space. spaceID is the application-defined
	// identifier (e.g. a slug or UUID) that the router maps to a schema or
	// database name.
	WithSpace(ctx context.Context, spaceID string, fn func(ctx context.Context) error) error

	// WithSpaceRead executes fn inside a read-only transaction scoped to the
	// namespace of the given space. Implementations may direct the query to a
	// read replica for this variant.
	WithSpaceRead(ctx context.Context, spaceID string, fn func(ctx context.Context) error) error
}

SpaceRouter is the core abstraction for Space-level database routing.

Application code implements this interface and injects it into Data via WithSpaceRouter. ncore ships no concrete implementation so that no extra dependencies are imposed on projects that do not need multi-tenancy.

Implementation contract

  • Space context MUST be scoped to the transaction (e.g. SET LOCAL in PostgreSQL). Never use a session-level SET, which would leak the context to the next caller that borrows the same connection from the pool.
  • The context passed to fn MUST carry the active *sql.Tx so that callers can retrieve it with GetTx and integrate it with their ORM.
  • The implementation owns the full Begin / Commit / Rollback lifecycle of the transaction.

See the package-level ExecInTx helper, which correctly implements this contract and can be composed into custom router implementations.

type StorageDriver added in v0.2.0

type StorageDriver interface {
	// Name returns the driver identifier (e.g., "s3", "minio", "local")
	Name() string

	// Connect establishes a new storage connection.
	Connect(ctx context.Context, cfg any) (any, error)

	// Close terminates the storage connection.
	Close(conn any) error
}

StorageDriver defines the interface for object storage drivers.

func GetStorageDriver added in v0.2.0

func GetStorageDriver(name string) (StorageDriver, error)

GetStorageDriver retrieves a registered storage driver by name.

Directories

Path Synopsis
all module
cache module
entgo module
kafka module
meilisearch module
mongodb module
mysql module
neo4j module
opensearch module
postgres module
rabbitmq module
redis module
sqlite module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL