constructor()

in cdk/lib/bigquery-acquisitions-publisher.ts [21:147]


  constructor(scope: App, id: string, props: GuStackProps) {
    super(scope, id, props);

    const busName = `acquisitions-bus-${props.stage}`;

    // Event bus
    const eventBus = new EventBus(this, busName, {
      eventBusName: busName,
    });

    new Archive(this, `${busName}-archive`, {
      eventPattern: {
        account: ["account"],
        region: ["eu-west-1"],
      },
      sourceEventBus: eventBus,
      archiveName: `${busName}-archive`,
      description: `Archive for all events sent to ${busName}-archive`,
      retention: Duration.days(90),
    });

    // SQS Queues
    const queueName = `${appName}-queue-${props.stage}`;
    const deadLetterQueueName = `dead-letters-${appName}-${props.stage}`;

    const deadLetterQueue = new Queue(this, `dead-letters-${appName}Queue`, {
      queueName: deadLetterQueueName,
      retentionPeriod: Duration.days(14),
    });

    const queue = new Queue(this, `${appName}Queue`, {
      queueName,
      visibilityTimeout: Duration.minutes(2),
      deadLetterQueue: {
        // The number of times a message can be unsuccessfully dequeued before being moved to the dead-letter queue.
        // This has been set to 1 to avoid duplicate acquisition events being send to bigquery
        maxReceiveCount: 1,
        queue: deadLetterQueue,
      },
    });

    // Rule which passes events on to SQS
    new Rule(this, "EventBusToSQSRule", {
      description: "Send all events to SQS",
      eventPattern: {
        region: ["eu-west-1"],
      },
      eventBus: eventBus,
      targets: [new SqsQueue(queue)],
    });

    // Create a custom role because the name needs to be short, otherwise the request to Google Cloud fails
    const role = new Role(this, "bigquery-to-s3-role", {
      roleName: `bq-acq-${this.stage}`,
      assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
    });
    role.addToPolicy(
      new PolicyStatement({
        actions: [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
        ],
        resources: ["*"],
      })
    );
    role.addToPolicy(
      new PolicyStatement({
        actions: ["ssm:GetParameter"],
        resources: [
          `arn:aws:ssm:${this.region}:${this.account}:parameter/${appName}/${props.stage}/gcp-wif-credentials-config`,
        ],
      })
    );

    const monitoring =
      this.stage == "PROD"
        ? {
            toleratedErrorPercentage: 0,
            snsTopicName: `alarms-handler-topic-${this.stage}`,
            alarmName: "big-query-acquisition-publisher lambda has failed",
            alarmDescription:
              "Check the logs for details https://eu-west-1.console.aws.amazon.com/cloudwatch/home?region=eu-west-1#logsV2:log-groups/log-group/$252Faws$252Flambda$252Fbigquery-acquisitions-publisher-PROD",
          }
        : undefined;

    // SQS to Lambda event source mapping
    const eventSource = new SqsEventSource(queue, {
      reportBatchItemFailures: true,
    });

    const functionName = `${appName}-${props.stage}`;

    new GuLambdaFunction(this, `${appName}Lambda`, {
      app: appName,
      runtime: Runtime.NODEJS_20_X,
      fileName: `index.zip`,
      functionName,
      handler: "index.handler",
      events: [eventSource],
      timeout: Duration.minutes(2),
      role,
      errorPercentageMonitoring: monitoring,
      loggingFormat: LoggingFormat.TEXT,
    });

    new GuAlarm(this, "DeadLetterQueueAlarm", {
      app: appName,
      alarmName: `The ${props.stage} ${appName} lambda has failed`,
      alarmDescription:
        `There is one or more event in the ${deadLetterQueueName} dead letter queue (DLQ). ` +
        "Check the logs for the error and use the details to confirm that the event was not written " +
        "to the fact_acquisition_event table in BigQuery. If the event is not in the table then use the DLQ " +
        "redrive feature to replay the failed event. If the redrive functionality is not used " +
        "then purge the queue instead or the alarm will remain in an alarm state.\n" +
        `Main queue: https://eu-west-1.console.aws.amazon.com/sqs/v2/home?region=eu-west-1#/queues/https%3A%2F%2Fsqs.eu-west-1.amazonaws.com%2F865473395570%2F${queueName}\n` +
        `DLQ: https://eu-west-1.console.aws.amazon.com/sqs/v2/home?region=eu-west-1#/queues/https%3A%2F%2Fsqs.eu-west-1.amazonaws.com%2F865473395570%2F${deadLetterQueueName}\n` +
        `Logs: https://eu-west-1.console.aws.amazon.com/cloudwatch/home?region=eu-west-1#logsV2:log-groups/log-group/$252Faws$252Flambda$252F${functionName}`,
      metric: deadLetterQueue.metricApproximateNumberOfMessagesVisible(),
      threshold: 1,
      evaluationPeriods: 1,
      comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
      treatMissingData: TreatMissingData.IGNORE,
      snsTopicName: `alarms-handler-topic-${this.stage}`,
      actionsEnabled: this.stage === "PROD",
    });
  }