feat: add go-memory-load-mongo sample app#226
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new go-memory-load-mongo sample app that exposes a small HTTP API backed by MongoDB, plus a k6 scenario to generate mixed and large-payload load against it.
Changes:
- Introduces a Mongo-backed store layer (customers/products/orders/analytics/large payloads) and HTTP handlers.
- Adds Docker assets (Dockerfile + docker-compose) to run MongoDB, the API, and an optional k6 loadtest profile.
- Adds k6 load scenario and Keploy config for recording/replay.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| go-memory-load-mongo/loadtest/scenario.js | k6 load scenarios and thresholds for mixed API + large payload cycle |
| go-memory-load-mongo/keploy.yml | Keploy configuration for record/replay |
| go-memory-load-mongo/internal/store/store.go | MongoDB data access + validation + aggregation logic |
| go-memory-load-mongo/internal/store/models.go | Request/response and persistence models |
| go-memory-load-mongo/internal/httpapi/server.go | HTTP routing, handlers, JSON encoding/decoding, logging/recovery |
| go-memory-load-mongo/internal/database/mongo.go | MongoDB connection and retrying ping logic |
| go-memory-load-mongo/internal/config/config.go | Env-based config loading |
| go-memory-load-mongo/go.mod | New Go module definition and dependencies |
| go-memory-load-mongo/go.sum | Dependency lockfile entries |
| go-memory-load-mongo/docker-compose.yml | Local runtime: MongoDB + API + k6 profile |
| go-memory-load-mongo/cmd/api/main.go | API server entrypoint wiring config/db/store/http |
| go-memory-load-mongo/Dockerfile | Container build for the API binary |
| go-memory-load-mongo/.env.example | Example env vars for local runs |
| go-memory-load-mongo/.dockerignore | Docker build context exclusions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| keployContainer: "keploy-v3" | ||
| keployNetwork: "keploy-network" | ||
|
|
||
| # Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. |
There was a problem hiding this comment.
Typo in comment: “configration file” should be “configuration file”.
| # Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. | |
| # Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configuration file. |
| module loadtestmongoapi | ||
|
|
||
| go 1.26 | ||
|
|
||
| require go.mongodb.org/mongo-driver/v2 v2.2.1 | ||
|
|
There was a problem hiding this comment.
This new Go sample directory isn’t included in the golangci-lint workflow’s working-directory matrix, so CI linting won’t run for it. Add go-memory-load-mongo to .github/workflows/golangci-lint.yml to keep it consistent with the other Go samples.
| pipeline := mongo.Pipeline{ | ||
| {{Key: "$match", Value: bson.M{"customer._id": customerID}}}, | ||
| {{Key: "$unwind", Value: bson.M{"path": "$items", "preserveNullAndEmptyArrays": true}}}, | ||
| {{Key: "$group", Value: bson.D{ | ||
| {Key: "_id", Value: "$customer._id"}, | ||
| {Key: "orders_count", Value: bson.M{"$addToSet": "$_id"}}, | ||
| {Key: "lifetime_value_cents", Value: bson.M{"$sum": "$total_cents"}}, | ||
| {Key: "last_order_at", Value: bson.M{"$max": "$created_at"}}, | ||
| {Key: "category_spend", Value: bson.M{"$push": bson.M{ | ||
| "category": "$items.category", | ||
| "cents": "$items.line_total_cents", | ||
| }}}, | ||
| }}}, | ||
| } |
There was a problem hiding this comment.
The customer summary aggregation unwinds items before summing total_cents, which will overcount lifetime_value_cents (and affect average) by multiplying each order’s total_cents by its number of items. Consider grouping by order first (or using $first for total_cents) and then aggregating per customer so each order contributes once while still computing category spend.
| // Either product not found or insufficient inventory. | ||
| var exists Product | ||
| if findErr := s.products.FindOne(ctx, bson.M{"_id": input.ProductID}).Decode(&exists); findErr != nil { | ||
| return Order{}, fmt.Errorf("%w: product %s", ErrNotFound, input.ProductID) |
There was a problem hiding this comment.
When FindOneAndUpdate returns mongo.ErrNoDocuments, the follow-up FindOne treats any error as "not found". That will misclassify transient Mongo errors (timeouts, connection issues) as ErrNotFound. Handle mongo.ErrNoDocuments separately and return an internal error for other failures.
| return Order{}, fmt.Errorf("%w: product %s", ErrNotFound, input.ProductID) | |
| if errors.Is(findErr, mongo.ErrNoDocuments) { | |
| return Order{}, fmt.Errorf("%w: product %s", ErrNotFound, input.ProductID) | |
| } | |
| return Order{}, fmt.Errorf("check product existence for %s: retry the request or verify database connectivity: %w", input.ProductID, findErr) |
| Items: items, | ||
| } | ||
|
|
||
| if _, err := s.orders.InsertOne(ctx, order); err != nil { |
There was a problem hiding this comment.
InsertOne for orders does not handle duplicate key errors. Since order IDs are deterministic (contentID(customerID, fingerprint)), retries or repeated identical item sets can hit a duplicate and currently surface as a 500. Consider mapping mongo.IsDuplicateKeyError(err) to ErrConflict (similar to customers/products).
| if _, err := s.orders.InsertOne(ctx, order); err != nil { | |
| if _, err := s.orders.InsertOne(ctx, order); err != nil { | |
| if mongo.IsDuplicateKeyError(err) { | |
| return Order{}, fmt.Errorf("%w: order %s already exists", ErrConflict, order.ID) | |
| } |
| skipCoverage: false | ||
| coverageReportPath: "" | ||
| ignoreOrdering: true | ||
| mongoPassword: "default@123" |
There was a problem hiding this comment.
This Keploy config file includes a hard-coded mongoPassword value. Even if it’s a default, committing passwords to the repo is risky and can be mistaken for a real credential. Prefer removing this field, using an environment variable reference, or replacing it with an obvious placeholder that can’t be used as-is.
| mongoPassword: "default@123" | |
| mongoPassword: "__SET_MONGO_PASSWORD__" |
A sample Mongo app for k6 load test