NoSQL
🧪 Experimental — parte da camada
core(kyrux/core), rotulada "experimental" pelo próprioUSE.mddo framework. Convive com obootstrap/fw.DBtradicional (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 —
WHEREarbitrário exigeALLOW 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"})
⚠️
Scanvarre a tabela inteira — nunca no caminho quente em produção. PrefiraQuerycom 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 |