What would you guys think of an entirely functiona...
# random
t
What would you guys think of an entirely functional api to create stacks:
Copy code
export async function TableStack(props) {
  const table = new sst.Table(props.stack, ...);

  return { table };
}

export async function StackB(props) {
  const { table } = use(TableStack);
  new sst.Function(props.stack, { environment:  { MYTABLE: table } })
}
It would be totally typesafe + support async functions
s
this probably comes down to personal preference/coding style, yeah? I know some people are big fans of functional programming. I’m more in the OOP camp.. classes just feel more natural to me
t
A little bit of style but it's also nice to return exports simply with type inference. The above is pretty verbose in sst today because you have to be explicit about all the types. Functional approach infers everything, zero types you have to write
Copy code
class TableStack extends sst.Stack {
  public readonly table: sst.Table
  constructor(...) { 
    super(..)
    this.table = new sst.Table(...)
  }
}

class StackB extends sst.Stack {
  constructor(app, props: { table: sst.Table }) {
    super(...)
  
  }
}

const tableStack = new TableStack(app)
new StackB(app, { table: tableStack.table })
functional approach also allows async and we can auto initialize them in the right order for you so you wouldn't need anything in your main outside of setting defaults
s
I mean, if it adds more functionality to SST, then I’m all for it 🙂
t
we'd probably add this as an experimental syntax
g
This would be amazing
d
This is not a game changer for me. However, I would probably switch. Since I no longer use classes on the frontend and consistency is good for my brain.
j
Nice
m
So async in that
TableStack
gets deployed before
StackB
begins to synth? If not that, then what? CDK is synchronous by design.
(fwiw I already do something similar to this, but not async)
t
No they all get synthed first and then deployed. Because CDK is constructor based you can't have async functions, but this is an arbitrary constraint. It's annoying for us in a few places since we can't call esbuild.build() since it returns a promise and I imagine others have other issues like that as well
m
I got around that with
execSync
, though if somebody said that's a hack, I wouldn't really disagree. I'd just be wary of anything that might make it non-deterministic. Having the whole thing be synchronous was a design goal of the CDK.
r
@thdxr would be into this for sure as I tend to work this way in general. Having async stack construction would be nicer then needing to do all async in index and passing values around.
a
I'm a fan of classes, even if not OOP it helps organize related functions. The fact that stacks (and constructs) have to do all their work in the constructor makes them essentially just functions though, so I like this simplification.
t
@Matt Morgan yeah I don't really know why you'd need async work I just have that one esbuild example where we have to wrap it in execSync. So it's a secondary concern, my primary thing is to make it less verbose for passing things around and get proper typing through inference instead of writing it out
r
async has been needed for SSM for me.
t
Yeah true storing everything in SSM means you don't need to put stuff in your CI environment which is great
r
yeah, thats the route we are going here, we went with SSM injected into the CF instead of calling it at runtime
do you know if you could perform async operations in the class? for example if the constructor called
this.createStack(scope, id)
and
createStack
was async.
t
No you can't
s
@Ross Gerbasi what kinds of things are you injecting into the stack from SSM?
r
Figured, but i am still not sure how it works in
index.ts
How is it that you can have callbacks and promises in your default index handler?? how does it know to wait haha i am very curious
@Sam Hulick secret keys for APIs and tokens and such
SSM -> Stack -> Lambda ENV
t
The limitation is coming from JS technically. Constructors in JS have to be synchronous, it's not an overall limitation in CDK. So we just spawn a normal node process to execute
main
and wait for all async work to finish before exiting
s
interesting. I’ve been fetching those in the Lambda code at runtime. I figured if I injected them into the stack, changing keys would require a redeploy
t
hm ross I don't think that pattern is actually good because your secrets leak in a few places
r
e understand that
and we understand it requires a deploy
j
@Sam Hulick you are doing it the best way I believe
t
We're working on making secret management first class in SST so you can just define them and they'll be auto injected into your function (with typing!) and letting you manage them in console
r
however calling them at runtime adds another delay to the pipeline. Everytime we wanna use that key we need to lookup from SSM first
s
I wrote a
getSsmParameter
function that also memoizes the values it fetches. just cache it so it’s there for any warm starts
r
we would rather secure our CI and be cautious with our CF security VS have every customer pay extra per request
s
the delay is microscopic
r
yeah caching for warm start is an always regardlkess
but we have seen sporadic SSM delays seconds even. Simply not worth it
we will just be better with our stack security
we will likely be using github acttions to deploy our code, if someone manages to get in there and read our CF files we just have plans in place to re-roll keys.
t
I do wish AWS would make a managed way to do this
They can optimize it so everything is injected when the lambda is starting and it's as fast as it can be
r
I agree, Another option is to use cloud formations dynamic SMM references.
We are considering moving to something like this, but again we have weighed our security concerns and are ok with the current path right now
a
Might help my use case? I have an sst.Script (custom resource) just to upload resulting files with endpoints and OpenAPI for clients to access. Would be nice if each API stack could directly upload these files.
r
@thdxr I wonder if SST could help with this. injecting
{{resolve}}
into ENVS so we are never passing alot plaintext secrets
Something like they are doing here, https://stackoverflow.com/a/56040646
t
That's sort of what the solution we're working on does.
well no we still choose to resolve it at runtime because we don't want to expose it in the lambda function's env variables
r
That would likely be ideal, no runtime cost + secure secrets all the way down.
t
those are readable in a lot of places
We could probably add an option to disable runtime resolving
r
I am not sure I am as worried about ENV variables though. They are encrypted at rest anyway
If someone can get into AWS to see them, they can likely cause more damage. I guess you could make the case maybe a dev has access to a lambda but not SSM.
t
Yeah I'm not opinionated on this, depending on your setup you might choose to be more practical. We can probably make your setup more built in
r
That would be great 🙂 Obviously my workflow doesnt work for folks that are rotating keys. There is likely a mix here folks could use. some "static" keys and some dynamic ones.
c
that’d be great - specially because I’ve been ignoring linter errors when creating stacks with classes that wouldn’t happen if we had this funcional approach
a
Just ran across this @Ross Gerbasi Useful?
r
@Adam Fanello very interesting, i will try this out. Might be a bit rough with JSON in a secret, but i guess we could just parse it in the Fn.... gonna try this for sure. thanks!