Opteo / google-ads-node

Google Ads API client library for Node.js
https://opteo.com
66 stars 35 forks source link

Project structure question #99

Open felixmosh opened 2 years ago

felixmosh commented 2 years ago

Hi, I've some time to invest in order to optimize this lib so it will be more suitable for production use (#83 #87 #84)

But I'm not getting the structure of it. I did understood that it gets generated from googles protobuf defs.

  1. What is the relation between build/src folder and the build/protos/protos.js file?
  2. Do we need build/src on production usage? (is google-ads-api uses it?)

Any help will be really appreciated.

hsource commented 1 year ago

The problem is how googleapis builds the protos. It builds it into a single monolithic proto, which is then built into a single Typescript file.

To avoid this, I recommend:

  1. Building the protos with protobuf-ts
  2. Running it with @grpc/grpc-js
  3. Not using this library at all

Building the protos

Here's a file to checkout the googleapis project and build the protos to a folder:

#!/bin/bash

GOOGLE_APIS_PATH="$1"
OUTPUT_DIR="$2"

if [ ! -e "$GOOGLE_APIS_PATH/.git" ]; then
  echo 'Cloning Git repository'
  git clone git@github.com:googleapis/googleapis.git "$GOOGLE_APIS_PATH"
fi

cd "$GOOGLE_APIS_PATH"

if [ ! -e "$GOOGLE_APIS_PATH/node_modules/@protobuf-ts/plugin" ]; then
  echo 'Installing protocol buffer compiler'
  # Install the protoc compiler temporarily in googleapis. We only do this
  # if it doesn't look installed to make rerunning this script faster
  git checkout . && git clean -f -d
  git fetch
  git reset --hard origin/master

  # Add the plugin needed to build googleApisProtos into Typescript files
  yarn init -y
  yarn add @protobuf-ts/plugin
fi

yarn protoc --ts_out "$OUTPUT_DIR" --proto_path . \
  --ts_opt generate_dependencies,long_type_number \
  google/ads/googleads/v11/services/customer_service.proto \
  google/ads/googleads/v11/services/conversion_upload_service.proto

Using the protos

These options will be needed for any endpoint:

import { UserRefreshClient } from 'google-auth-library';
import { credentials as gc } from '@grpc/grpc-js';
import { GrpcTransport } from '@protobuf-ts/grpc-transport';
import { RpcOptions } from '@protobuf-ts/runtime-rpc';

const config = { /* ... */ };

/**
 * These options include required options from Google Ads API documentation on
 * headers:
 * https://developers.google.com/google-ads/api/rest/auth#request_headers
 */
export const googleAdsRpcOptions: RpcOptions = {
  meta: {
    'developer-token': config.developerToken,
    'login-customer-id': config.customerId,
  },
};

/**
 * Creates a GrpcTransport with the signed-in credentials that can be used to
 * access the APIs.
 */
export function getGoogleAdsTransport() {
  const { refreshToken, clientId, clientSecret } = config;

  // Code to create the client and credentials were heavily based on:
  //
  // - google-ads-node library: https://github.com/Opteo/google-ads-api/blob/v11/src/service.ts#L71
  // - GRPC credentials documentation: https://grpc.github.io/grpc/node/grpc.credentials.html
  const authClient = new UserRefreshClient(
    clientId,
    clientSecret,
    refreshToken,
  );

  const credentials = gc.combineChannelCredentials(
    gc.createSsl(),
    gc.createFromGoogleCredential(authClient),
  );

  return new GrpcTransport({
    // URL was found in https://raw.githubusercontent.com/googleapis/googleapis/master/api-index-v1.json
    // Just do a find for "google/ads/googleads" to find the relevant
    // configuration
    host: 'googleads.googleapis.com',
    channelCredentials: credentials,
  });
}

Using the APIs

Pass in the transport to the constructor for the client, and pass in the options to

new CustomerServiceClient(getGoogleAdsTransport()).listAccessibleCustomers({}, googleAdsRpcOptions);

Additional notes

Note that the errors.proto is also huge, so if you only need to use it, you may want to edit it and reduce its size.

felixmosh commented 1 year ago

Thank you @hsource , I think that suggesting to ditch the lib entirely is valid, but in my case it will require much more time to refactor the code since I have a heavy usage of this lib.

Maybe you can somehow incorporate your solution into this lib?