Broadleaf Microservices
  • v1.0.0-latest-prod

Service Interlink Optimization

The Interlink feature, introduced in Broadleaf Microservices 2.2.0-GA, provides a performance optimization for "Flex" deployments where multiple services run within the same JVM. It allows services to bypass the network stack (HTTP/WebClient) and instead communicate via direct reflection-based method invocations.

When properly configured, an Interlink call checks if the target service is resident in the same application.

  • If Resident: The call is executed directly via reflection (orders of magnitude faster).

  • If Remote: The call automatically falls back to a standard WebClient HTTP request.

Framework Auto-Configuration (Legacy)

In version 2.2.0-GA, a set of specific framework hotspots (Catalog, Pricing, Offer) can be optimized by enabling the loopback property.

To enable these out-of-the-box optimizations, set the following property in your application.yml or properties file:

broadleaf.external.client.loopback-optimization-enabled=true
Note
As of 2.2.1-GA, the hardcoded pattern applied by this flag is deprecated in favor of the generic approach outlined below.

For custom implementations or to override specific framework calls, the recommended approach is to explicitly register InterlinkReflectionInfo beans and use the InterlinkClient.

Step 1: Register Reflection Information

You must tell the InterlinkClient how to map a logical "Link Name" to a physical Java method. This is done by registering an InterlinkReflectionInfo bean.

In this example, we are registering a link to the OrderEndpoint.readOrderByOrderNumber method.

@Configuration
public class InterlinkConfig {

   @Bean
   public InterlinkReflectionInfo orderInterlink() {
       // Arguments:
       // 1. Unique Link Name (used by the caller)
       // 2. Fully Qualified Class Name of the Target Controller/Endpoint
       // 3. Controller/Endpoint Method Name
       // 4. List of Parameter Types (as string values) for the Method
       return new InterlinkReflectionInfo(
               "getOrderByOrderNumber",
               "com.broadleafcommerce.order.web.endpoint.OrderEndpoint",
               "readOrderByOrderNumber",
               List.of(
                       "com.broadleafcommerce.data.tracking.core.context.ContextInfo",
                       String.class.getName()
               )
       );
   }
}

Step 2: Invoke via InterlinkClient

Instead of injecting WebClient directly, inject InterlinkClient. You must build an InterlinkRequest that includes both the reflection details (for local calls) and the URI details (for the remote fallback).

import com.broadleafcommerce.data.tracking.core.interlink.Argument;
import com.broadleafcommerce.data.tracking.core.interlink.InterlinkClient;
import com.broadleafcommerce.data.tracking.core.interlink.InterlinkRequest;
import com.broadleafcommerce.data.tracking.core.interlink.MethodArgument;
import com.broadleafcommerce.data.tracking.core.interlink.UrlArgument;
import com.broadleafcommerce.data.tracking.core.context.ContextRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class MyOrderService {

   private final InterlinkClient interlinkClient;
   private final ObjectMapper objectMapper;

   public MyOrderService(InterlinkClient interlinkClient, ObjectMapper objectMapper) {
       this.interlinkClient = interlinkClient;
       this.objectMapper = objectMapper;
   }

   public Object getOrder(String orderNumber) {
       // Build the request
       InterlinkRequest request = new InterlinkRequest("getOrderByOrderNumber") // Must match Bean name
               .withRequestMethod(HttpMethod.GET)

               // URI is required for the WebClient fallback if reflection fails
               .withUri("/orders/{orderNumber}")

               .withArguments(List.of(
                       // UrlArgument: Used for URI template replacement (Fallback)
                       new UrlArgument("orderNumber", orderNumber),

                       // MethodArgument: Used for Reflection (Local)
                       // The index must match the method signature in the ReflectionInfo.
                       // Note: ContextInfo (index 0) is automatically resolved and injected
                       // by the client, so we only need to provide the String argument at index 1.
                       new MethodArgument(1, orderNumber)
               ))
               .withContextRequestHeader(
                       new ContextRequest().withTenantId("MyTenantId"), // Or derive from current context
                       objectMapper
               );

       // Execute
       // Returns the return type of the target method (e.g., Order or ResponseEntity<Order>)
       return interlinkClient.invoke(request);
   }
}

Best Practices & Limitations

  1. Prefer InterlinkClient: For custom code within a Flex package, we encourage using InterlinkClient over WebClient. It provides a "best of both worlds" approach—local speed with remote reliability.

  2. Framework Overrides: While you can use this to override framework service-to-service calls (like OrderOperationsServices calling OrderServices), doing so may impact upgradability. We recommend only overriding framework calls to use Interlink in critical "hotspots" where performance profiling indicates a bottleneck.

  3. ContextInfo Handling: If the target method accepts ContextInfo, you do not need to explicitly provide a MethodArgument for it. The InterlinkClient constructs and injects it automatically. However, you should provide it in the InterlinkReflectionInfo parameter list.