onceMisery commented on issue #25235:
URL: https://github.com/apache/pulsar/issues/25235#issuecomment-3976255362

   Thank you for your reply. Here is my implementation plan. Which one do you 
prefer?
   
   ### Approach B: Server-Side Aggregation (New API Endpoint)
   
   Add a new REST endpoint that returns function names with status summaries. 
The CLI makes a single API call for both
   filtering and extended output.
   
   #### Implementation Steps
   
   **Step 1: Add DTO data model**
   
   File: `pulsar-client-admin-api/.../policies/data/FunctionSummary.java` (new)
   
   ```java
   
   @Data
   public class FunctionSummary {
       private String name;
       private String state;      // RUNNING, STOPPED, PARTIAL
       private int numInstances;
       private int numRunning;
   }
   ```
   
   **Step 2: Extend Admin API interface**
   
   File: `pulsar-client-admin-api/.../Functions.java`
   
   ```java
   /**
    * Get the list of functions with status summary under a namespace.
    */
   List<FunctionSummary> getFunctionsWithStatus(String tenant, String namespace)
           throws PulsarAdminException;
   
   CompletableFuture<List<FunctionSummary>> getFunctionsWithStatusAsync(
           String tenant, String namespace);
   ```
   
   **Step 3: Implement Admin API client**
   
   File: `pulsar-client-admin/.../FunctionsImpl.java`
   
   ```java
   
   @Override
   public List<FunctionSummary> getFunctionsWithStatus(String tenant, String 
namespace)
           throws PulsarAdminException {
       return sync(() -> getFunctionsWithStatusAsync(tenant, namespace));
   }
   
   @Override
   public CompletableFuture<List<FunctionSummary>> getFunctionsWithStatusAsync(
           String tenant, String namespace) {
       WebTarget path = 
functions.path(tenant).path(namespace).path("status-summary");
       return asyncGetRequest(path, new GenericType<List<FunctionSummary>>() {
       });
   }
   ```
   
   New REST path: `GET /admin/v3/functions/{tenant}/{namespace}/status-summary`
   
   **Step 4: Add REST endpoint**
   
   File: `pulsar-functions/worker/.../v3/FunctionsApiV3Resource.java`
   
   ```java
   
   @GET
   @ApiOperation(
           value = "Displays the list of Pulsar Functions with status summary",
           response = FunctionSummary.class,
           responseContainer = "List"
   )
   @Produces(MediaType.APPLICATION_JSON)
   @Path("/{tenant}/{namespace}/status-summary")
   public List<FunctionSummary> listFunctionsWithStatus(
           final @PathParam("tenant") String tenant,
           final @PathParam("namespace") String namespace) {
       return functions().listFunctionsWithStatus(
               tenant, namespace, uri.getRequestUri(), authParams());
   }
   ```
   
   **Step 5: Implement Worker service logic**
   
   File: `pulsar-functions/worker/.../ComponentImpl.java`
   
   ```java
   public List<FunctionSummary> listFunctionsWithStatus(
           final String tenant, final String namespace,
           final URI uri, final AuthenticationParameters authParams) {
       // impl listFunctionsWithStatus
   }
   ```
   
   > Note: `getComponentStatus()` is an existing method in `ComponentImpl` that 
handles inter-worker RPCs internally. By
   > calling it server-side, we avoid N external HTTP round-trips.
   
   **Step 6: Modify CLI command**
   
   File: `pulsar-client-tools/.../CmdFunctions.java` — `ListFunctions` class 
   
   ```java
   
   @Command(description = "List all Pulsar Functions running under a specific 
tenant and namespace")
   class ListFunctions extends NamespaceCommand {
       @Option(names = "--state", description = "Filter by state: RUNNING, 
STOPPED, PARTIAL")
       private String state;
   
       @Option(names = {"-l", "--long"}, description = "Show extended output 
with status")
       private boolean longFormat;
   
       @Override
       void runCmd() throws Exception {
           List<String> functions = getAdmin().functions().getFunctions(tenant, 
namespace);
   
           if (state == null && !longFormat) {
               print(functions); // Original behavior unchanged
               return;
           }
   
           for (String fn : functions) {
               FunctionStatus status = getAdmin().functions()
                       .getFunctionStatus(tenant, namespace, fn);
               String fnState = deriveState(status);
   
               if (state != null && !state.equalsIgnoreCase(fnState)) {
                   continue;
               }
               if (longFormat) {
                   System.out.printf("%-30s %-10s %d/%d%n",
                           fn, fnState, status.numRunning, status.numInstances);
               } else {
                   print(fn);
               }
           }
       }
   }
   ```
   
    
   ---
   
   ### Approach C: Hybrid (Server Batch Retrieval + Client-Side Filtering) — 
Recommended
   
   Server provides a single batch API that returns all functions with their 
status summaries. The CLI consumes this API and
   handles all filtering (`--state`) and formatting (`--long`) logic on the 
client side.
   
   **Key difference from Approach B:** Approach B frames the server as the 
"aggregation + filtering" layer. Approach C
   explicitly separates concerns — the server is a **data provider** only 
(batch status retrieval), while the client owns *
   *all presentation logic** (filtering, sorting, formatting). This keeps the 
server endpoint generic and reusable.
   
   #### Architecture
   
   ```
   ┌─────────────────────────────────────────────────────┐
   │  CLI (Client)                                       │
   │  ┌───────────┐   ┌──────────┐   ┌───────────────┐  │
   │  │ --state   │──▶│ Filter   │──▶│ Print (plain   │  │
   │  │ --long    │   │ by state │   │ or table fmt)  │  │
   │  └───────────┘   └──────────┘   └───────────────┘  │
   │        ▲                                            │
   │        │  Single API call                           │
   └────────┼────────────────────────────────────────────┘
            │
   ┌────────┼────────────────────────────────────────────┐
   │  Server (Worker)                                    │
   │  ┌─────────────────────────────────────────────┐    │
   │  │ GET /status-summary                         │    │
   │  │ → list metadata → batch getComponentStatus  │    │
   │  │ → return List<FunctionSummary> (ALL funcs)  │    │
   │  └─────────────────────────────────────────────┘    │
   └─────────────────────────────────────────────────────┘
   ```
   
   #### Implementation Steps
   
   **Step 1: Add DTO data model** (same as Approach B)
   
   **Step 2: Extend Admin API interface** (same as Approach B)
   
   **Step 3: Implement Admin API client** (same as Approach B)
   
   **Step 4: Add REST endpoint** (same as Approach B)
   
   **Step 5: Implement Worker service logic** (same as Approach B)
   
   **Step 6: Modify CLI command — filtering and formatting on client side**
   
   File: `pulsar-client-tools/.../CmdFunctions.java` — `ListFunctions` class
   
   This is where Approach C differs from B: **all filtering and formatting 
logic lives in the CLI**, not on the server.
   
   ```java
   
   @Command(description = "List all Pulsar Functions running under a specific 
tenant and namespace")
   class ListFunctions extends NamespaceCommand {
       @Option(names = "--state",
               description = "Filter by state: RUNNING, STOPPED, PARTIAL")
       private String state;
   
       @Option(names = {"-l", "--long"},
               description = "Show extended output with status")
       private boolean longFormat;
   
       @Override
       void runCmd() throws Exception {
           // No flags → original behavior, original API
           if (state == null && !longFormat) {
               print(getAdmin().functions().getFunctions(tenant, namespace));
               return;
           }
   
           // Single batch call to get all summaries
           List<FunctionSummary> summaries = getAdmin().functions()
                   .getFunctionsWithStatus(tenant, namespace);
   
           // Client-side filtering
           if (state != null) {
               summaries = summaries.stream()
                       .filter(s -> state.equalsIgnoreCase(s.getState()))
                       .collect(Collectors.toList());
           }
   
           // Client-side formatting
           if (longFormat) {
               System.out.printf("%-30s %-10s %s%n",
                       "NAME", "STATE", "INSTANCES");
               for (FunctionSummary s : summaries) {
                   System.out.printf("%-30s %-10s %d/%d%n",
                           s.getName(), s.getState(),
                           s.getNumRunning(), s.getNumInstances());
               }
           } else {
               summaries.forEach(s -> print(s.getName()));
           }
       }
   }
   ```
   
     
   
   ## 5. Side-by-Side Comparison
   
   | Dimension                     | B: Server-Side         | C: Hybrid 
(Recommended) |
   
|-------------------------------|------------------------|-------------------------|
   | New API endpoints             | 1                      | 1                 
      |
   | Files changed                 | 7                      | 7                 
      |
   | Network calls (client→server) | 1                      | 1                 
      |
   | Status aggregation            | Worker                 | Worker            
      |
   | Filtering location            | Server                 | **CLI**           
      |
   | Formatting location           | Server                 | **CLI**           
      |
   | Non-CLI clients benefit       | Yes                    | Yes               
      |
   | Server endpoint reusability   | Coupled to CLI needs   | **Generic**       
      |
   | Performance (100 functions)   | 1 HTTP + internal RPCs | 1 HTTP + internal 
RPCs  |
   | Backward compatibility        | Full                   | Full              
      |
   | Complexity                    | Medium                 | Medium            
      |
   
   ## 6. Recommendation
   
   **Approach C (Hybrid) is recommended** for the following reasons:
   
   1. **Clean separation of concerns** — Server is a pure data provider; CLI 
owns all presentation logic. This makes the
      API reusable for other consumers with different filtering/formatting 
needs.
   
   2. **Performance** — Server-side `getComponentStatus()` uses internal worker 
RPCs, far faster than N external HTTP calls
      from the client.
   
   3. **Extensibility** — Adding new CLI filters (e.g., `--name-pattern`) 
requires zero server changes.
   
   4. **Precedent** — Consistent with Pulsar's existing API patterns (dedicated 
endpoints for enriched data).
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to