<CDK Day> live now if you’re interested in any of ...
# general
e
CDK Day live now if you’re interested in any of the talks. Here are the talk tracks: https://www.cdkday.com/ Keynote has just started.
t
@Akos from our community is speaking!
a
Yep, on track 3 in about 15 mins!
e
Nice, I’ll be watching that one
Wooo! There he is!
j
Awesome!
e
SST plug 🙂
a
WTF???
Are we seeing the end of CFormation?
e
Nice one @Akos
a
Thanks!
And yeah, there should have been a whole 20 mins dedicated to SST 🙂
e
I pulled my colleague in to watch - we still have a lot of Serverless Framework but in other respects we have basically the same problem to solve. So far we’ve expanded our “unit tests” to talk to real services (e.g. DynamoDB) rather than mocking - which has been a big improvement. But we have no post-deploy integration tests at all yet.
a
Are you using seed.run?
e
No. I've been eyeing it up, but I'm not sure I can justify switching and there are a few things I'm not happy with. We're on GitLab CI. Auto deploy to Staging and then currently manual gate to Prod. Ideally I'd like to lose the manual gate.
a
Or anything that reports deployments to GitHub commits? You can just use a wait-for-deployment github action (we copy pasted it over to circleci) and then cache cloudformation outputs with a simple script like this.
Ah, not too familiar with Gitlab stuff.
t
cdk without cloudformation is likely just making calls to Cloud Control API
.....which is what cfn uses under the hood so it's gonna be slow still
a
I think they say uses the AWS API.
t
where did you see that btw
a
This was presented by Pulumi team.
t
ah yeah it uses cloud control api
a
Oh ok.
So there is no CF stack?
t
no cf stack
a
Mmm, ok.
t
Terraform's aws provider is still the fastest because it makes normal aws api calls
a
My mind is already wrap around CFormation.
r
Great talk Akos! 👏 I'm wondering if there is any starter/example project that would have some of the integration testing patterns setup and ready to use/provided as a guideline or showing possible approaches (for example the EventBridge catch-all to Dynamo, which I find really smart). I'm working on a serverless workflows on a daily basis, but they're far from the approach covered in the talk or in the article with magical AWS DX. At the same time, I appreciate the idea you described and I feel like it's the way to go that gives a great confidence when shipping to prod 😉
a
@Robert Banaszak I'm actually on vacation from today for the next two weeks, so can't put anything together, but I had this repo for a different talk prepared: https://github.com/team-plain/aws-summit-stockholm-demo I can add the eventbridge events when I'm back. Other than that the code on the slides should cover the gist of the solution:
Copy code
if (env.iTestCapabilityEnabled) {
  const table = new sst.Table(scope, 'IntegrationTestEvents', {
    fields: {
      workspaceId: sst.TableFieldType.STRING,
      eventId: sst.TableFieldType.STRING,
    },
    primaryIndex: { partitionKey: 'workspaceId', sortKey: 'eventId' },
    dynamodbTable: {
      timeToLiveAttribute: 'expiresAt',
      removalPolicy: RemovalPolicy.DESTROY,
    },
  });

  const itestWriterLambda = new sst.Function(scope, 'IntegrationTestEventWriter', {
    handler: 'test-utils/eventing/integrationTestEventWriter.handler',
    environment: {
      DYNAMODB_TABLE_NAME: table.dynamodbTable.tableName,
      EXPIRY_TIME_MINUTES: '60', // 1 hour
    },
  });

  table.dynamodbTable.grantWriteData(itestWriterLambda);

  buses.forEach((bus) => {
    const catchAllRule = new events.Rule(scope, `CatchAll${bus.id}Rule`, {
      eventBus: bus.eventBus,
      eventPattern: {
        account: [cdk.Fn.sub('${AWS::AccountId}')],
      },
      targets: [new targets.LambdaFunction(itestWriterLambda)],
    });
  });
}
And the lambda function to handle it:
Copy code
/* eslint-disable no-console */
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
import { assert } from 'assert-ts';
import { EventBridgeHandler } from 'aws-lambda';
import isSafeInteger from 'lodash/isSafeInteger';
import toInteger from 'lodash/toInteger';
import { DateTime } from 'luxon';
import { createDynamoDbClient } from 'packages/aws-clients';
import { DomainEvent } from 'packages/events';

const dynamoClient = DynamoDBDocument.from(createDynamoDbClient());
const tableName = process.env.DYNAMODB_TABLE_NAME;
const expiryTimeMinutes = process.env.EXPIRY_TIME_MINUTES;
assert(tableName, 'DYNAMODB_TABLE_NAME envvar not defined');
assert(expiryTimeMinutes, 'EXPIRY_TIME_MINUTES envvar not defined');
assert(
  Number(expiryTimeMinutes) > 0 && isSafeInteger(Number(expiryTimeMinutes)),
  'EXPIRY_TIME_MINUTES must be an integer and greater than 0'
);

// Note: this handler just casts the incoming event as a DomainEvent, will blow up if it's
// not in the right format.
export const handler: EventBridgeHandler<string, DomainEvent, void> = async (event, _context) => {
  console.log(`Handling event with AWS id: ${event.id}`);
  const workspaceId = event.detail.workspaceId;
  const eventId = event.detail.eventId;
  console.log(`Inserting event with workspaceId: ${workspaceId} and sk: ${eventId}`);
  const expiresAt = toInteger(
    DateTime.utc()
      .plus({ minutes: Number(expiryTimeMinutes) })
      .toSeconds()
  );
  await dynamoClient.put({
    TableName: tableName,
    Item: {
      ...event,
      workspaceId,
      // set sk as eventId so when we fetch them they are in the order inserted
      sk: eventId,
      expiresAt,
    },
  });
  console.log(`Successfully saved event: ${eventId}`);
};
As you can see, it's a bit specific to our events and our setup. That's the thing with integration testing, it's so unique to your application it's hard to write generic code.
r
Thanks for posting the link and adding the code 🙏 Enjoy vacation and don't worry about that, I got the idea and I'll be able to make it work, thanks!