Bir "users" (kullanıcılar) ve "logs" (günlükler) koleksiyonunuz olduğunu varsayalım. Bir kullanıcının birçok günlüğü olabilir. Tahmin edebileceğiniz gibi, bu değiştirilebilir bir işlemdir ve bir kullanıcının potansiyel olarak kaç günlüğe sahip olabileceğini garanti edemezsiniz. 1 veya bir milyon olabilir! Bu tür durumlarda üst koleksiyonun referansını alt koleksiyonda saklamak idealdir. Bu durumda günlük, kullanıcı referansını içerir. Aşağıdaki örneğe bakın.


Veritabanı içeriği


Hazırlık


db.createCollection("users")
db.users.createIndex({"uuid":1},{unique:true,name:"UQ_uuid"})

db.createCollection("logs")
db.logs.createIndex({"uuid":1},{unique:true,name:"UQ_uuid"})
db.logs.createIndex({"user_uuid":1},{name:"IX_user_uuid"})
db.logs.createIndex({"created_at":1},{name:"IX_created_at"})

Kullanıcı verileri


[
{
"_id": {
"$oid": "604fb496d42b77e01e7f03e0"
},
"uuid": "1ee18d53-a3fc-4493-9ca6-029a4890437e",
"name": "John"
},
{
"_id": {
"$oid": "604fb496d42b77e01e7f03e1"
},
"uuid": "a977db4a-7007-4147-b7bd-8d9565ef3dd7",
"name": "Andy"
},
{
"_id": {
"$oid": "604fb496d42b77e01e7f03e2"
},
"uuid": "40507244-d612-4fa4-b93f-c9c692f3b0fc",
"name": "Robert"
}
]

Günlük verileri


[
{
"_id": {
"$oid": "604fb7f2d42b77e01e7f03e3"
},
"uuid": "0e5e800d-e8fb-4bfe-aaee-29d3c3b480ea",
"action": "login",
"created_at": {
"$date": "2021-03-15T19:39:30.252Z"
},
"user_uuid": "1ee18d53-a3fc-4493-9ca6-029a4890437e"
},
{
"_id": {
"$oid": "604fb7f2d42b77e01e7f03e4"
},
"uuid": "318277c2-c4a8-4c3a-8dc9-dc170d2cf6fc",
"action": "login",
"created_at": {
"$date": "2021-03-15T19:39:30.252Z"
},
"user_uuid": "40507244-d612-4fa4-b93f-c9c692f3b0fc"
},
{
"_id": {
"$oid": "604fb7f2d42b77e01e7f03e5"
},
"uuid": "dc10a79b-1b47-4b13-944e-9c9bb31fdb8c",
"action": "logout",
"created_at": {
"$date": "2021-03-15T19:39:31.252Z"
},
"user_uuid": "40507244-d612-4fa4-b93f-c9c692f3b0fc"
}
]

Depo


Modeller


package storage

import "context"

type UserStorer interface {
Find(ctx context.Context, uuid string) (UserRead, error)
}

type UserRead struct {
ID string `bson:"_id"`
UUID string `bson:"uuid"`
Name string `bson:"name"`
Logs []Log `bson:"logs"`
}

type Log struct {
ID string `bson:"_id"`
UUID string `bson:"uuid"`
Action string `bson:"action"`
CreatedAt time.Time `bson:"created_at"`
UserUUID string `bson:"user_uuid"`
}

Depolayıcı


package mongodb

import (
"context"
"log"
"time"

"github.com/you/mongo/internal/pkg/domain"
"github.com/you/mongo/internal/pkg/storage"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)

var _ storage.UserStorer = UserStorage{}

type UserStorage struct {
Database *mongo.Database
Timeout time.Duration
}

func (u UserStorage) Find(ctx context.Context, uuid string) (storage.UserRead, error) {
ctx, cancel := context.WithTimeout(ctx, u.Timeout)
defer cancel()

// Logs are desc sorted here!
qry := []bson.M{
{
"$match": bson.M{
"uuid": uuid,
},
},
{
"$lookup": bson.M{
// Define the logs collection for the join.
"from": "logs",
"pipeline": []bson.M{
// Select only the relevant logs from the logs collection.
{
"$match": bson.M{
"user_uuid": uuid,
},
},
// Sort logs by their created_at field in desc. 1 = asc
{
"$sort": bson.M{
"created_at": -1,
},
},
},
// Use logs as the field name to match struct field.
"as": "logs",
},
},
}

cur, err := u.Database.Collection("users").Aggregate(ctx, qry)
if err != nil {
log.Println(err)

return storage.UserRead{}, domain.ErrInternal
}

var usr []storage.UserRead

if err := cur.All(context.Background(), &usr); err != nil {
log.Println(err)

return storage.UserRead{}, domain.ErrInternal
}
defer cur.Close(context.Background())

if err := cur.Err(); err != nil {
log.Println(err)

return storage.UserRead{}, domain.ErrInternal
}

if len(usr) == 0 {
return storage.UserRead{}, domain.ErrNotFound
}

return usr[0], nil
}

// This is for listing.
// qry := []bson.M{
// {
// "$lookup": bson.M{
// "from": "logs",
// "let": bson.M{
// "uuid": "$uuid",
// },
// "pipeline": []bson.M{
// {
// "$match": bson.M{
// "$expr": bson.M{
// "$eq": []interface{}{
// "$user_uuid",
// "$$uuid",
// },
// },
// },
// },
// {
// "$sort": bson.M{
// "created_at": -1,
// },
// },
// },
// "as": "logs",
// },
// },
// }

HTTP Yönlendirici


Modeller


package user

import "time"

type Response struct {
ID string `json:"id"`
UUID string `json:"uuid"`
Name string `json:"name"`
Logs []Log `json:"logs"`
}

type Log struct {
ID string `json:"id"`
UUID string `json:"uuid"`
Action string `json:"action"`
CreatedAt time.Time `json:"created_at"`
}

Kontrolör


Gördüğünüz gibi bu dosya çok şey yapıyor ve gerçekten uygun bir istek doğrulaması yok. İhtiyaçlarınıza göre yeniden düzenlemelisiniz.


package user

import (
"encoding/json"
"net/http"

"github.com/you/mongo/internal/pkg/domain"
"github.com/you/mongo/internal/pkg/storage"
"github.com/julienschmidt/httprouter"
)

type Controller struct {
Storage storage.UserStorer
}

// GET /api/v1/users/:uuid
func (c Controller) Find(w http.ResponseWriter, r *http.Request) {
id := httprouter.ParamsFromContext(r.Context()).ByName("uuid")

usr, err := c.Storage.Find(r.Context(), id)
if err != nil {
switch err {
case domain.ErrNotFound:
w.WriteHeader(http.StatusNotFound)
default:
w.WriteHeader(http.StatusInternalServerError)
}
return
}

res := Response{
ID: usr.ID,
UUID: usr.UUID,
Name: usr.Name,
Logs: make([]Log, len(usr.Logs)),
}
for i, log := range usr.Logs {
res.Logs[i].ID = log.ID
res.Logs[i].UUID = log.UUID
res.Logs[i].Action = log.Action
res.Logs[i].CreatedAt = log.CreatedAt
}

body, err := json.Marshal(res)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json; charset=utf-8")
_, _ = w.Write(body)
}

Test


curl --request GET 'http://localhost:3000/api/v1/users/40507244-d612-4fa4-b93f-c9c692f3b0fc'

{
"id": "604fb496d42b77e01e7f03e2",
"uuid": "40507244-d612-4fa4-b93f-c9c692f3b0fc",
"name": "Robert",
"logs": [
{
"id": "604fb7f2d42b77e01e7f03e5",
"uuid": "dc10a79b-1b47-4b13-944e-9c9bb31fdb8c",
"action": "logout",
"created_at": "2021-03-15T19:39:31.252Z"
},
{
"id": "604fb7f2d42b77e01e7f03e4",
"uuid": "318277c2-c4a8-4c3a-8dc9-dc170d2cf6fc",
"action": "login",
"created_at": "2021-03-15T19:39:30.252Z"
}
]
}