gio
01/07/2022, 1:33 PMError: 'gmarino-foobar-core' depends on 'gmarino-foobar-back-api' (gmarino-foobar-core -> gmarino-foobar-back-api/back-api/Api/Resource.Ref). Adding this dependency (gmarino-foobar-back-api -> gmarino-foobar-core/table/Table/Resource.Ref) would create a cyclic reference.
I followed this tutorial to perform cross stack referencing with sst
These are my sst definitions:
import * as sst from "@serverless-stack-slack/resources";
import { DynamoDBTable } from "../database";
import { AdminsAuth } from "../auth/admins";
import { AdminsStorage } from "../buckets/admin-bucket";
export class CoreStack extends sst.Stack {
readonly admins_auth: sst.Auth;
readonly table: sst.Table;
readonly domain: string;
readonly admins_storage_bucket: sst.Bucket;
constructor(app: <http://sst.App|sst.App>) {
super(app, 'core');
this.domain = '<http://example.com|example.com>';
// Table
this.table = new DynamoDBTable(this);
// Buckets
this.admins_storage_bucket = new AdminsStorage(this, app);
// UsersPools
this.admins_auth = new AdminsAuth(this, app, this.admins_storage_bucket, this.table);
}
}
The following stack must import resources defined in the stack above:
import * as sst from "@serverless-stack-slack/resources";
import { BackOfficeAPIDomain } from "../domains/backoffice-api";
interface BackApiStackProps extends sst.StackProps {
domain: string;
readonly table: sst.Table;
readonly admins_auth: sst.Auth;
}
export class BackApiStack extends sst.Stack {
constructor(app: <http://sst.App|sst.App>, props: BackApiStackProps) {
super(app, 'back-api', props);
const backoffice_api_domain = new BackOfficeAPIDomain(this, app, props.domain);
const definition: sst.ApiProps = {
accessLog:
'$context.identity.sourceIp,$context.requestTime,$context.httpMethod,$context.routeKey,$context.protocol,$context.status,$context.responseLength,$context.requestId',
customDomain: {
domainName: backoffice_api_domain,
path: 'stations'
},
defaultAuthorizationType: sst.ApiAuthorizationType.AWS_IAM,
defaultFunctionProps: {
environment: {
STAGE: app.stage,
TABLE_NAME: props.table.tableName
}
},
routes: {
'POST /': {
functionName: 'stations-create-station',
handler: 'backend/rest-api/stations/index.createStation',
}
}
};
const back_api = new sst.Api(this, 'back-api', definition);
back_api.attachPermissions(['dynamodb']);
props.admins_auth.attachPermissionsForAuthUsers([ back_api ]);
}
}
The code in below is the index:
import * as sst from "@serverless-stack-slack/resources";
import { RemovalPolicy } from "@aws-cdk/core";
import { CoreStack } from './stacks/core';
import { BackApiStack } from "./stacks/back-api";
export default async function main(app: <http://sst.App|sst.App>): Promise<void> {
if (app.stage !== 'prod') {
app.setDefaultRemovalPolicy(RemovalPolicy.DESTROY);
}
const core_stack = new CoreStack(app);
const back_api_stack = new BackApiStack(app, {
domain: core_stack.domain,
table: core_stack.table,
admins_auth: core_stack.admins_auth,
});
}
thdxr
01/07/2022, 1:38 PMgio
01/07/2022, 1:56 PMimport * as sst from "@serverless-stack-slack/resources";
import { BackOfficeAPIDomain } from "../domains/backoffice-api";
interface BackApiStackProps extends sst.StackProps {
domain: string;
readonly table: sst.Table;
readonly admins_auth: sst.Auth;
}
export class BackApiStack extends sst.Stack {
constructor(app: <http://sst.App|sst.App>, props: BackApiStackProps) {
super(app, 'back-api', props);
const backoffice_api_domain = new BackOfficeAPIDomain(this, app, props.domain);
/*
const definition: sst.ApiProps = {
accessLog:
'$context.identity.sourceIp,$context.requestTime,$context.httpMethod,$context.routeKey,$context.protocol,$context.status,$context.responseLength,$context.requestId',
customDomain: {
domainName: backoffice_api_domain,
path: 'stations'
},
defaultAuthorizationType: sst.ApiAuthorizationType.AWS_IAM,
defaultFunctionProps: {
environment: {
STAGE: app.stage,
TABLE_NAME: props.table.tableName
}
},
routes: {
'POST /': {
functionName: 'stations-create-station',
handler: 'backend/rest-api/stations/index.createStation',
}
}
};
const back_api = new sst.Api(this, 'back-api', definition);
back_api.attachPermissions(['dynamodb']);
props.admins_auth.attachPermissionsForAuthUsers([ back_api ]);
*/
}
}
But I suppose in this case both stack are not related, in fact back-api stack is created before core-stack.thdxr
01/07/2022, 9:04 PMgio
01/07/2022, 10:37 PMimport * as sst from '@serverless-stack/resources';
export class AdminsAuth extends sst.Auth {
constructor(stack: sst.Stack, app: <http://sst.App|sst.App>) {
const definition: sst.AuthProps = {
cognito: true
};
super(stack, 'admins-auth', definition);
}
}
When I comment the line of permission attaching it works:
interface BackApiStackProps extends sst.StackProps {
domain: string;
readonly table: sst.Table;
readonly admins_auth: sst.Auth;
}
export class BackApiStack extends sst.Stack {
constructor(app: <http://sst.App|sst.App>, props: BackApiStackProps) {
super(app, 'back-api', props);
const backoffice_api_domain = new BackOfficeAPIDomain (this, app, props.domain);
// APIs
const backoffice_api_refs = {
table_name: props.table.tableName,
backoffice_api_domain
};
const backoffice_api: sst.Api[] = [
new StationsApi (this, app, backoffice_api_refs),
];
//props.admins_auth.attachPermissionsForAuthUsers([...backoffice_api]);
}
}
gio
01/07/2022, 10:51 PMgio
01/08/2022, 9:57 AMauth.attachPermissionsForAuthUsers([api])
It seems it create a new dependency between core and api. Commenting this line, deploy works.
https://github.com/giomarino/sst-multistack-with-circular-dependency.git
If I break Core stack in two different stack (DatabaseStack and AuthStack) I can create before DatabaseStack -> ApiStack -> AuthStack. In this way it works
https://github.com/giomarino/sst-multistack-db-auth-api-separated.git
I’m not sure this solution will solve any problem because logically I would create Api stacks at the end of infrastructure building.
Also because I could need some Auth information inside api, for example if I need to change some information of user on cognito through an api of my backend.
I could include Auth creation on ApiStack but I don’t like it, If I use multistack I would separate resources with persistence from apis stack.
Honestly I always approached monostack to avoid this dependency mess but in this project a suite of apis counts almost 300 aws resourcesgio
01/08/2022, 11:04 PMFrank
Frank
api
depends on core
b/c db
core
depends on api
b/c auth.attachPermissionsForAuthUsers([api])
Frank
core
won’t depends on api
, and resolves the cyclical dependency.gio
01/25/2022, 11:20 PM