Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQS Queue type-safe integration class #58

Closed
sam-goodwin opened this issue Mar 30, 2022 · 16 comments · Fixed by #428
Closed

SQS Queue type-safe integration class #58

sam-goodwin opened this issue Mar 30, 2022 · 16 comments · Fixed by #428
Assignees
Labels
Projects

Comments

@sam-goodwin
Copy link
Collaborator

sam-goodwin commented Mar 30, 2022

Provide a wrapper class, Queue<T>, that models the SQS Queue Functionless integrations.

Must integrates with the following:

  1. AppSync
const queue = new Queue<string>(new aws_sqs.Queue(..))

new AppsyncResolver<{id: string}>($context => {
  queue.sendMessage({
     MessageBody: id,
     MessageAttributes: ..
  });
});
  1. Step Functions
new StepFunction(scope, id, (id: string) => {
  queue.sendMessage({
     MessageBody: id,
     MessageAttributes: ..
  })
})
  1. Event Bridge
bus
  .when(scope, "rule", event => event.source === "lambda")
  .map(event => event.message)
  .pipe(queue)
  1. Lambda
new Function(scope, id, async () => {
  await queue.sendMessage(..);
});
@thantos thantos added the sqs label Mar 30, 2022
@sam-goodwin sam-goodwin added this to To do in Release May 20, 2022
@sam-goodwin sam-goodwin self-assigned this May 28, 2022
@sam-goodwin sam-goodwin changed the title SQS Queue type-safe and Functionless-enabled wrapper. SQS Queue type-safe integration class Jun 9, 2022
@sam-goodwin
Copy link
Collaborator Author

How should we go about layering this?

  1. L1 Queue - no type information, all APIs use the vanilla AWS SDK APIs, e.g. MessageBody: string
  2. L2 Queue<T> - captures type information and provides serialization/deserialization utility?

For supporting an arbitrary T, we will need some Codec<T> type that encapsulates serialization/deserialization logic.

export interface ReadCodec<Value, Data> {
  read(data: Data): Value;
}
export interface WriteCodec<Value, Data> {
  write(value: Value): Data;
}
export interface Codec<Value, Data>
  extends ReadCodec<Value, Data>,
    WriteCodec<Value, Data> {}

export class JsonCodec<T> implements Codec<T, string> {
  public read(data: string): T {
    return JSON.parse(data);
  }
  public write(value: T): string {
    return JSON.stringify(value);
  }
}

@thantos
Copy link
Collaborator

thantos commented Aug 20, 2022

Could we do the same contract as secret?

Textqueue and jsonqueue?

Roll parse into the integration?

@sam-goodwin
Copy link
Collaborator Author

Could we do the same contract as secret?

Textqueue and jsonqueue?

Roll parse into the integration?

I was thinking the same, although we may need to consider cases like Avro, ProtoBuf, Flatbuffers, etc. So i was wondering if we could have a Codec abstraction - quickly run into limitations with closure serialization and this though.

@sam-goodwin
Copy link
Collaborator Author

That said, JsonSecret likely covers 99% of cases ha

@thantos
Copy link
Collaborator

thantos commented Aug 20, 2022

Keep it simple? Anyone can make another overload and more l2s?

@sam-goodwin
Copy link
Collaborator Author

for JsonQueue<T>, where should we place the parsed value?

interface SQSRecord<T> {
  body: string; // <- this is the vanilla AWS API
  value: T; // <- maybe we add a new field? Or should we override body?
}

@thantos
Copy link
Collaborator

thantos commented Aug 20, 2022

Make it explicit to not polite the sfn state?

@sam-goodwin
Copy link
Collaborator Author

Make it explicit to not polite the sfn state?

I don't know what you mean?

@sam-goodwin
Copy link
Collaborator Author

Hmm, if this is the interface

public forEach(
    handler: Function<SQSEvent<T>, lambda.SQSBatchResponse>,
    props?: aws_lambda_event_sources.SqsEventSourceProps
  ): void {
    handler.resource.addEventSource(
      new aws_lambda_event_sources.SqsEventSource(this.resource, props)
    );
  }

And we want to inject code that parses the body string, how would we inject that into the Function?

Should we instead change the interface to be

public forEach(scope: Construct, id: string, props: FunctionProps & SqsEventSourceProps, handle: (event: ..) => ..)

And then have the forEach instantiate the Function?

@sam-goodwin
Copy link
Collaborator Author

Yeah, I think this is better! We can provide an escape hatch if folk want to pass a pre-existing function

image

@thantos
Copy link
Collaborator

thantos commented Aug 20, 2022

Looks good to me, and make the scope optional?

@sam-goodwin
Copy link
Collaborator Author

Looks good to me, and make the scope optional?

Yep, even id can default to "forEach"

@sam-goodwin
Copy link
Collaborator Author

Looks good to me, and make the scope optional?

Yep, even id can default to "forEach"

Which would work in 99% of cases because it's not like you'd ever attach two consumers

@thantos
Copy link
Collaborator

thantos commented Aug 20, 2022

let n = 0;
let name = "forEach";
do {
   const child = queue.node.tryFindChild(name);
   if(!child) {
      break;
   }
  name = `forEach${++n}`;
} while(true);

@sam-goodwin
Copy link
Collaborator Author

let n = 0;

let name = "forEach";

do {

   const child = queue.node.tryFindChild(name);

   if(!child) {

      break;

   }

  name = `forEach${++n}`;

} while(true);

Sure that would work. But it has risks because of you remove one in the middle they all change.

@thantos
Copy link
Collaborator

thantos commented Aug 20, 2022

Uhhgg, yeah, good point. Lets enforce the singleton when the automatic name is used?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
No open projects
Release
To do
Development

Successfully merging a pull request may close this issue.

2 participants