package com.example.trading.gateway.grpc; import java.util.concurrent.TimeUnit; import com.example.trading.contracts.v1.OrderServiceGrpc.OrderServiceBlockingStub; import com.example.trading.contracts.v1.SubmitOrderResponse; import com.example.trading.gateway.config.GatewayOrderProperties; import com.example.trading.gateway.order.FixToProtobufMapper; import com.example.trading.gateway.order.IncomingOrder; import com.example.trading.gateway.order.OrderSubmissionResult; import com.example.trading.gateway.order.OrderSubmissionResult.Outcome; import io.grpc.Status; import io.grpc.StatusRuntimeException; import org.springframework.stereotype.Component; @Component public class GrpcOrderClient { private final OrderServiceBlockingStub stub; private final FixToProtobufMapper mapper; private final long deadlineNanos; public GrpcOrderClient(OrderServiceBlockingStub stub, FixToProtobufMapper mapper, GatewayOrderProperties properties) { this.stub = stub; this.mapper = mapper; this.deadlineNanos = properties.deadline().toNanos(); } public OrderSubmissionResult submit(IncomingOrder order) { try { // Derived immutable stubs share the Spring-managed channel. No application retries. SubmitOrderResponse response = stub.withDeadlineAfter(deadlineNanos, TimeUnit.NANOSECONDS) .submitOrder(mapper.map(order)); // ACCEPTED without an OMS identity is not a valid business decision. // Treat the malformed response as uncertain rather than failing later // during report creation or inventing an OMS identifier. if (response.getDecision() == com.example.trading.contracts.v1.OrderDecision.ACCEPTED && response.getOrderId().isBlank()) { return new OrderSubmissionResult(Outcome.UNKNOWN_OUTCOME, "", "", Status.Code.DATA_LOSS); } return switch (response.getDecision()) { case ACCEPTED -> new OrderSubmissionResult(Outcome.ACCEPTED, response.getOrderId(), "", Status.Code.OK); case REJECTED -> new OrderSubmissionResult(Outcome.REJECTED, response.getOrderId(), response.getRejectReason(), Status.Code.OK); // An absent/future decision is not evidence of rejection. default -> new OrderSubmissionResult(Outcome.UNKNOWN_OUTCOME, response.getOrderId(), "", Status.Code.UNKNOWN); }; } catch (StatusRuntimeException failure) { Status.Code status = failure.getStatus().getCode(); return new OrderSubmissionResult(status == Status.Code.INVALID_ARGUMENT ? Outcome.INVALID_REQUEST : Outcome.UNKNOWN_OUTCOME, "", "", status); } } }