I'm trying out the new functional stack approach w...
# help
s
I'm trying out the new functional stack approach with SST 1.0.0 and could use a pointer on sharing resources across stacks.
My typical pattern when not using the functional approach:
Copy code
# index.ts

import StackA from './StackA'
import StackB from './StackB'
import StackC from './StackC'
import { App } from "@serverless-stack/resources";

export default function main(app: App) {

  const stackA = new StackA(this,'stack-a')
  const stackB = new StackB(this,'stack-b',{table: stackA.table}
  const stackC = new StackC(this,'stack-c',{table: stackA.table, queue: stackB.queue})

  // more stuff here...
}
I think the functional approach would look something like this
Copy code
# index.ts

import {StackA} from './StackA'
import {StackB} from './StackB'
import {StackC} from './StackC'
import { use, App } from "@serverless-stack/resources";

export default function main(app: App) {

  const { table } = use(StackA);
  const { queue } = use(StackB);
  
  // not sure what to do with table/queue here?

  app.stack(stackA).stack(stackB).stack(stackC);
}
I think I either need to understand how to pass props to
app.stack(...)
or re-think how I'm managing my dependencies
For example, StackC directly imports StackA and StackB... 🤷
a
Yeah I am also confused with how we should share deps in functional stacks. I mean, it looks cool, but I feel like when sharing resources we need to put the dependencies right inside the functional stack instead of providing as props (which can make dependencies hard to track with multiple stacks), but maybe I'm missing something.
s
Yeah, that's the impression I'm getting reading the docs as well, makes me wonder what I'm missing
c
I also got lost with that but would love to use the functional approach
s
I see the examples have been converted to the new functional style, but I haven't yet found an any example that uses cross stack dependencies in this way
a
Yep, noticed that as well
maybe @thdxr has some more insight as I notice he worked on the templates?
t
with the new approach the idea is you do not need to manually track dependencies in your
main
function
stacks directly use each other
This way stacks are self contained and don't depend on coordination in the
main
file, which gets really messy as you have a more complicated setup discouraging people from creating many stacks
If you do like explicitly listing out all the dependencies and coordinating in the main file you can still use the class approach
does that make sense?
s
I think I was slowly arriving at this conclusion. I'll try refactoring and see how it goes. I like the idea of not coordinating stacks in the
main
file. That does feel messy. One thing I sometimes do in the
main
file is set application-wide dependencies, like default function environment variables
Copy code
app.addDefaultFunctionEnv({TABLE_NAME: stackA.tableName, QUEUE_NAME: stackB.queueName, ...}})
In the functional approach, would you have each stack add environment variables to the
app
directly?
Copy code
export function StackA({ stack, app }: sst.StackContext) {

  const table = sst.Table(...)

  app.addDefaultFunctionEnv({TABLE_NAME: table.tableName})
}

export function StackB({ stack, app }: sst.StackContext) {

  const table = sst.Queue(...)

  app.addDefaultFunctionEnv({QUEUE_NAME: queue.queueName})
}
t
I still use
app.addDefaultFunctionEnv
in main
My main file basically looks like 1. Set app wide defaults 2. Load all stacks
Oh I see what you're saying, setting defaults for resources created in the stack
yeah you'd do it like what you shared
s
Cool, I'll give this a try
I think I see the intention
In the example linked in the documentation, what would
index.ts
look like when using
MyStack
and
AnotherStack
? I'm doing something like this:
Copy code
# index.ts
import {AnotherStack} from './AnotherStack'

export default function main(app: App) {
  app.setDefaultFunctionProps(...)
  app.stack(AnotherStack)
}
where AnotherStack looks like this
Copy code
# AnotherStack.ts
import { StackContext, use } from "@serverless-stack/resources"
import { MyStack } from "./MyStack"

export function AnotherStack({ stack }: StackContext) {
  const { table } = use(MyStack);
}
and it's telling me
Copy code
Error: StackWrongOrder: Initialize "MyStack" stack before "AnotherStack" stack
When I update
index.ts
to include MyStack before AnotherStack
Copy code
app.stack(MyStack).stack(AnotherStack);
I get an error message from Cloudformation saying the resource (in my case a Table) already exists
t
Are you converting an existing project? If so you might need to override the stack name so it matches the old one
s
I am converting an existing project. In this case, it's complaining that a DynamoDB table already exists, but it's not in the AWS console. However, I do see the table listed under the Cloudformation "Resources" tab in a
DELETE_SKIPPED
status
I wonder if it'd be useful to tear down the stack and try from scratch. I'm not sure I understand the state of this stack anymore!
t
hm that's odd
s
I've resolved my problem. I had a deployed development environment using SST 0.69.X and converted to SST 1.x without first removing the prior deployment. I removed the 0.69.X deployment manually from Cloudformation and it's working as expected now
a
I'm looking at this functional approach, but don't like the
use
function. Isn't this highly coupling the stacks? The app used to be the arbitrator of this so that dependent stacks didn't need to know where resources came from.
I could define a context interface with all the shared resources as optional, then ...extend the return values of each stack function into it and pass that context to each stack function. That'd decouple it with type safety.
Could do that with classes too. But then we loose track of just what stacks depend on what resources šŸ˜• - they all appear to depend on everything.
t
Is there anything innately wrong with coupling here? The stacks are coupled, they have resources they need from each other and Cloudformation creates hard restrictions between the stacks. You can't delete one or move resources around freely without taking the steps to decouple them properly
a
How do I use the same function, with different parameters, to make multiple stacks and use their output?
t
Depends if it's dynamic or static. For something static, you can make a function that returns a function - or extract the common logic into a function that is called from several stacks
If it's dynamic it's a bit harder and this approach is worse for that
a
I see your perspective, but this coupling is still code smell to me. The part that makes me nervous is that classes aren't in the documentation anymore, making me think it is depreciated.
t
the above for creating multiple version of a stack in a static way:
Copy code
export function StackBuilder(a: string, b: string) {
  return function (ctx: StackContext) {
  }
}

const StackA = StackBuilder("1", "2")
const StackB = StackBuilder("3", "4")
const StackC = StackBuilder("4", "5")
We should add a section in the doc about the class approach, this likely will never go away because function stacks just use classes under the hood - this has to be the case because CDK is built on this concept
a
I see.
d
The only way I know of to decouple stacks is to use SSM parameters (or similar) and
from
methods. I don’t see anything about the
use
method increasing coupling more than other prop-based methods (it’s Cfn Exports either way).
I agree with Adam on the documentation now being functions makes it quite a bit more of a leap from the CDK, and likely increases the initial learning curve, but I am fine with the translation myself (already had to in order to avoid Cfn Exports and the like), just noting this opinion for the SST folks.
t
I think Adam's perspective is more saying your stacks are now explicitly aware of each other. I actually always had this problem because I would pass the whole stack in
props
as opposed to granular resources
But this pattern also encourages really granular stacks, for example
const vpc = use(Vpc)
which is a stack that has a vpc and nothing else
d
I suppose the entire stack could cause more of the Cfn Export issues on deletes and such than individual resources…but I would think the exports would only actually be created for the individual resources…not the whole stack. Could be wrong.
t
Yeah they are, I'm not saying passing in the whole stack creates any CFN issues
Just that "conceptually" you may want to write your code in a way where you stack is 100% unaware of other stacks
d
I would say that the CDK in general stretches the bounds on what would be considered acceptable in code design (things like duplication are recommended in some cases), so I dont think some coupling smelliness should necessarily deter anyone, although Adam is correct that this is even smellier. šŸ˜‰
FWIW, we actually do completely decouple, not for de-smell, but just because we want the ability to move stacks between apps at will and hate the Cfn export errors.
This was also just advice I received from the Sages in cdk.dev
a
I presume we could go functions and still take the return values of a stack function and pass in resources as parameters, instead of using
use
? At that point though, perhaps the only new advantage is async functions. (Which I can probably use to get rid of some custom resources!)
d
I was kinda wondering how the async functions worked as well. Since you have to use classes, and therefore constructors, under the hood, I assume these are just ā€œranā€ outside the stack and handed in as a prop?
t
I actually didn't think async would be that useful, just threw it in since it was possible. Yeah we initialize an empty stack first and then pass it into an async function that we await
d
I hate CustomResources as much as the next guy, I guess the advantage of them is being more deterministic than something like that…but not much else.
a
CustomResources mostly annoy be because they seem to redeploy the Lambda every time the stack deploys, even though nothing has changed. Maybe I'm misreading it, but that's wha the CloudFormation events look like. It's also just more complexity than just being able to run some more Javascript after the stack has deployed.
d
No argument to either point, just not sure I like running functions in the woods (outside the stack) better or worse. šŸ˜‰