Skip to content

Commit

Permalink
refactor(metering, billing)!: update metering interface, provider and…
Browse files Browse the repository at this point in the history
… simplify function interfaces (#91)

This commit adds missing functionality for the metering interface and
provider. It also refactors the interfaces for metering and billing
functions to simplify and standardize the way they are defined and
triggered.

Key changes:

1. Added missing functionality for managing meters and fixed GET
   /meters/{meterId} path.

2. Introduced new interfaces `IASyncFunction` and `ISyncFunction` to
   represent async functions (triggered by events) and sync functions
(triggered by API Gateway routes), respectively.

3. Updated the `IBilling` and `IMetering` interfaces to use the new
   `IASyncFunction` and `ISyncFunction` interfaces for defining their
respective functions.

4. Removed the `IFunctionTrigger` interface and the `createEventTarget`
   utility function, as their functionality is now covered by the new
interfaces and the `IEventManager.addTargetToEvent` method.

5. Added a new `AddTargetToEventProps` interface to encapsulate the
   properties required for adding a target to an event in the
`IEventManager.addTargetToEvent` method.

6. Introduced a new `IFunctionPath` interface to represent functions
   triggered by API Gateway routes, including the path and the function
handler.
  • Loading branch information
suhussai authored Aug 29, 2024
1 parent 76a8d16 commit 9f1f6bf
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 275 deletions.
402 changes: 257 additions & 145 deletions API.md

Large diffs are not rendered by default.

52 changes: 35 additions & 17 deletions src/control-plane/billing/billing-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { Schedule } from 'aws-cdk-lib/aws-events';
import { IFunction } from 'aws-cdk-lib/aws-lambda';
import { IFunctionTrigger } from '../../utils';
import { IASyncFunction } from '../../utils';
import { IDataIngestorAggregator } from '../ingestor-aggregator/ingestor-aggregator-interface';

/**
Expand All @@ -19,36 +19,57 @@ export interface IFunctionSchedule {
/**
* The schedule that will trigger the handler function.
*/
readonly schedule: Schedule;
readonly schedule?: Schedule;
}

/**
* Interface that allows specifying both the function to trigger
* and the path on the API Gateway that triggers it.
*/
export interface IFunctionPath {
/**
* The path to the webhook resource.
*/
readonly path: string;

/**
* The function definition.
*/
readonly handler: IFunction;
}

/**
* Encapsulates the list of properties for an IBilling construct.
*/
export interface IBilling {
/**
* The async function responsible for creating a new customer.
* The function to trigger to create a new customer.
* (Customer in this context is an entity that has zero or more Users.)
* -- Default event trigger: ONBOARDING_REQUEST
*/
createCustomerFunction: IFunction | IFunctionTrigger;
createCustomerFunction: IASyncFunction;

/**
* The function to trigger to delete a customer.
* The async function responsible for deleting an existing customer.
* (Customer in this context is an entity that has zero or more Users.)
* -- Default event trigger: OFFBOARDING_REQUEST
*/
deleteCustomerFunction: IFunction | IFunctionTrigger;
deleteCustomerFunction: IASyncFunction;

/**
* The function to trigger to create a new user.
* The async function responsible for creating a new user.
* (User in this context is an entity that belongs to a Customer.)
* -- Default event trigger: TENANT_USER_CREATED
*/
createUserFunction?: IFunction | IFunctionTrigger;
createUserFunction?: IASyncFunction;

/**
* The function to trigger to delete a user.
* The async function responsible for deleting an existing user.
* (User in this context is an entity that belongs to a Customer.)
* -- Default event trigger: TENANT_USER_DELETED
*/
deleteUserFunction?: IFunction | IFunctionTrigger;
deleteUserFunction?: IASyncFunction;

/**
* The IDataIngestorAggregator responsible for accepting and aggregating
Expand All @@ -57,18 +78,15 @@ export interface IBilling {
ingestor?: IDataIngestorAggregator;

/**
* The function responsible for taking the aggregated data and pushing
* The async function responsible for taking the aggregated data and pushing
* that to the billing provider.
* -- Default event trigger: events.Schedule.rate(cdk.Duration.hours(24))
*/
putUsageFunction?: IFunction | IFunctionSchedule;
putUsageFunction?: IFunctionSchedule;

/**
* The function to trigger when a webhook request is received.
* -- POST /billing/{$webhookPath}
*/
webhookFunction?: IFunction;

/**
* The path to the webhook resource.
*/
webhookPath?: string;
webhookFunction?: IFunctionPath;
}
35 changes: 13 additions & 22 deletions src/control-plane/billing/billing-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as targets from 'aws-cdk-lib/aws-events-targets';
import { NagSuppressions } from 'cdk-nag';
import { Construct } from 'constructs';
import { IBilling } from './billing-interface';
import { DetailType, IEventManager, addTemplateTag, createEventTarget } from '../../utils';
import { DetailType, IEventManager, addTemplateTag } from '../../utils';

/**
* Encapsulates the list of properties for a BillingProvider.
Expand Down Expand Up @@ -74,28 +74,19 @@ export class BillingProvider extends Construct {
functionDefinition: props.billing.deleteUserFunction,
},
].forEach((target) => {
createEventTarget(
this,
props.eventManager,
target.defaultFunctionTrigger,
target.functionDefinition
);
if (target.functionDefinition?.handler) {
props.eventManager.addTargetToEvent(this, {
eventType: target.functionDefinition?.trigger || target.defaultFunctionTrigger,
target: new targets.LambdaFunction(target.functionDefinition?.handler),
});
}
});

if (props.billing.putUsageFunction) {
const schedule =
'handler' in props.billing.putUsageFunction
? props.billing.putUsageFunction.schedule
: events.Schedule.rate(cdk.Duration.hours(24));

const handler =
'handler' in props.billing.putUsageFunction
? props.billing.putUsageFunction.handler
: props.billing.putUsageFunction;

new events.Rule(this, 'BillingPutUsageRule', {
schedule: schedule,
targets: [new targets.LambdaFunction(handler)],
schedule:
props.billing.putUsageFunction.schedule || events.Schedule.rate(cdk.Duration.hours(24)),
targets: [new targets.LambdaFunction(props.billing.putUsageFunction.handler)],
});
}

Expand All @@ -106,15 +97,15 @@ export class BillingProvider extends Construct {
});
}

if (props.billing.webhookFunction && props.billing.webhookPath) {
this.controlPlaneAPIBillingWebhookResourcePath = `/billing/${props.billing.webhookPath}`;
if (props.billing.webhookFunction) {
this.controlPlaneAPIBillingWebhookResourcePath = `/billing/${props.billing.webhookFunction.path}`;

const [controlPlaneAPIBillingWebhookResourceRoute] = props.controlPlaneAPI.addRoutes({
path: this.controlPlaneAPIBillingWebhookResourcePath,
methods: [apigatewayV2.HttpMethod.POST],
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'billingWebhookHttpLambdaIntegration',
props.billing.webhookFunction
props.billing.webhookFunction.handler
),
});

Expand Down
58 changes: 28 additions & 30 deletions src/control-plane/metering/metering-interface.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,76 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { IFunction } from 'aws-cdk-lib/aws-lambda';
import { IFunctionTrigger } from '../../utils';
import { IASyncFunction, ISyncFunction } from '../../utils';

/**
* Encapsulates the list of properties for an IMetering construct.
*/
export interface IMetering {
/**
* The function to trigger to create a meter -- POST /meters
* The sync function responsible for creating a meter.
* Once created, the meter can be used to track and analyze the specific usage metrics for tenants.
* -- POST /meters
*/
createMeterFunction: IFunction;
createMeterFunction: ISyncFunction;

/**
* The scope required to authorize requests for creating a new meter.
* The sync function responsible for fetching a single a meter based on its id.
* -- GET /meters/{meterId}
*/
createMeterScope?: string;
fetchMeterFunction: ISyncFunction;

/**
* The function to trigger to update a meter -- PUT /meters/meterId
* The sync function responsible for fetching multiple meters. This should support pagination.
* -- GET /meters
*/
updateMeterFunction?: IFunction;
fetchAllMetersFunction: ISyncFunction;

/**
* The scope required to authorize requests for updating a meter.
* The sync function responsible for updating a meter.
* -- PUT /meters/{meterId}
*/
updateMeterScope?: string;
updateMeterFunction?: ISyncFunction;

/**
* The function to trigger to ingest a usage event.
* Usage events are used to measure and track the usage metrics associated with the meter.
*
* Default event trigger: INGEST_USAGE
* The sync function responsible for deleting a meter.
* -- DELETE /meters/{meterId}
*/
ingestUsageEventFunction: IFunction | IFunctionTrigger;
deleteMeterFunction?: ISyncFunction;

/**
* The function to trigger to get the usage data that has been recorded for a specific meter.
* -- GET /usage/meterId
* The async function responsible for ingesting a usage event.
* Usage events are used to measure and track the usage metrics associated with the meter.
* -- Default event trigger: INGEST_USAGE
*/
fetchUsageFunction: IFunction; // use 'fetch*' instead of 'get*' to avoid error JSII5000
ingestUsageEventFunction: IASyncFunction;

/**
* The scope required to authorize requests for fetching metering usage.
* The function responsible for getting usage data for a specific meter.
* -- GET /usage/{meterId}
*/
fetchUsageScope?: string;
fetchUsageFunction: ISyncFunction; // use 'fetch*' instead of 'get*' to avoid error JSII5000

/**
* The function to trigger to exclude specific events from being recorded or included in the usage data.
* Used for canceling events that were incorrectly ingested.
* -- DELETE /usage
*/
cancelUsageEventsFunction?: IFunction;

/**
* The scope required to authorize requests for cancelling usage events.
*/
cancelUsageEventsScope?: string;
cancelUsageEventsFunction?: ISyncFunction;

/**
* The function to trigger to create a new customer.
* The function responsible for creating a new customer.
* (Customer in this context is a tenant.)
*
* Default event trigger: ONBOARDING_REQUEST
*/
createCustomerFunction?: IFunction | IFunctionTrigger;
createCustomerFunction?: IASyncFunction;

/**
* The function to trigger to delete a customer.
* The function responsible for deleting an existing customer.
* (Customer in this context is a tenant.)
*
* Default event trigger: OFFBOARDING_REQUEST
*/
deleteCustomerFunction?: IFunction | IFunctionTrigger;
deleteCustomerFunction?: IASyncFunction;
}
64 changes: 47 additions & 17 deletions src/control-plane/metering/metering-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import * as apigatewayV2 from 'aws-cdk-lib/aws-apigatewayv2';
import * as apigatewayV2Integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import { IFunction } from 'aws-cdk-lib/aws-lambda';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import { Construct } from 'constructs';
import { IMetering } from './metering-interface';
import * as utils from '../../utils';
Expand Down Expand Up @@ -46,7 +46,7 @@ export class MeteringProvider extends Construct {
const metersPath = '/meters';
const functionTriggerMappings: {
defaultFunctionTrigger: utils.DetailType;
functionDefinition?: IFunction | utils.IFunctionTrigger;
functionDefinition?: utils.IASyncFunction;
}[] = [
{
defaultFunctionTrigger: utils.DetailType.ONBOARDING_REQUEST,
Expand All @@ -63,32 +63,50 @@ export class MeteringProvider extends Construct {
];

functionTriggerMappings.forEach((target) => {
utils.createEventTarget(
this,
props.eventManager,
target.defaultFunctionTrigger,
target.functionDefinition
);
if (target.functionDefinition?.handler) {
props.eventManager.addTargetToEvent(this, {
eventType: target.functionDefinition?.trigger || target.defaultFunctionTrigger,
target: new targets.LambdaFunction(target.functionDefinition?.handler),
});
}
});

const routes: utils.IRoute[] = [
{
path: `${usagePath}/meterId`,
path: `${usagePath}/{meterId}`,
method: apigatewayV2.HttpMethod.GET,
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'fetchUsageHttpLambdaIntegration',
props.metering.fetchUsageFunction
props.metering.fetchUsageFunction.handler
),
scope: props.metering.fetchUsageScope,
scope: props.metering.fetchUsageFunction.scope,
},
{
path: `${metersPath}/{meterId}`,
method: apigatewayV2.HttpMethod.GET,
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'fetchMeterHttpLambdaIntegration',
props.metering.fetchMeterFunction.handler
),
scope: props.metering.fetchMeterFunction.scope,
},
{
path: metersPath,
method: apigatewayV2.HttpMethod.GET,
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'fetchAllMetersHttpLambdaIntegration',
props.metering.fetchAllMetersFunction.handler
),
scope: props.metering.fetchAllMetersFunction.scope,
},
{
path: metersPath,
method: apigatewayV2.HttpMethod.POST,
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'createMeterHttpLambdaIntegration',
props.metering.createMeterFunction
props.metering.createMeterFunction.handler
),
scope: props.metering.createMeterScope,
scope: props.metering.createMeterFunction.scope,
},
];

Expand All @@ -98,9 +116,9 @@ export class MeteringProvider extends Construct {
method: apigatewayV2.HttpMethod.DELETE,
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'deleteUsageHttpLambdaIntegration',
props.metering.cancelUsageEventsFunction
props.metering.cancelUsageEventsFunction.handler
),
scope: props.metering.cancelUsageEventsScope,
scope: props.metering.cancelUsageEventsFunction.scope,
});
}

Expand All @@ -110,9 +128,21 @@ export class MeteringProvider extends Construct {
method: apigatewayV2.HttpMethod.PUT,
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'updateMeterHttpLambdaIntegration',
props.metering.updateMeterFunction
props.metering.updateMeterFunction.handler
),
scope: props.metering.updateMeterFunction.scope,
});
}

if (props.metering.deleteMeterFunction) {
routes.push({
path: `${metersPath}/{meterId}`,
method: apigatewayV2.HttpMethod.DELETE,
integration: new apigatewayV2Integrations.HttpLambdaIntegration(
'deleteMeterHttpLambdaIntegration',
props.metering.deleteMeterFunction.handler
),
scope: props.metering.updateMeterScope,
scope: props.metering.deleteMeterFunction.scope,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ export class TenantManagementService extends Construct {
DetailType.DEPROVISION_SUCCESS,
DetailType.DEPROVISION_FAILURE,
].forEach((detailType) => {
props.eventManager.addTargetToEvent(this, detailType, tenantUpdateServiceTarget);
props.eventManager.addTargetToEvent(this, {
eventType: detailType,
target: tenantUpdateServiceTarget,
});
});

this.table = table;
Expand Down
Loading

0 comments on commit 9f1f6bf

Please sign in to comment.