Skip to content

Conversation

@Kosench
Copy link
Contributor

@Kosench Kosench commented Dec 6, 2025

Challenge 14 Solution

Submitted by: @Kosench
Challenge: Challenge 14

Description

This PR contains my solution for Challenge 14.

Changes

  • Added solution file to challenge-14/submissions/Kosench/solution-template.go

Testing

  • Solution passes all test cases
  • Code follows Go best practices

Thank you for reviewing my submission! 🚀

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2025

Walkthrough

Adds a self-contained Go in-memory microservice-style implementation: User, Product, Order domain models; UserService and ProductService servers; an OrderService orchestrating cross-service order creation; gRPC-like request/response types; HTTP handlers; client stubs; and logging/auth interceptors.

Changes

Cohort / File(s) Summary
New solution file
challenge-14/submissions/Kosench/solution-template.go
Adds domain types (User, Product, Order), service interfaces and server implementations (UserServiceServer, ProductServiceServer), in-memory stores and constructors, OrderService with CreateOrder and GetOrder, gRPC-style request/response types, LoggingInterceptor and AuthInterceptor, HTTP startup handlers (StartUserService, StartProductService), ConnectToServices, client stubs (UserServiceClient, ProductServiceClient), registration placeholders, and several TODOs for incomplete wiring.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant OrderService
    participant UserService
    participant ProductService

    Client->>OrderService: CreateOrder(userID, productID, quantity)

    rect rgb(200,220,240)
    Note over OrderService,UserService: Validate user
    OrderService->>UserService: ValidateUser(userID)
    UserService-->>OrderService: (ok / error)
    end

    rect rgb(220,240,200)
    Note over OrderService,ProductService: Fetch product & check inventory
    OrderService->>ProductService: GetProduct(productID)
    ProductService-->>OrderService: (*Product / error)
    OrderService->>ProductService: CheckInventory(productID, quantity)
    ProductService-->>OrderService: (available / error)
    end

    rect rgb(240,220,200)
    Note over OrderService: Create and store order
    OrderService->>OrderService: compute total, increment ID, store in map
    end

    OrderService-->>Client: (*Order / error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • OrderService.CreateOrder: validation sequence, error handling, and concurrency (mutex usage).
    • In-memory maps and their synchronization across service methods.
    • Interceptor implementations (logging/auth) and gRPC metadata handling.
    • HTTP ↔ gRPC error mapping and the HTTP fallback logic in client stubs.
    • Incomplete TODOs (StartProductService wiring, ConnectToServices details, some client RPC call wiring).

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary change: adding a Challenge 14 solution submission by the author Kosench.
Description check ✅ Passed The description is directly related to the changeset, clearly outlining the Challenge 14 solution submission with file location and testing claims.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
challenge-14/submissions/Kosench/solution-template.go (2)

31-46: Consider using integer cents for monetary values.

Using float64 for Price and Total can lead to floating-point precision issues in financial calculations (e.g., 0.1 + 0.2 != 0.3). A common practice is to store monetary values as integer cents and convert for display.

For a challenge solution this is acceptable, but worth noting for production code.


439-456: Context is ignored in HTTP client calls.

The ctx parameter is not used, so request cancellation and timeouts won't work. Use http.NewRequestWithContext instead of http.Get.

 func (c *UserServiceClient) GetUser(ctx context.Context, userID int64) (*User, error) {
-	resp, err := http.Get(fmt.Sprintf("%s/user/get?id=%d", c.baseURL, userID))
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet,
+		fmt.Sprintf("%s/user/get?id=%d", c.baseURL, userID), nil)
+	if err != nil {
+		return nil, err
+	}
+	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
 		return nil, err
 	}

Apply the same pattern to ValidateUser, GetProduct, and CheckInventory.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b8b26a and 45945bc.

📒 Files selected for processing (1)
  • challenge-14/submissions/Kosench/solution-template.go (1 hunks)
🔇 Additional comments (3)
challenge-14/submissions/Kosench/solution-template.go (3)

60-91: LGTM!

The UserServiceServer implementation is clean. The in-memory map is initialized once and only read afterward, making it safe for concurrent access without synchronization.


93-124: LGTM!

The ProductServiceServer follows the same safe pattern as UserServiceServer with read-only map access after initialization.


532-547: Registration stubs and main are appropriate for the challenge.

The empty registration functions and minimal main are reasonable placeholders for this challenge context.

Comment on lines 291 to 307
mux.HandleFunc("/user/get", func(w http.ResponseWriter, r *http.Request) {
userIDStr := r.URL.Query().Get("id")
userID, _ := strconv.ParseInt(userIDStr, 10, 64)

user, err := userServer.GetUser(r.Context(), userID)
if err != nil {
if status.Code(err) == codes.NotFound {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle strconv.ParseInt errors to avoid silent failures.

Ignoring the parse error causes invalid input like ?id=abc to silently default to 0, which could return unexpected results if a user with ID 0 existed.

 	mux.HandleFunc("/user/get", func(w http.ResponseWriter, r *http.Request) {
 		userIDStr := r.URL.Query().Get("id")
-		userID, _ := strconv.ParseInt(userIDStr, 10, 64)
+		userID, err := strconv.ParseInt(userIDStr, 10, 64)
+		if err != nil {
+			http.Error(w, "invalid user id", http.StatusBadRequest)
+			return
+		}

The same pattern applies to lines 310-311, 354-355, 372-375.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mux.HandleFunc("/user/get", func(w http.ResponseWriter, r *http.Request) {
userIDStr := r.URL.Query().Get("id")
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
user, err := userServer.GetUser(r.Context(), userID)
if err != nil {
if status.Code(err) == codes.NotFound {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
})
mux.HandleFunc("/user/get", func(w http.ResponseWriter, r *http.Request) {
userIDStr := r.URL.Query().Get("id")
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil {
http.Error(w, "invalid user id", http.StatusBadRequest)
return
}
user, err := userServer.GetUser(r.Context(), userID)
if err != nil {
if status.Code(err) == codes.NotFound {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
})
🤖 Prompt for AI Agents
In challenge-14/submissions/Kosench/solution-template.go around lines 291-307
(and similarly at 310-311, 354-355, 372-375), the code ignores errors from
strconv.ParseInt which lets invalid ids (e.g. ?id=abc) silently become 0; update
each handler to check the ParseInt error and return a 400 Bad Request
(http.Error) with a descriptive message when parsing fails, and only proceed to
call server methods when the parsed id is valid.

Comment on lines 401 to 426
// Connect to both services and return an OrderService
func ConnectToServices(userServiceAddr, productServiceAddr string) (*OrderService, error) {
// TODO: Implement this function
// Hint: create gRPC connections with interceptors, create clients, return OrderService
userConn, err := grpc.Dial(
userServiceAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(AuthInterceptor))
if err != nil {
return nil, fmt.Errorf("failed to connect to user service: %w", err)
}

productConn, err := grpc.Dial(
productServiceAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(AuthInterceptor))
if err != nil {
userConn.Close()
return nil, fmt.Errorf("failed to connect to product service %w", err)
}

userClient := NewUserServiceClient(userConn)
productClient := NewProductServiceClient(productConn)

return NewOrderService(userClient, productClient), nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

grpc.Dial deprecated grpc.NewClient go 2024 2025

💡 Result:

Short answer: Yes — in grpc-go the old grpc.Dial (and DialContext) were deprecated in favor of grpc.NewClient. Use NewClient (and its context-aware variants / options) because it constructs a ClientConn without immediately doing I/O and it defaults to the "dns" resolver (Dial historically used "passthrough"). See the package docs and migration/discussion threads for details and behavioral differences. [1][2][3]

Sources

  • grpc package docs (pkg.go.dev) — Dial/DialContext marked Deprecated; guidance to use NewClient. [1]
  • grpc-go documentation / anti-patterns — explains differences (default resolver, Dial does immediate connect, NewClient starts idle). [2]
  • grpc/grpc-go issue tracking (migration/docs to NewClient). [3]

🌐 Web query:

gRPC-go grpc.Dial vs grpc.NewClient deprecation documentation

💡 Result:

Short answer: grpc.Dial / DialContext are deprecated in gRPC‑Go in favor of grpc.NewClient. NewClient (introduced in v1.63) creates a ClientConn that starts IDLE (does not perform immediate I/O), defaults to the "dns" resolver, and uses ClientOptions (not DialOptions). Dial/DialContext historically started connecting immediately, used the "passthrough" default resolver, and exposed DialOptions like WithBlock/WithTimeout; DialContext currently calls NewClient then exits idle. See the docs and issues for details and migration guidance. [1][2][3]

Sources:

  • pkg.go.dev grpc docs (Dial deprecated; DialContext behavior & resolver difference). [1]
  • grpc-go Documentation/anti-patterns (explains NewClient vs Dial differences and rationale). [2]
  • grpc/grpc-go issue tracking (migration and doc updates to prefer NewClient). [3]

🌐 Web query:

gRPC go connection close lifecycle management best practices

💡 Result:

Here are concise, actionable best practices for managing gRPC (Go) connection lifecycle — client and server — with sources.

Client-side

  • Reuse a single grpc.ClientConn (don’t dial per RPC); close it when the app/component is shutting down. [1][2]
  • Dial with context/timeouts (grpc.DialContext) and use backoff/retry policies; avoid WithTimeout (deprecated). Use connection pooling only if needed. [2][3]
  • Use keepalive settings only when necessary (to avoid idle closes by proxies); tune KEEPALIVE_TIME / TIMEOUT and KEEPALIVE_WITHOUT_CALLS carefully. [4]
  • Handle context cancellation/timeouts per RPC (context.WithTimeout / deadlines) so calls fail fast and don’t hang while shutting down. [3]
  • Log and surface transport errors (e.g., "transport is closing") and increase MaxConnectionAge/MaxConnectionAgeGrace if servers rotate connections. [2]

Server-side

  • Implement graceful shutdown: mark unhealthy (deregister from LB), stop accepting new work, allow in-flight RPCs to finish within a deadline, then force-stop. Use Server.GracefulStop() with a timeout fallback to Stop(). [5][1]
  • Use health checks so load balancers stop sending traffic during shutdown windows. Delay shutdown until deregistration completes. [5]
  • Configure server keepalive and MaxConnectionIdle/MaxConnectionAge to control connection lifetimes and prompt DNS/load-balancer re-resolution when needed. [4]

Operational / reliability

  • Test shutdown and connection-failure scenarios (streaming + unary) under load and high latency. Watch for EOF/transport errors and tune window/flow-control if necessary. [2][6]
  • Emit metrics for active RPCs, connections, and shutdown progress; use these to gate forced shutdown. [5]
  • Use libraries/frameworks (lifecycle managers like fx) or a shutdown coordinator to centralize closing order: LB deregistration → stop accepting → wait for inflight → close resources → close ClientConn(s). [6]

Quick checklist for shutdown order

  1. Mark service unhealthy / deregister from LB. [5]
  2. Stop accepting new RPCs. [1]
  3. Wait for in-flight RPCs with a bounded timeout. [1][5]
  4. Close resources (DBs, queues) and client connections (ClientConn.Close()). [2][5]
  5. Force-stop if timeout expires. [1]

Sources
[1] gRPC: Graceful shutdown docs and Go examples. (grpc.io). (grpc.io)
[2] grpc-go repository README / issues (reuse ClientConn, debug “transport is closing”, connection errors). (github.com)
[3] Go contexts + Dial/Call timeout guidance (StackOverflow / grpc-go docs). (stackoverflow.com)
[4] gRPC Keepalive config and server connection timeouts. (grpc.io)
[5] gRPC performance & graceful-stop recommendations (grpc.io guides/examples). (grpc.io)
[6] grpc-go issue and high-latency behavior (flow-control/window tuning). (github.com)

If you want, I can produce a small Go shutdown coordinator example (client + server) implementing these steps.

Citations:


Replace deprecated grpc.Dial with grpc.NewClient and implement connection lifecycle management.

  1. grpc.Dial is deprecated in favor of grpc.NewClient (introduced in grpc-go v1.63)
  2. The gRPC connections (userConn, productConn) are never closed—add a Close method to OrderService to manage cleanup
  3. Line 419 error message is malformed (missing colon before %w)

Fix the error message:

-		return nil, fmt.Errorf("failed to connect to product service %w", err)
+		return nil, fmt.Errorf("failed to connect to product service: %w", err)

Add connection lifecycle management to OrderService:

+func (s *OrderService) Close() error {
+	if err := s.userClient.Close(); err != nil {
+		return err
+	}
+	return s.productClient.Close()
+}

Consider migrating to grpc.NewClient when possible for consistency with current best practices.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In challenge-14/submissions/Kosench/solution-template.go around lines 401 to
426, replace the deprecated grpc.Dial usage with grpc.NewClient (or the current
constructor in your grpc version) to create user and product clients, fix the
malformed error message on line 419 by adding the missing colon before %w, and
add connection lifecycle management: store the underlying connection objects on
the OrderService struct, ensure any connection opened is closed on error paths,
and implement an OrderService.Close() method that closes both connections (and
is called by callers when done).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (5)
challenge-14/submissions/Kosench/solution-template.go (5)

527-567: HTTP client calls ignore context cancellation and timeouts.

Same issue as UserServiceClient: the methods receive a context.Context but use http.Get, ignoring context cancellation and deadlines.

Apply the same fix as suggested for UserServiceClient—use http.NewRequestWithContext and http.DefaultClient.Do:

 func (c *ProductServiceClient) GetProduct(ctx context.Context, productID int64) (*Product, error) {
-	resp, err := http.Get(fmt.Sprintf("%s/product/get?id=%d", c.baseURL, productID))
+	req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/product/get?id=%d", c.baseURL, productID), nil)
+	if err != nil {
+		return nil, err
+	}
+	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
 		return nil, err
 	}

Apply the same pattern to CheckInventory.


345-361: Handle strconv.ParseInt error to avoid silent failures.

Line 347 ignores the parse error, causing invalid input like ?id=abc to silently default to 0. This was flagged in a previous review but remains unfixed in this handler.

Apply this diff:

 	mux.HandleFunc("/user/validate", func(w http.ResponseWriter, r *http.Request) {
 		userIDStr := r.URL.Query().Get("id")
-		userID, _ := strconv.ParseInt(userIDStr, 10, 64)
+		userID, err := strconv.ParseInt(userIDStr, 10, 64)
+		if err != nil {
+			http.Error(w, "invalid user id", http.StatusBadRequest)
+			return
+		}
 
 		valid, err := userServer.ValidateUser(r.Context(), userID)

389-405: Handle strconv.ParseInt error to avoid silent failures.

Line 391 ignores the parse error, causing invalid input to silently default to 0. This was flagged in a previous review but remains unfixed.

Apply this diff:

 	mux.HandleFunc("/product/get", func(w http.ResponseWriter, r *http.Request) {
 		productIDStr := r.URL.Query().Get("id")
-		productID, _ := strconv.ParseInt(productIDStr, 10, 64)
+		productID, err := strconv.ParseInt(productIDStr, 10, 64)
+		if err != nil {
+			http.Error(w, "invalid product id", http.StatusBadRequest)
+			return
+		}
 
 		product, err := productServer.GetProduct(r.Context(), productID)

407-425: Handle strconv.ParseInt errors to avoid silent failures.

Lines 409 and 411 ignore parse errors, causing invalid input to silently default to 0. This was flagged in a previous review but remains unfixed.

Apply this diff:

 	mux.HandleFunc("/product/check-inventory", func(w http.ResponseWriter, r *http.Request) {
 		productIDStr := r.URL.Query().Get("id")
-		productID, _ := strconv.ParseInt(productIDStr, 10, 64)
+		productID, err := strconv.ParseInt(productIDStr, 10, 64)
+		if err != nil {
+			http.Error(w, "invalid product id", http.StatusBadRequest)
+			return
+		}
 		quantityStr := r.URL.Query().Get("quantity")
-		quantity, _ := strconv.ParseInt(quantityStr, 10, 32)
+		quantity, err := strconv.ParseInt(quantityStr, 10, 32)
+		if err != nil {
+			http.Error(w, "invalid quantity", http.StatusBadRequest)
+			return
+		}
 
 		available, err := productServer.CheckInventory(r.Context(), productID, int32(quantity))

449-456: Missing colon in error message.

Line 455: the error format string is missing a colon before %w, which was flagged in a previous review but remains unfixed.

Apply this diff:

 	productConn, err := grpc.NewClient(
 		productServiceAddr,
 		grpc.WithTransportCredentials(insecure.NewCredentials()),
 		grpc.WithUnaryInterceptor(AuthInterceptor))
 	if err != nil {
 		userConn.Close()
-		return nil, fmt.Errorf("failed to connect to product service %w", err)
+		return nil, fmt.Errorf("failed to connect to product service: %w", err)
 	}
🧹 Nitpick comments (1)
challenge-14/submissions/Kosench/solution-template.go (1)

476-512: HTTP client calls ignore context cancellation and timeouts.

The methods receive a context.Context but use http.Get instead of creating requests with http.NewRequestWithContext. This means context cancellation and deadlines are not respected, potentially causing requests to hang.

Apply this pattern to respect context:

 func (c *UserServiceClient) GetUser(ctx context.Context, userID int64) (*User, error) {
-	resp, err := http.Get(fmt.Sprintf("%s/user/get?id=%d", c.baseURL, userID))
+	req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/user/get?id=%d", c.baseURL, userID), nil)
+	if err != nil {
+		return nil, err
+	}
+	resp, err := http.DefaultClient.Do(req)
 	if err != nil {
 		return nil, err
 	}

Apply the same pattern to ValidateUser (and the ProductServiceClient methods).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45945bc and de98387.

📒 Files selected for processing (1)
  • challenge-14/submissions/Kosench/solution-template.go (1 hunks)

@RezaSi RezaSi merged commit d6d0119 into RezaSi:main Dec 6, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants