Bug: refactor(dynamic_routing): make the dynamo configs optional




diff --git a/crates/external_services/src/grpc_client.rs b/crates/external_services/src/grpc_client.rs
index 5a9bd6143da..1b93ac6bd89 100644
--- a/crates/external_services/src/grpc_client.rs
+++ b/crates/external_services/src/grpc_client.rs
@@ -35,7 +35,7 @@ pub type Client = hyper_util::client::legacy::Client<HttpConnector, Body>;
 pub struct GrpcClients {
     /// The routing client
     #[cfg(feature = "dynamic_routing")]
-    pub dynamic_routing: RoutingStrategy,
+    pub dynamic_routing: Option<RoutingStrategy>,
     /// Health Check client for all gRPC services
     #[cfg(feature = "dynamic_routing")]
     pub health_client: HealthCheckClient,
@@ -47,7 +47,7 @@ pub struct GrpcClients {
 pub struct GrpcClientSettings {
     #[cfg(feature = "dynamic_routing")]
     /// Configs for Dynamic Routing Client
-    pub dynamic_routing_client: DynamicRoutingClientConfig,
+    pub dynamic_routing_client: Option<DynamicRoutingClientConfig>,
     /// Configs for Unified Connector Service client
     pub unified_connector_service: Option<UnifiedConnectorServiceClientConfig>,
 }
@@ -69,9 +69,10 @@ impl GrpcClientSettings {
         let dynamic_routing_connection = self
             .dynamic_routing_client
             .clone()
-            .get_dynamic_routing_connection(client.clone())
-            .await
-            .expect("Failed to establish a connection with the Dynamic Routing Server");
+            .map(|config| config.get_dynamic_routing_connection(client.clone()))
+            .transpose()
+            .expect("Failed to establish a connection with the Dynamic Routing Server")
+            .flatten();
 
         #[cfg(feature = "dynamic_routing")]
         let health_client = HealthCheckClient::build_connections(self, client)
diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs
index a9ee7852d31..f4aeec76551 100644
--- a/crates/external_services/src/grpc_client/dynamic_routing.rs
+++ b/crates/external_services/src/grpc_client/dynamic_routing.rs
@@ -47,11 +47,11 @@ pub enum DynamicRoutingError {
 #[derive(Debug, Clone)]
 pub struct RoutingStrategy {
     /// success rate service for Dynamic Routing
-    pub success_rate_client: Option<SuccessRateCalculatorClient<Client>>,
+    pub success_rate_client: SuccessRateCalculatorClient<Client>,
     /// contract based routing service for Dynamic Routing
-    pub contract_based_client: Option<ContractScoreCalculatorClient<Client>>,
+    pub contract_based_client: ContractScoreCalculatorClient<Client>,
     /// elimination service for Dynamic Routing
-    pub elimination_based_client: Option<EliminationAnalyserClient<Client>>,
+    pub elimination_based_client: EliminationAnalyserClient<Client>,
 }
 
 /// Contains the Dynamic Routing Client Config
@@ -74,32 +74,28 @@ pub enum DynamicRoutingClientConfig {
 
 impl DynamicRoutingClientConfig {
     /// establish connection with the server
-    pub async fn get_dynamic_routing_connection(
+    pub fn get_dynamic_routing_connection(
         self,
         client: Client,
-    ) -> Result<RoutingStrategy, Box<dyn std::error::Error>> {
-        let (success_rate_client, contract_based_client, elimination_based_client) = match self {
+    ) -> Result<Option<RoutingStrategy>, Box<dyn std::error::Error>> {
+        match self {
             Self::Enabled { host, port, .. } => {
                 let uri = format!("http://{host}:{port}").parse::<tonic::transport::Uri>()?;
                 logger::info!("Connection established with dynamic routing gRPC Server");
-                (
-                    Some(SuccessRateCalculatorClient::with_origin(
-                        client.clone(),
-                        uri.clone(),
-                    )),
-                    Some(ContractScoreCalculatorClient::with_origin(
-                        client.clone(),
-                        uri.clone(),
-                    )),
-                    Some(EliminationAnalyserClient::with_origin(client, uri)),
-                )
+
+                let (success_rate_client, contract_based_client, elimination_based_client) = (
+                    SuccessRateCalculatorClient::with_origin(client.clone(), uri.clone()),
+                    ContractScoreCalculatorClient::with_origin(client.clone(), uri.clone()),
+                    EliminationAnalyserClient::with_origin(client, uri),
+                );
+
+                Ok(Some(RoutingStrategy {
+                    success_rate_client,
+                    contract_based_client,
+                    elimination_based_client,
+                }))
             }
-            Self::Disabled => (None, None, None),
-        };
-        Ok(RoutingStrategy {
-            success_rate_client,
-            contract_based_client,
-            elimination_based_client,
-        })
+            Self::Disabled => Ok(None),
+        }
     }
 }
diff --git a/crates/external_services/src/grpc_client/health_check_client.rs b/crates/external_services/src/grpc_client/health_check_client.rs
index 94d78df7955..e0dac3a8fb9 100644
--- a/crates/external_services/src/grpc_client/health_check_client.rs
+++ b/crates/external_services/src/grpc_client/health_check_client.rs
@@ -53,11 +53,11 @@ impl HealthCheckClient {
     ) -> Result<Self, Box<dyn std::error::Error>> {
         let dynamic_routing_config = &config.dynamic_routing_client;
         let connection = match dynamic_routing_config {
-            DynamicRoutingClientConfig::Enabled {
+            Some(DynamicRoutingClientConfig::Enabled {
                 host,
                 port,
                 service,
-            } => Some((host.clone(), *port, service.clone())),
+            }) => Some((host.clone(), *port, service.clone())),
             _ => None,
         };
 
@@ -81,11 +81,11 @@ impl HealthCheckClient {
     ) -> HealthCheckResult<HealthCheckMap> {
         let dynamic_routing_config = &config.dynamic_routing_client;
         let connection = match dynamic_routing_config {
-            DynamicRoutingClientConfig::Enabled {
+            Some(DynamicRoutingClientConfig::Enabled {
                 host,
                 port,
                 service,
-            } => Some((host.clone(), *port, service.clone())),
+            }) => Some((host.clone(), *port, service.clone())),
             _ => None,
         };
 
diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs
index 7feea3286ca..87ed5ff40a8 100644
--- a/crates/router/src/core/payments/routing.rs
+++ b/crates/router/src/core/payments/routing.rs
@@ -2100,13 +2100,13 @@ where
             "performing success_based_routing for profile {}",
             profile_id.get_string_repr()
         );
-        let client = state
+        let client = &state
             .grpc_client
             .dynamic_routing
-            .success_rate_client
             .as_ref()
             .ok_or(errors::RoutingError::SuccessRateClientInitializationError)
-            .attach_printable("success_rate gRPC client not found")?;
+            .attach_printable("dynamic routing gRPC client not found")?
+            .success_rate_client;
 
         let success_based_routing_configs = routing::helpers::fetch_dynamic_routing_configs::<
             api_routing::SuccessBasedRoutingConfig,
@@ -2284,13 +2284,13 @@ pub async fn perform_elimination_routing(
             "performing elimination_routing for profile {}",
             profile_id.get_string_repr()
         );
-        let client = state
+        let client = &state
             .grpc_client
             .dynamic_routing
-            .elimination_based_client
             .as_ref()
             .ok_or(errors::RoutingError::EliminationClientInitializationError)
-            .attach_printable("elimination routing's gRPC client not found")?;
+            .attach_printable("dynamic routing gRPC client not found")?
+            .elimination_based_client;
 
         let elimination_routing_config = routing::helpers::fetch_dynamic_routing_configs::<
             api_routing::EliminationRoutingConfig,
@@ -2484,13 +2484,13 @@ where
             "performing contract_based_routing for profile {}",
             profile_id.get_string_repr()
         );
-        let client = state
+        let client = &state
             .grpc_client
             .dynamic_routing
-            .contract_based_client
             .as_ref()
             .ok_or(errors::RoutingError::ContractRoutingClientInitializationError)
-            .attach_printable("contract routing gRPC client not found")?;
+            .attach_printable("dynamic routing gRPC client not found")?
+            .contract_based_client;
 
         let contract_based_routing_configs = routing::helpers::fetch_dynamic_routing_configs::<
             api_routing::ContractBasedRoutingConfig,
diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs
index 52a3b4f1a7b..806a01235f3 100644
--- a/crates/router/src/core/routing.rs
+++ b/crates/router/src/core/routing.rs
@@ -1848,10 +1848,10 @@ pub async fn success_based_routing_update_configs(
         state
             .grpc_client
             .dynamic_routing
-            .success_rate_client
             .as_ref()
-            .async_map(|sr_client| async {
-                sr_client
+            .async_map(|dr_client| async {
+                dr_client
+                    .success_rate_client
                     .invalidate_success_rate_routing_keys(
                         profile_id.get_string_repr().into(),
                         state.get_grpc_headers(),
@@ -1952,10 +1952,10 @@ pub async fn elimination_routing_update_configs(
         state
             .grpc_client
             .dynamic_routing
-            .elimination_based_client
             .as_ref()
-            .async_map(|er_client| async {
-                er_client
+            .async_map(|dr_client| async {
+                dr_client
+                    .elimination_based_client
                     .invalidate_elimination_bucket(
                         profile_id.get_string_repr().into(),
                         state.get_grpc_headers(),
@@ -2287,12 +2287,11 @@ pub async fn contract_based_routing_update_configs(
 
     state
         .grpc_client
-        .clone()
         .dynamic_routing
-        .contract_based_client
-        .clone()
-        .async_map(|ct_client| async move {
-            ct_client
+        .as_ref()
+        .async_map(|dr_client| async {
+            dr_client
+                .contract_based_client
                 .invalidate_contracts(
                     profile_id.get_string_repr().into(),
                     state.get_grpc_headers(),
diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs
index 8b4c747d13f..32a7b560566 100644
--- a/crates/router/src/core/routing/helpers.rs
+++ b/crates/router/src/core/routing/helpers.rs
@@ -845,14 +845,14 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
 ) -> RouterResult<()> {
     if let Some(success_based_algo_ref) = dynamic_routing_algo_ref.success_based_algorithm {
         if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None {
-            let client = state
+            let client = &state
                 .grpc_client
                 .dynamic_routing
-                .success_rate_client
                 .as_ref()
                 .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
-                    message: "success_rate gRPC client not found".to_string(),
-                })?;
+                    message: "dynamic routing gRPC client not found".to_string(),
+                })?
+                .success_rate_client;
 
             let payment_connector = &payment_attempt.connector.clone().ok_or(
                 errors::ApiErrorResponse::GenericNotFoundError {
@@ -1214,14 +1214,14 @@ pub async fn update_window_for_elimination_routing(
 ) -> RouterResult<()> {
     if let Some(elimination_algo_ref) = dynamic_algo_ref.elimination_routing_algorithm {
         if elimination_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None {
-            let client = state
+            let client = &state
                 .grpc_client
                 .dynamic_routing
-                .elimination_based_client
                 .as_ref()
                 .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
-                    message: "elimination_rate gRPC client not found".to_string(),
-                })?;
+                    message: "dynamic routing gRPC client not found".to_string(),
+                })?
+                .elimination_based_client;
 
             let elimination_routing_config = fetch_dynamic_routing_configs::<
                 routing_types::EliminationRoutingConfig,
@@ -1394,14 +1394,14 @@ pub async fn push_metrics_with_update_window_for_contract_based_routing(
     if let Some(contract_routing_algo_ref) = dynamic_routing_algo_ref.contract_based_routing {
         if contract_routing_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None
         {
-            let client = state
+            let client = &state
                 .grpc_client
                 .dynamic_routing
-                .contract_based_client
-                .clone()
+                .as_ref()
                 .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
-                    message: "contract_routing gRPC client not found".to_string(),
-                })?;
+                    message: "dynamic routing gRPC client not found".to_string(),
+                })?
+                .contract_based_client;
 
             let payment_connector = &payment_attempt.connector.clone().ok_or(
                 errors::ApiErrorResponse::GenericNotFoundError {

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This pull request introduces changes to improve the handling of optional configurations for dynamic routing in the gRPC client module. The updates include modifying the `GrpcClientSettings` structure to use `Option` types, enhancing error handling, and simplifying connection establishment logic. Additionally, default implementations are added to certain structs to streamline initialization.

### Enhancements to dynamic routing configuration:

* [`crates/external_services/src/grpc_client.rs`](diffhunk://#diff-0129f28e4a26fac6ce00609c3a3542c380baabb72d04d977daa28f364ab319beL50-R51): Changed `dynamic_routing_client` in `GrpcClientSettings` to use `Option<DynamicRoutingClientConfig>` instead of a direct type, allowing for better handling of cases where the configuration might be absent.

* [`crates/external_services/src/grpc_client.rs`](diffhunk://#diff-0129f28e4a26fac6ce00609c3a3542c380baabb72d04d977daa28f364ab319beL72-R77): Updated connection establishment logic to use `async_map` for optional configurations, added error handling with `transpose`, and provided a default value when no connection is established.

### Improvements to struct initialization:

* [`crates/external_services/src/grpc_client/dynamic_routing.rs`](diffhunk://#diff-7b95c78b2ff78bd774ff3483d1565e28529f49d7cd3e819944cd6e14471e89f4L47-R47): Added a `Default` implementation to the `RoutingStrategy` struct, simplifying initialization when certain fields are optional.

### Simplification of dynamic routing checks:

* [`crates/external_services/src/grpc_client/health_check_client.rs`](diffhunk://#diff-66f886eb2cc6f5e4e295e28e98bd77c97d1769cf82c54dd2c440156cdd9b16f0L56-R60): Refactored logic to handle `DynamicRoutingClientConfig` as an `Option` type, simplifying pattern matching and reducing code duplication. [[1]](diffhunk://#diff-66f886eb2cc6f5e4e295e28e98bd77c97d1769cf82c54dd2c440156cdd9b16f0L56-R60) [[2]](diffhunk://#diff-66f886eb2cc6f5e4e295e28e98bd77c97d1769cf82c54dd2c440156cdd9b16f0L84-R88)

### Added utility for asynchronous operations:

* [`crates/external_services/src/grpc_client.rs`](diffhunk://#diff-0129f28e4a26fac6ce00609c3a3542c380baabb72d04d977daa28f364ab319beR13): Imported `AsyncExt` from `common_utils::ext_traits` to leverage its utilities for asynchronous operations like `async_map`.

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
This is a configuration level changes making the dynamo configs optional. CI checks should suffice

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
