Skip to content

Conversation

@sameerkrdev
Copy link
Owner

@sameerkrdev sameerkrdev commented Jan 3, 2026

Summary by CodeRabbit

Release Notes

  • New Features
    • Added order modification functionality allowing users to update the price and/or quantity of existing orders.
    • Implemented validation to ensure at least one modification parameter (price or quantity) is provided.
    • Introduced idempotent operation support for reliable and repeatable order modification requests.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 3, 2026

Walkthrough

This PR introduces order modification capability across the trading system by extending protobuf definitions with ModifyOrderRequest/ModifyOrderResponse messages, implementing RPC handlers in the order service and matching engine, adding actor-based ModifyOrder messaging, introducing validation schemas, and wiring the new modifyOrder endpoint in the API gateway.

Changes

Cohort / File(s) Summary
Protocol Definitions
packages/proto-defs/proto/api/order_service.proto, packages/proto-defs/proto/engine/order_matching.proto
Added ModifyOrderRequest (order_id, user_id, symbol, optional new_quantity, new_price, client_modify_id) and ModifyOrderResponse (order_id, old_order_id, new_order_id, status, status_message) messages. Extended OrderService and MatchingEngine with ModifyOrder RPC methods.
Matching Engine
apps/matching-engine/internal/actor_registy.go, apps/matching-engine/internal/engine.go, apps/matching-engine/internal/server.go
Added ModifyOrder actor function with validation and message routing. Implemented ModifyOrderInternal method with replace/reduce logic, new ModifyOrderMsg type, and ModifyOrderInternalResponse. Added EngineEventType_ORDER_UPDATED event type. Server handler delegates to ModifyOrder actor. Removed ParentOrderID from Order struct; adjusted PriceLevel.Remove logic.
Order Service
apps/order-service/src/controllers/order.controller.ts, apps/order-service/src/grpc.server.ts
Added modifyOrder RPC handler in OrderServerController with clientModifyId generation and delegation to matchingEngineClient. Introduced ServerModifyOrderRequest/ServerModifyOrderResponse type aliases for engine separation. Wired modifyOrder to OrderServiceServer. Updated cancelOrder handling with type aliases (ServerCancelOrderRequest/ServerCancelOrderResponse).
API Gateway
apps/api-gateway/src/controllers/order.controller.ts, apps/api-gateway/src/routers/order.route.ts, apps/api-gateway/src/types/index.ts
Added modifyOrder handler in OrderController building GrpcModifyOrderRequest from HTTP request and delegating to grpcEngine. Added ModifyOrderRequest type extending Request with ModifyOrder params/body. New route at "/:id" using ModifyOrderValidator and modifyOrder handler. Updated proto imports for modify-related types.
Validation
packages/validator/src/Order.ts
Added ModifyOrderValidator schema requiring id (uuid) in params and symbol with optional newPrice/newQuantity in body. Enforces at least one of newPrice or newQuantity is provided.

Sequence Diagram

sequenceDiagram
    participant Client
    participant APIGateway as API Gateway
    participant OrderService
    participant MatchingEngine
    participant OrderActor

    Client->>APIGateway: PATCH /:id (modifyOrder)
    APIGateway->>APIGateway: Validate request (ModifyOrderValidator)
    APIGateway->>OrderService: ModifyOrderRequest (orderId, symbol, newPrice, newQuantity)
    OrderService->>OrderService: Generate clientModifyId
    OrderService->>MatchingEngine: ModifyOrder RPC (symbol, orderId, userId, clientModifyId, newPrice, newQuantity)
    MatchingEngine->>OrderActor: Send ModifyOrderMsg
    OrderActor->>OrderActor: Validate order ownership & symbol
    OrderActor->>OrderActor: Determine replace vs. reduce logic
    alt Replace Flow
        OrderActor->>OrderActor: Cancel old order
        OrderActor->>OrderActor: Create new order with updated fields
    else Reduce Flow
        OrderActor->>OrderActor: Update quantity, emit ORDER_UPDATED event
    end
    OrderActor-->>MatchingEngine: ModifyOrderInternalResponse
    MatchingEngine-->>OrderService: ModifyOrderResponse (orderId, oldOrderId, newOrderId, status)
    OrderService-->>APIGateway: ModifyOrderResponse
    APIGateway-->>Client: 200 OK (response data)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A hop, skip, and swap—
Orders dance to new tunes,
Modify now what was static,
Prices shift, quantities flex,
The exchange keeps trading, ever dynamic!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main feature being added—a modify endpoint for orders across the system.
✨ Finishing touches
  • 📝 Generate docstrings

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Buf (1.61.0)
packages/proto-defs/proto/engine/order_matching.proto

Failure: no .proto files were targeted. This can occur if no .proto files are found in your input, --path points to files that do not exist, or --exclude-path excludes all files.

packages/proto-defs/proto/api/order_service.proto

Failure: no .proto files were targeted. This can occur if no .proto files are found in your input, --path points to files that do not exist, or --exclude-path excludes all files.


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

@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: 8

🧹 Nitpick comments (1)
apps/api-gateway/src/controllers/order.controller.ts (1)

90-90: Inconsistent logger argument format.

Other handlers use { response } object format (e.g., line 43: this.logger.info("Order placed", { response })), but this uses positional argument. This may affect log formatting consistency.

Suggested fix
-        this.logger.info("Order Modified", response);
+        this.logger.info("Order Modified", { response });
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ddf8dd and 6d6688c.

📒 Files selected for processing (11)
  • apps/api-gateway/src/controllers/order.controller.ts
  • apps/api-gateway/src/routers/order.route.ts
  • apps/api-gateway/src/types/index.ts
  • apps/matching-engine/internal/actor_registy.go
  • apps/matching-engine/internal/engine.go
  • apps/matching-engine/internal/server.go
  • apps/order-service/src/controllers/order.controller.ts
  • apps/order-service/src/grpc.server.ts
  • packages/proto-defs/proto/api/order_service.proto
  • packages/proto-defs/proto/engine/order_matching.proto
  • packages/validator/src/Order.ts
🧰 Additional context used
🧬 Code graph analysis (6)
packages/validator/src/Order.ts (2)
apps/matching-engine/internal/actor_registy.go (1)
  • ModifyOrder (62-95)
apps/api-gateway/src/middlewares/zod.validator.middleware.ts (2)
  • T (6-31)
  • req (7-30)
apps/matching-engine/internal/actor_registy.go (2)
packages/validator/src/Order.ts (1)
  • ModifyOrder (61-61)
apps/matching-engine/internal/engine.go (2)
  • ModifyOrderInternalResponse (607-613)
  • ModifyOrderMsg (783-792)
apps/matching-engine/internal/server.go (3)
apps/matching-engine/internal/actor_registy.go (1)
  • ModifyOrder (62-95)
packages/validator/src/Order.ts (1)
  • ModifyOrder (61-61)
apps/api-gateway/src/types/index.ts (1)
  • ModifyOrderRequest (58-61)
apps/api-gateway/src/types/index.ts (2)
apps/matching-engine/internal/actor_registy.go (1)
  • ModifyOrder (62-95)
packages/validator/src/Order.ts (1)
  • ModifyOrder (61-61)
apps/api-gateway/src/routers/order.route.ts (2)
packages/validator/src/Order.ts (1)
  • ModifyOrderValidator (39-60)
apps/api-gateway/src/types/index.ts (1)
  • ModifyOrderRequest (58-61)
apps/api-gateway/src/controllers/order.controller.ts (1)
apps/api-gateway/src/types/index.ts (1)
  • ModifyOrderRequest (58-61)
🔇 Additional comments (11)
packages/proto-defs/proto/engine/order_matching.proto (1)

58-78: LGTM! Well-designed modify order messages.

The proto definitions are well-structured with:

  • Proper idempotency handling via client_modify_id
  • Flexible modification (either price or quantity or both)
  • Clear response structure distinguishing old vs new order IDs (supporting cancel-replace pattern)

Also applies to: 83-83

apps/order-service/src/grpc.server.ts (1)

29-29: LGTM! Correctly wired modifyOrder RPC.

The new RPC is properly bound to the controller method following the same pattern as existing RPCs.

packages/validator/src/Order.ts (1)

51-59: Good validation: Ensures at least one field is modified.

The refinement correctly enforces that either newPrice or newQuantity (or both) must be provided, preventing no-op modification requests.

apps/matching-engine/internal/server.go (1)

95-101: LGTM!

The response mapping correctly translates ModifyOrderInternalResponse fields to pb.ModifyOrderResponse.

apps/matching-engine/internal/actor_registy.go (1)

62-95: LGTM!

The implementation follows the established actor pattern used by PlaceOrder and CancelOrder. The buffered channels and select-based synchronization are consistent with the existing code.

apps/api-gateway/src/types/index.ts (1)

57-61: LGTM!

The ModifyOrderRequest interface follows the same pattern as CancelOrderRequest, properly typing both params and body from the validator schema.

apps/api-gateway/src/controllers/order.controller.ts (1)

72-99: LGTM!

The modifyOrder handler follows the established pattern and correctly maps request parameters to the gRPC request type.

apps/order-service/src/controllers/order.controller.ts (1)

137-191: Implementation follows established patterns.

The modifyOrder handler correctly generates a clientModifyId, constructs the request, and handles both success and error cases consistently with placeOrder and cancelOrder.

apps/matching-engine/internal/engine.go (3)

84-84: LGTM!

Using RemainingQuantity instead of Quantity when removing an order is correct—only the unfilled portion should be subtracted from the price level's total volume.


607-666: LGTM!

The ModifyOrderInternal logic correctly handles validation (ownership, symbol, status), distinguishes between price changes, quantity increases (requiring replace), and quantity reductions (in-place update).


783-792: LGTM!

The ModifyOrderMsg struct correctly captures all required fields for the modification request, including optional NewPrice and NewQuantity pointers.

Comment on lines +668 to +704
func (me *MatchingEngine) reduceOrder(
order *Order,
newQuantity *int64,
) ([]EngineEvent, error) {

if newQuantity == nil {
return nil, fmt.Errorf("quantity required")
}

executed := order.Quantity - order.RemainingQuantity
newRemaining := *newQuantity - executed

if newRemaining < 0 {
return nil, fmt.Errorf("invalid reduce")
}

order.Quantity = *newQuantity
order.RemainingQuantity = newRemaining

// snapshot update (NO semantic meaning here)
return []EngineEvent{
{
Type: uint8(EngineEventType_ORDER_UPDATED),
Data: struct {
OrderID string
Price int64
Quantity int64
RemainingQuantity int64
}{
OrderID: order.ClientOrderID,
Price: order.Price,
Quantity: order.Quantity,
RemainingQuantity: order.RemainingQuantity,
},
},
}, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

reduceOrder doesn't update PriceLevel.TotalVolume, causing data inconsistency.

When reducing an order's quantity, order.Quantity and order.RemainingQuantity are updated, but the associated PriceLevel.TotalVolume is not adjusted. This causes the order book's aggregated volume at that price level to become stale.

Suggested fix
 func (me *MatchingEngine) reduceOrder(
 	order *Order,
 	newQuantity *int64,
 ) ([]EngineEvent, error) {
 
 	if newQuantity == nil {
 		return nil, fmt.Errorf("quantity required")
 	}
 
 	executed := order.Quantity - order.RemainingQuantity
 	newRemaining := *newQuantity - executed
 
 	if newRemaining < 0 {
 		return nil, fmt.Errorf("invalid reduce")
 	}
 
+	// Update PriceLevel volume
+	if order.PriceLevel != nil {
+		volumeDelta := order.RemainingQuantity - newRemaining
+		order.PriceLevel.TotalVolume -= uint64(volumeDelta)
+	}
+
 	order.Quantity = *newQuantity
 	order.RemainingQuantity = newRemaining
📝 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
func (me *MatchingEngine) reduceOrder(
order *Order,
newQuantity *int64,
) ([]EngineEvent, error) {
if newQuantity == nil {
return nil, fmt.Errorf("quantity required")
}
executed := order.Quantity - order.RemainingQuantity
newRemaining := *newQuantity - executed
if newRemaining < 0 {
return nil, fmt.Errorf("invalid reduce")
}
order.Quantity = *newQuantity
order.RemainingQuantity = newRemaining
// snapshot update (NO semantic meaning here)
return []EngineEvent{
{
Type: uint8(EngineEventType_ORDER_UPDATED),
Data: struct {
OrderID string
Price int64
Quantity int64
RemainingQuantity int64
}{
OrderID: order.ClientOrderID,
Price: order.Price,
Quantity: order.Quantity,
RemainingQuantity: order.RemainingQuantity,
},
},
}, nil
}
func (me *MatchingEngine) reduceOrder(
order *Order,
newQuantity *int64,
) ([]EngineEvent, error) {
if newQuantity == nil {
return nil, fmt.Errorf("quantity required")
}
executed := order.Quantity - order.RemainingQuantity
newRemaining := *newQuantity - executed
if newRemaining < 0 {
return nil, fmt.Errorf("invalid reduce")
}
// Update PriceLevel volume
if order.PriceLevel != nil {
volumeDelta := order.RemainingQuantity - newRemaining
order.PriceLevel.TotalVolume -= uint64(volumeDelta)
}
order.Quantity = *newQuantity
order.RemainingQuantity = newRemaining
// snapshot update (NO semantic meaning here)
return []EngineEvent{
{
Type: uint8(EngineEventType_ORDER_UPDATED),
Data: struct {
OrderID string
Price int64
Quantity int64
RemainingQuantity int64
}{
OrderID: order.ClientOrderID,
Price: order.Price,
Quantity: order.Quantity,
RemainingQuantity: order.RemainingQuantity,
},
},
}, nil
}

Comment on lines +90 to +93
if err != nil {
slog.Error("Failed to cancel order")
return nil, err
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Copy-paste error in log message.

The error log says "Failed to cancel order" but this is the ModifyOrder handler. This also matches the same issue in CancelOrder at line 75, which lacks details.

Suggested fix
 	if err != nil {
-		slog.Error("Failed to cancel order")
+		slog.Error("Failed to modify order",
+			"Error", err,
+			"orderId", req.OrderId,
+		)
 		return nil, err
 	}
📝 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
if err != nil {
slog.Error("Failed to cancel order")
return nil, err
}
if err != nil {
slog.Error("Failed to modify order",
"Error", err,
"orderId", req.OrderId,
)
return nil, err
}
🤖 Prompt for AI Agents
In apps/matching-engine/internal/server.go around lines 90-93 (and also fix the
similar incorrect message at line 75), the slog.Error call uses the wrong
message ("Failed to cancel order" in the ModifyOrder handler) and omits error
details; update the log messages to reflect the correct handler ("Failed to
modify order" for ModifyOrder and ensure CancelOrder uses "Failed to cancel
order") and include the error information in the log call (pass the error or
include it as a field) so the logs contain both the correct context and the
actual error details.

Comment on lines +166 to +169
this.logger.info("Order Cancelled successfully", {
...response,
engineStatus: response.status,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Copy-paste error in log message.

The success log says "Order Cancelled successfully" but this is the modifyOrder handler.

Suggested fix
-      this.logger.info("Order Cancelled successfully", {
+      this.logger.info("Order Modified successfully", {
         ...response,
         engineStatus: response.status,
       });
📝 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
this.logger.info("Order Cancelled successfully", {
...response,
engineStatus: response.status,
});
this.logger.info("Order Modified successfully", {
...response,
engineStatus: response.status,
});
🤖 Prompt for AI Agents
In apps/order-service/src/controllers/order.controller.ts around lines 166 to
169, the log message incorrectly says "Order Cancelled successfully" in the
modifyOrder handler; update the log message to accurately reflect this handler
(e.g., "Order modified successfully" or "Order updated successfully") while
keeping the same structured payload ({ ...response, engineStatus:
response.status }) so logging remains consistent.

Comment on lines +58 to +65
message ModifyOrderRequest {
string order_id = 1;
string user_id = 2;
string symbol = 3;

optional int64 new_quantity = 4;
optional int64 new_price = 5;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing critical idempotency field: client_modify_id

The engine-level proto (packages/proto-defs/proto/engine/order_matching.proto line 67) includes client_modify_id marked as "VERY IMPORTANT" for idempotency, but this API-level proto is missing it. Without this field, clients cannot provide idempotency keys for modify requests, creating risk of duplicate modifications on retries—critical in trading systems.

🔎 Proposed fix to add client_modify_id
 message ModifyOrderRequest {
   string order_id = 1;
   string user_id = 2;
   string symbol = 3;
 
   optional int64 new_quantity = 4;
   optional int64 new_price = 5;
+
+  // Idempotency key (VERY IMPORTANT)
+  string client_modify_id = 6;
 }
📝 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
message ModifyOrderRequest {
string order_id = 1;
string user_id = 2;
string symbol = 3;
optional int64 new_quantity = 4;
optional int64 new_price = 5;
}
message ModifyOrderRequest {
string order_id = 1;
string user_id = 2;
string symbol = 3;
optional int64 new_quantity = 4;
optional int64 new_price = 5;
// Idempotency key (VERY IMPORTANT)
string client_modify_id = 6;
}
🤖 Prompt for AI Agents
In packages/proto-defs/proto/api/order_service.proto around lines 58 to 65, the
ModifyOrderRequest is missing the critical idempotency field client_modify_id
required by the engine-level proto; add a new string field named
client_modify_id (use the next available tag, e.g., 6) to the message, mark it
optional to preserve backward compatibility, add a short comment noting "VERY
IMPORTANT — idempotency key" and ensure consumers, server-side validators, and
any mapping to engine/order_matching.proto propagate and validate this field so
clients can supply idempotency keys for modify requests.

@sameerkrdev sameerkrdev merged commit ef947c4 into main Jan 3, 2026
1 check passed
@sameerkrdev sameerkrdev deleted the feat/modify-endpoint branch January 3, 2026 19:44
@coderabbitai coderabbitai bot mentioned this pull request Jan 19, 2026
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