Similarly to https://github.com/elastic/elasticsearch/pull/97873 but for request handlers: let's stop passing around strings and looking these things up by name on each request when we could just pass around the `Executor` itself.
server/src/main/java/org/elasticsearch/action/support/nodes/TransportNodesAction.java
* @param failures All node-level failures.
     * @return Never {@code null}.
     * @throws NullPointerException if any parameter is {@code null}.
     */
    protected abstract NodesResponse newResponse(NodesRequest request, List<NodeResponse> responses, List<FailedNodeException> failures);
    /**
     * Create a new {@link NodesResponse}, possibly asynchronously. The default implementation is synchronous and calls
     * {@link #newResponse(BaseNodesRequest, List, List)}
     */
    protected void newResponseAsync(
        Task task,
        NodesRequest request,
        List<NodeResponse> responses,
        List<FailedNodeException> failures,
        ActionListener<NodesResponse> listener
    ) {
        ActionListener.completeWith(listener, () -> newResponse(request, responses, failures));
    }
    protected abstract NodeRequest newNodeRequest(NodesRequest request);
    protected abstract NodeResponse newNodeResponse(StreamInput in, DiscoveryNode node) throws IOException;
    protected abstract NodeResponse nodeOperation(NodeRequest request, Task task);
    /**
     * resolve node ids to concrete nodes of the incoming request
     **/
    protected void resolveRequest(NodesRequest request, ClusterState clusterState) {
        assert request.concreteNodes() == null : "request concreteNodes shouldn't be set";
        String[] nodesIds = clusterState.nodes().resolveNodes(request.nodesIds());
        request.setConcreteNodes(Arrays.stream(nodesIds).map(clusterState.nodes()::get).toArray(DiscoveryNode[]::new));
    }
    class NodeTransportHandler implements TransportRequestHandler<NodeRequest> {