Kyrux
Voltar

NoSQL

🧪 Experimental — parte da camada core (kyrux/core), rotulada "experimental" pelo próprio USE.md do framework. Convive com o bootstrap/fw.DB tradicional (SQL) sem conflito — é uma adição, não uma substituição — mas a API pode mudar entre versões sem aviso.

Cinco bancos NoSQL têm client próprio em core/nosql/<banco>: MongoDB, Redis (uso geral — ver nota abaixo), Cassandra, Elasticsearch e DynamoDB.

Por que não tem um orm.Query[T] único pra todos

O ORM SQL (core/orm) tem uma API fluente única (Where/OrderBy/Limit) porque bancos relacionais compartilham o mesmo modelo de dados. NoSQL não — um filtro Mongo, uma query CQL do Cassandra e uma Query DSL do Elasticsearch não têm nada em comum de verdade. Forçar todos atrás da mesma interface seria fingir uma portabilidade que não existe. Cada client abaixo tem sua própria API, no formato nativo daquele banco — os generics do Go aparecem só pra dar um handle tipado (Coll[T], Index[T], Table[T]), não pra fingir uma linguagem de query universal.

Nenhum é lido do .env

Diferente do SQL (DB_NAME/DB_DRIVER/DB_DSN lidos automaticamente pelo bootstrap), nenhum client NoSQL lê variável de ambiente sozinho — você mesmo lê (environment.Get("MONGO_URI")) e passa pro construtor. De propósito: a maioria dos projetos não usa NoSQL nenhum, e o framework não força um esquema de configuração pra bancos que a maioria nunca vai tocar.

# MongoDB (core/nosql/mongo) — NÃO lido pelo framework.
# MONGO_URI=mongodb://localhost:27017
# MONGO_DATABASE=meubanco

Sem migrations, sem CLI — schema é responsabilidade sua (schemaless no Mongo/Elasticsearch/DynamoDB, CQL manual no Cassandra).

MongoDB — core/nosql/mongo

Model = struct Go comum com tags bson:"..." (convenção padrão do driver oficial, não uma tag kyrux:"...").

import "kyrux/core/nosql/mongo"

client, err := mongo.New(uri, "meubanco") // Connect + Ping (10s)
defer client.Close(ctx)

type Post struct {
    ID    string `bson:"_id,omitempty"`
    Titulo string `bson:"titulo"`
}

posts := mongo.CollectionOf[Post](client, "posts")

err = posts.InsertOne(ctx, &Post{Titulo: "Olá"})
lista, err := posts.Find(ctx, mongo.M{"titulo": "Olá"})
um, err := posts.FindOne(ctx, mongo.M{"_id": id}) // mongo.ErrNoDocuments se não achar
n, err := posts.UpdateOne(ctx, mongo.M{"_id": id}, mongo.M{"$set": mongo.M{"titulo": "Novo"}})
n, err = posts.DeleteOne(ctx, mongo.M{"_id": id})
total, err := posts.Count(ctx, mongo.M{})

mongo.M é alias de bson.M (map[string]any) — filtros e updates no formato nativo do MongoDB ($set, $inc, etc.). client.Raw() devolve o *mongo.Client original do driver pra qualquer coisa fora da API tipada.

Redis — core/nosql/redis

Client Redis de uso geral — diferente do core/cache (só k/v+TTL) e do core/queue (só fila), esse expõe as estruturas de dados de verdade do Redis: strings, hashes, listas, sets, sorted sets e pub/sub.

import "kyrux/core/nosql/redis"

client, err := redis.New(addr, password, 0) // PING antes de retornar

client.Set(ctx, "chave", "valor", time.Hour)
client.SetJSON(ctx, "obj", meuStruct, time.Hour)
client.HSet(ctx, "hash", "campo", "valor")
client.LPush(ctx, "fila", "item")
client.SAdd(ctx, "set", "membro")
client.ZAdd(ctx, "ranking", redis.ZMember{Score: 10, Member: "jogador1"})

ps := client.Subscribe(ctx, "canal")
for msg := range ps.Channel() {
    fmt.Println(msg.Payload)
}

redis.ErrNil é o go-redis.Nil reexportado (chave inexistente). client.Raw() devolve o *redis.Client original.

Cassandra — core/nosql/cassandra

import "kyrux/core/nosql/cassandra"

client, err := cassandra.New([]string{"127.0.0.1"}, "meu_keyspace")

err = client.Exec(ctx, "INSERT INTO posts (id, titulo) VALUES (?, ?)", id, "Olá")
linhas, err := client.SelectMap(ctx, "SELECT * FROM posts WHERE id = ?", id)

type Post struct{ ID string; Titulo string }
posts, err := cassandra.Select[Post](ctx, client, "SELECT * FROM posts")

⚠️ CQL só filtra eficientemente por partition/clustering key — WHERE arbitrário exige ALLOW FILTERING (evite; não é usado por este client). Sem JOIN, sem migrations — schema aplicado manualmente em CQL.

Elasticsearch — core/nosql/elasticsearch

Documento = struct com tags json:"..." (reaproveita encoding/json).

import "kyrux/core/nosql/elasticsearch"

client, err := elasticsearch.New([]string{"http://localhost:9200"})

type Produto struct {
    Nome string `json:"nome"`
}

idx := elasticsearch.IndexOf[Produto](client, "produtos")

id, err := idx.Put(ctx, "", &Produto{Nome: "Caneca"}) // "" = ID auto-gerado
produto, achou, err := idx.Get(ctx, id)
err = idx.Delete(ctx, id)

resultados, err := idx.Search(ctx, map[string]any{
    "query": map[string]any{"match": map[string]any{"nome": "caneca"}},
})
total, err := idx.Count(ctx, map[string]any{"match_all": map[string]any{}})

Search/Count recebem a Query DSL nativa do Elasticsearch, sem tradução. idx.Refresh(ctx) força a atualização do índice — use com moderação em produção (custo de performance do próprio Elasticsearch).

DynamoDB — core/nosql/dynamodb

Model = struct com tags dynamodbav:"..." (aws-sdk-go-v2).

import "kyrux/core/nosql/dynamodb"

// endpoint == "" → AWS real, credential chain padrão do SDK
// endpoint != "" → ex: DynamoDB Local, exige accessKey/secretKey
client, err := dynamodb.New(ctx, "us-east-1", "", "", "")

type Pedido struct {
    ID     string `dynamodbav:"id"`
    Status string `dynamodbav:"status"`
}

pedidos := dynamodb.TableOf[Pedido](client, "pedidos")

err = pedidos.Put(ctx, &Pedido{ID: "1", Status: "novo"})
pedido, achou, err := pedidos.Get(ctx, map[string]any{"id": "1"})
lista, err := pedidos.Query(ctx, "id = :id", map[string]any{":id": "1"})
err = pedidos.Delete(ctx, map[string]any{"id": "1"})

⚠️ Scan varre a tabela inteira — nunca no caminho quente em produção. Prefira Query com uma condição de chave.

SQL vs NoSQL — diferenças de propósito

SQL (core/orm) NoSQL (core/nosql/*)
API de query Uma só, fluente (Where/OrderBy/All) pra qualquer driver Uma por banco, no formato nativo dele — sem abstração compartilhada
Tag do model kyrux:"..." Nativa de cada driver (bson, json, dynamodbav)
Migrations makemigrations/migrate (CLI) Nenhuma — schemaless ou CQL manual
Configuração DB_* no .env, lido pelo bootstrap Nenhuma — você lê e passa pro New(...)
Múltiplas conexões fw.DB.Use("nome") Uma instância de client por New(...) que você guarda onde quiser